/* * Copyright (C) 2008 The Android Open Source Project * Copyright (c) 2010-2011 The Linux Foundation. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server; import com.android.internal.net.IPVersion; import com.android.internal.telephony.Phone; import com.android.server.connectivity.Tethering; import android.app.Notification; import android.app.NotificationManager; import android.content.Context; import android.content.ContentResolver; import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.LinkCapabilities; import android.net.ExtraLinkCapabilities; import android.net.LinkInfo; import android.net.MobileDataStateTracker; import android.net.NetworkInfo; import android.net.NetworkStateTracker; import android.net.NetworkUtils; import android.net.wifi.WifiStateTracker; import android.net.wimax.WimaxManagerConstants; import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.provider.Settings; import android.text.TextUtils; import android.util.EventLog; import android.util.Slog; import java.io.FileDescriptor; import java.io.PrintWriter; import java.net.InetAddress; import java.net.UnknownHostException; import com.android.internal.telephony.Phone; import com.android.server.connectivity.Tethering; import dalvik.system.DexClassLoader; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.List; import java.util.Map; /** * @hide */ public class ConnectivityService extends IConnectivityManager.Stub { private static final boolean DBG = true; private static final String TAG = "ConnectivityService"; // how long to wait before switching back to a radio's default network private static final int RESTORE_DEFAULT_NETWORK_DELAY = 1 * 60 * 1000; // system property that can override the above value private static final String NETWORK_RESTORE_DELAY_PROP_NAME = "android.telephony.apn-restore"; private Tethering mTethering; private boolean mTetheringConfigValid = false; /** * Sometimes we want to refer to the individual network state * trackers separately, and sometimes we just want to treat them * abstractly. */ private NetworkStateTracker mNetTrackers[]; /** * A per Net list of the PID's that requested access to the net * used both as a refcount and for per-PID DNS selection */ private List mNetRequestersPids[]; // priority order of the nettrackers // (excluding dynamically set mNetworkPreference) // TODO - move mNetworkTypePreference into this private int[] mPriorityList; private Context mContext; private int mNetworkPreference; private int mActiveDefaultNetwork = -1; // 0 is full bad, 100 is full good private int mDefaultInetCondition = 0; private int mDefaultInetConditionPublished = 0; private boolean mInetConditionChangeInFlight = false; private int mDefaultConnectionSequence = 0; private int mNumDnsEntries; private boolean mTestMode; private static ConnectivityService sServiceInstance; private static final int ENABLED = 1; private static final int DISABLED = 0; // Share the event space with NetworkStateTracker (which can't see this // internal class but sends us events). If you change these, change // NetworkStateTracker.java too. private static final int MIN_NETWORK_STATE_TRACKER_EVENT = 1; private static final int MAX_NETWORK_STATE_TRACKER_EVENT = 100; /** * used internally as a delayed event to make us switch back to the * default network */ private static final int EVENT_RESTORE_DEFAULT_NETWORK = MAX_NETWORK_STATE_TRACKER_EVENT + 1; /** * used internally to change our mobile data enabled flag */ private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = MAX_NETWORK_STATE_TRACKER_EVENT + 2; /** * used internally to change our network preference setting * arg1 = networkType to prefer */ private static final int EVENT_SET_NETWORK_PREFERENCE = MAX_NETWORK_STATE_TRACKER_EVENT + 3; /** * used internally to synchronize inet condition reports * arg1 = networkType * arg2 = condition (0 bad, 100 good) */ private static final int EVENT_INET_CONDITION_CHANGE = MAX_NETWORK_STATE_TRACKER_EVENT + 4; /** * used internally to mark the end of inet condition hold periods * arg1 = networkType */ private static final int EVENT_INET_CONDITION_HOLD_END = MAX_NETWORK_STATE_TRACKER_EVENT + 5; /** * used internally to set the background data preference * arg1 = TRUE for enabled, FALSE for disabled */ private static final int EVENT_SET_BACKGROUND_DATA = MAX_NETWORK_STATE_TRACKER_EVENT + 6; /** * used internally to set enable/disable cellular data * arg1 = ENBALED or DISABLED */ private static final int EVENT_SET_MOBILE_DATA = MAX_NETWORK_STATE_TRACKER_EVENT + 7; private Handler mHandler; private ILinkManager mLinkManager = null; private boolean mCneStarted = false; // list of DeathRecipients used to make sure features are turned off when // a process dies private List mFeatureUsers; private boolean mSystemReady; private Intent mInitialBroadcast; // used in DBG mode to track inet condition reports private static final int INET_CONDITION_LOG_MAX_SIZE = 15; private ArrayList mInetLog; private static class NetworkAttributes { /** * Class for holding settings read from resources. */ public String mName; public int mType; public int mRadio; public int mPriority; public NetworkInfo.State mLastState; public NetworkAttributes(String init) { String fragments[] = init.split(","); mName = fragments[0].toLowerCase(); mType = Integer.parseInt(fragments[1]); mRadio = Integer.parseInt(fragments[2]); mPriority = Integer.parseInt(fragments[3]); mLastState = NetworkInfo.State.UNKNOWN; } public boolean isDefault() { return (mType == mRadio); } } NetworkAttributes[] mNetAttributes; int mNetworksDefined; private static class RadioAttributes { public int mSimultaneity; public int mType; public RadioAttributes(String init) { String fragments[] = init.split(","); mType = Integer.parseInt(fragments[0]); mSimultaneity = Integer.parseInt(fragments[1]); } } RadioAttributes[] mRadioAttributes; private static class ConnectivityThread extends Thread { private Context mContext; private ConnectivityThread(Context context) { super("ConnectivityThread"); mContext = context; } @Override public void run() { Looper.prepare(); synchronized (this) { sServiceInstance = new ConnectivityService(mContext); notifyAll(); } Looper.loop(); } public static ConnectivityService getServiceInstance(Context context) { ConnectivityThread thread = new ConnectivityThread(context); thread.start(); synchronized (thread) { while (sServiceInstance == null) { try { // Wait until sServiceInstance has been initialized. thread.wait(); } catch (InterruptedException ignore) { Slog.e(TAG, "Unexpected InterruptedException while waiting"+ " for ConnectivityService thread"); } } } return sServiceInstance; } } public static ConnectivityService getInstance(Context context) { return ConnectivityThread.getServiceInstance(context); } private ConnectivityService(Context context) { if (DBG) Slog.v(TAG, "ConnectivityService starting up"); // setup our unique device name String id = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); if (id != null && id.length() > 0) { String name = new String("android_").concat(id); SystemProperties.set("net.hostname", name); } mContext = context; mNetTrackers = new NetworkStateTracker[ ConnectivityManager.MAX_NETWORK_TYPE+1]; mHandler = new MyHandler(); mNetworkPreference = getPersistedNetworkPreference(); mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1]; mNetAttributes = new NetworkAttributes[ConnectivityManager.MAX_NETWORK_TYPE+1]; // Load device network attributes from resources String[] raStrings = context.getResources().getStringArray( com.android.internal.R.array.radioAttributes); for (String raString : raStrings) { RadioAttributes r = new RadioAttributes(raString); if (r.mType > ConnectivityManager.MAX_RADIO_TYPE) { Slog.e(TAG, "Error in radioAttributes - ignoring attempt to define type " + r.mType); continue; } if (mRadioAttributes[r.mType] != null) { Slog.e(TAG, "Error in radioAttributes - ignoring attempt to redefine type " + r.mType); continue; } mRadioAttributes[r.mType] = r; } String[] naStrings = context.getResources().getStringArray( com.android.internal.R.array.networkAttributes); for (String naString : naStrings) { try { NetworkAttributes n = new NetworkAttributes(naString); if (n.mType > ConnectivityManager.MAX_NETWORK_TYPE) { Slog.e(TAG, "Error in networkAttributes - ignoring attempt to define type " + n.mType); continue; } if (mNetAttributes[n.mType] != null) { Slog.e(TAG, "Error in networkAttributes - ignoring attempt to redefine type " + n.mType); continue; } if (mRadioAttributes[n.mRadio] == null) { Slog.e(TAG, "Error in networkAttributes - ignoring attempt to use undefined " + "radio " + n.mRadio + " in network type " + n.mType); continue; } mNetAttributes[n.mType] = n; mNetworksDefined++; } catch(Exception e) { // ignore it - leave the entry null } } // high priority first mPriorityList = new int[mNetworksDefined]; { int insertionPoint = mNetworksDefined-1; int currentLowest = 0; int nextLowest = 0; while (insertionPoint > -1) { for (NetworkAttributes na : mNetAttributes) { if (na == null) continue; if (na.mPriority < currentLowest) continue; if (na.mPriority > currentLowest) { if (na.mPriority < nextLowest || nextLowest == 0) { nextLowest = na.mPriority; } continue; } mPriorityList[insertionPoint--] = na.mType; } currentLowest = nextLowest; nextLowest = 0; } } mNetRequestersPids = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1]; for (int i : mPriorityList) { mNetRequestersPids[i] = new ArrayList(); } mFeatureUsers = new ArrayList(); mNumDnsEntries = 0; mTestMode = SystemProperties.get("cm.test.mode").equals("true") && SystemProperties.get("ro.build.type").equals("eng"); /* * Create the network state trackers for Wi-Fi and mobile * data. Maybe this could be done with a factory class, * but it's not clear that it's worth it, given that * the number of different network types is not going * to change very often. */ boolean noMobileData = !getMobileDataEnabled(); for (int netType : mPriorityList) { switch (mNetAttributes[netType].mRadio) { case ConnectivityManager.TYPE_WIFI: if (DBG) Slog.v(TAG, "Starting Wifi Service."); WifiStateTracker wst = new WifiStateTracker(context, mHandler); WifiService wifiService = new WifiService(context, wst); ServiceManager.addService(Context.WIFI_SERVICE, wifiService); wifiService.startWifi(); mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst; wst.startMonitoring(); break; case ConnectivityManager.TYPE_MOBILE: mNetTrackers[netType] = new MobileDataStateTracker(context, mHandler, netType, mNetAttributes[netType].mName); mNetTrackers[netType].startMonitoring(); if (noMobileData) { if (DBG) Slog.d(TAG, "tearing down Mobile networks due to setting"); mNetTrackers[netType].teardown(); } break; case ConnectivityManager.TYPE_WIMAX: NetworkStateTracker nst = makeWimaxStateTracker(); if (nst != null) { nst.startMonitoring(); } mNetTrackers[netType] = nst; if (noMobileData) { if (DBG) Slog.d(TAG, "tearing down WiMAX networks due to setting"); mNetTrackers[netType].teardown(); } break; default: Slog.e(TAG, "Trying to create a DataStateTracker for an unknown radio type " + mNetAttributes[netType].mRadio); continue; } } mTethering = new Tethering(mContext, mHandler.getLooper()); mTetheringConfigValid = (((mNetTrackers[ConnectivityManager.TYPE_MOBILE_DUN] != null) || !mTethering.isDunRequired()) && (mTethering.getTetherableUsbRegexs().length != 0 || mTethering.getTetherableWifiRegexs().length != 0) && mTethering.getUpstreamIfaceRegexs().length != 0); if (DBG) { mInetLog = new ArrayList(); } } private NetworkStateTracker makeWimaxStateTracker() { //Initialize Wimax DexClassLoader wimaxClassLoader; Class wimaxStateTrackerClass = null; Class wimaxServiceClass = null; Class wimaxManagerClass; String wimaxJarLocation; String wimaxLibLocation; String wimaxManagerClassName; String wimaxServiceClassName; String wimaxStateTrackerClassName; NetworkStateTracker wimaxStateTracker = null; boolean isWimaxEnabled = mContext.getResources().getBoolean( com.android.internal.R.bool.config_wimaxEnabled); if (isWimaxEnabled) { try { wimaxJarLocation = mContext.getResources().getString( com.android.internal.R.string.config_wimaxServiceJarLocation); wimaxLibLocation = mContext.getResources().getString( com.android.internal.R.string.config_wimaxNativeLibLocation); wimaxManagerClassName = mContext.getResources().getString( com.android.internal.R.string.config_wimaxManagerClassname); wimaxServiceClassName = mContext.getResources().getString( com.android.internal.R.string.config_wimaxServiceClassname); wimaxStateTrackerClassName = mContext.getResources().getString( com.android.internal.R.string.config_wimaxStateTrackerClassname); wimaxClassLoader = new DexClassLoader(wimaxJarLocation, new ContextWrapper(mContext).getCacheDir().getAbsolutePath(), wimaxLibLocation,ClassLoader.getSystemClassLoader()); try { wimaxManagerClass = wimaxClassLoader.loadClass(wimaxManagerClassName); wimaxStateTrackerClass = wimaxClassLoader.loadClass(wimaxStateTrackerClassName); wimaxServiceClass = wimaxClassLoader.loadClass(wimaxServiceClassName); } catch (ClassNotFoundException ex) { ex.printStackTrace(); return null; } } catch(Resources.NotFoundException ex) { Slog.e(TAG, "Wimax Resources does not exist!!! "); return null; } try { Slog.v(TAG, "Starting Wimax Service... "); Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor (new Class[] {Context.class,Handler.class}); wimaxStateTracker = (NetworkStateTracker)wmxStTrkrConst.newInstance(mContext,mHandler); Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor (new Class[] {Context.class,wimaxStateTrackerClass}); wmxSrvConst.setAccessible(true); IBinder svcInvoker = (IBinder) wmxSrvConst.newInstance(mContext,wimaxStateTracker); wmxSrvConst.setAccessible(false); ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker); } catch(ClassCastException ex) { ex.printStackTrace(); return null; } catch (NoSuchMethodException ex) { ex.printStackTrace(); return null; } catch (InstantiationException ex) { ex.printStackTrace(); return null; } catch(IllegalAccessException ex) { ex.printStackTrace(); return null; } catch(InvocationTargetException ex) { ex.printStackTrace(); return null; } catch(Exception ex) { ex.printStackTrace(); return null; } } else { Slog.e(TAG, "Wimax is not enabled or not added to the network attributes!!! "); return null; } return wimaxStateTracker; } /** * Sets the preferred network. * @param preference the new preference */ public void setNetworkPreference(int preference) { enforceChangePermission(); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0)); } public int getNetworkPreference() { enforceAccessPermission(); int preference; synchronized(this) { preference = mNetworkPreference; } return preference; } private void handleSetNetworkPreference(int preference) { if (ConnectivityManager.isNetworkTypeValid(preference) && mNetAttributes[preference] != null && mNetAttributes[preference].isDefault()) { if (mNetworkPreference != preference) { final ContentResolver cr = mContext.getContentResolver(); Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, preference); synchronized(this) { mNetworkPreference = preference; } if (isCneAware()) { /* send it to cne and it will handle it */ mLinkManager.setDefaultConnectionNwPref(preference); } else { enforcePreference(); } } } } private int getPersistedNetworkPreference() { final ContentResolver cr = mContext.getContentResolver(); final int networkPrefSetting = Settings.Secure .getInt(cr, Settings.Secure.NETWORK_PREFERENCE, -1); if (networkPrefSetting != -1) { return networkPrefSetting; } return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE; } /** * Make the state of network connectivity conform to the preference settings * In this method, we only tear down a non-preferred network. Establishing * a connection to the preferred network is taken care of when we handle * the disconnect event from the non-preferred network * (see {@link #handleDisconnect(NetworkInfo)}). */ private void enforcePreference() { if (mNetTrackers[mNetworkPreference].getNetworkInfo().isConnected()) return; if (!mNetTrackers[mNetworkPreference].isAvailable()) return; for (int t=0; t <= ConnectivityManager.MAX_RADIO_TYPE; t++) { if (t != mNetworkPreference && mNetTrackers[t] != null && mNetTrackers[t].getNetworkInfo().isConnected()) { if (DBG) { Slog.d(TAG, "tearing down " + mNetTrackers[t].getNetworkInfo() + " in enforcePreference"); } teardown(mNetTrackers[t]); } } } private boolean teardown(NetworkStateTracker netTracker) { if (netTracker.teardown()) { netTracker.setTeardownRequested(true); return true; } else { return false; } } /** * Return NetworkInfo for the active (i.e., connected) network interface. * It is assumed that at most one network is active at a time. If more * than one is active, it is indeterminate which will be returned. * @return the info for the active network, or {@code null} if none is * active */ public NetworkInfo getActiveNetworkInfo() { enforceAccessPermission(); if (mActiveDefaultNetwork != -1) { return mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); } return null; } public NetworkInfo getNetworkInfo(int networkType) { enforceAccessPermission(); if (ConnectivityManager.isNetworkTypeValid(networkType)) { NetworkStateTracker t = mNetTrackers[networkType]; if (t != null) return t.getNetworkInfo(); } return null; } public NetworkInfo[] getAllNetworkInfo() { enforceAccessPermission(); NetworkInfo[] result = new NetworkInfo[mNetworksDefined]; int i = 0; for (NetworkStateTracker t : mNetTrackers) { if(t != null) result[i++] = t.getNetworkInfo(); } return result; } public boolean setRadios(boolean turnOn) { boolean result = true; enforceChangePermission(); for (NetworkStateTracker t : mNetTrackers) { if (t != null) result = t.setRadio(turnOn) && result; } return result; } public boolean setRadio(int netType, boolean turnOn) { enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(netType)) { return false; } NetworkStateTracker tracker = mNetTrackers[netType]; return tracker != null && tracker.setRadio(turnOn); } /** * Used to notice when the calling process dies so we can self-expire * * Also used to know if the process has cleaned up after itself when * our auto-expire timer goes off. The timer has a link to an object. * */ private class FeatureUser implements IBinder.DeathRecipient { private final int mNetworkType; private final String mFeature; private final IBinder mBinder; private final int mPid; private final int mUid; private final long mCreateTime; FeatureUser(int type, String feature, IBinder binder) { super(); mNetworkType = type; mFeature = feature; mBinder = binder; mPid = getCallingPid(); mUid = getCallingUid(); mCreateTime = System.currentTimeMillis(); try { mBinder.linkToDeath(this, 0); } catch (RemoteException e) { binderDied(); } } void unlinkDeathRecipient() { mBinder.unlinkToDeath(this, 0); } public void binderDied() { Slog.d(TAG, "ConnectivityService FeatureUser binderDied(" + mNetworkType + ", " + mFeature + ", " + mBinder + "), created " + (System.currentTimeMillis() - mCreateTime) + " mSec ago"); stopUsingNetworkFeature(this, false); } public void expire() { Slog.d(TAG, "ConnectivityService FeatureUser expire(" + mNetworkType + ", " + mFeature + ", " + mBinder +"), created " + (System.currentTimeMillis() - mCreateTime) + " mSec ago"); stopUsingNetworkFeature(this, false); } public String toString() { return "FeatureUser("+mNetworkType+","+mFeature+","+mPid+","+mUid+"), created " + (System.currentTimeMillis() - mCreateTime) + " mSec ago"; } } // javadoc from interface public int startUsingNetworkFeature(int networkType, String feature, IBinder binder) { if (DBG) { Slog.d(TAG, "startUsingNetworkFeature for net " + networkType + ": " + feature); } enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType) || mNetAttributes[networkType] == null) { return Phone.APN_REQUEST_FAILED; } FeatureUser f = new FeatureUser(networkType, feature, binder); // TODO - move this into the MobileDataStateTracker int usedNetworkType = networkType; if(networkType == ConnectivityManager.TYPE_MOBILE) { if (!getMobileDataEnabled()) { if (DBG) Slog.d(TAG, "requested special network with data disabled - rejected"); return Phone.APN_TYPE_NOT_AVAILABLE; } if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; } } NetworkStateTracker network = mNetTrackers[usedNetworkType]; if (network != null) { if (usedNetworkType != networkType) { Integer currentPid = new Integer(getCallingPid()); NetworkStateTracker radio = mNetTrackers[networkType]; NetworkInfo ni = network.getNetworkInfo(); if (ni.isAvailable() == false) { if (DBG) Slog.d(TAG, "special network not available"); return Phone.APN_TYPE_NOT_AVAILABLE; } synchronized(this) { mFeatureUsers.add(f); if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) { // this gets used for per-pid dns when connected mNetRequestersPids[usedNetworkType].add(currentPid); } } mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK, f), getRestoreDefaultNetworkDelay()); if ((ni.isConnectedOrConnecting() == true) && !network.isTeardownRequested()) { if (ni.isConnected() == true) { // add the pid-specific dns handleDnsConfigurationChange(networkType); if (DBG) Slog.d(TAG, "special network already active"); return Phone.APN_ALREADY_ACTIVE; } if (DBG) Slog.d(TAG, "special network already connecting"); return Phone.APN_REQUEST_STARTED; } // check if the radio in play can make another contact // assume if cannot for now if (DBG) Slog.d(TAG, "reconnecting to special network"); network.reconnect(); return Phone.APN_REQUEST_STARTED; } else { synchronized(this) { mFeatureUsers.add(f); } mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_RESTORE_DEFAULT_NETWORK, f), getRestoreDefaultNetworkDelay()); return network.startUsingNetworkFeature(feature, getCallingPid(), getCallingUid()); } } return Phone.APN_TYPE_NOT_AVAILABLE; } // javadoc from interface public int stopUsingNetworkFeature(int networkType, String feature) { enforceChangePermission(); int pid = getCallingPid(); int uid = getCallingUid(); FeatureUser u = null; boolean found = false; synchronized(this) { for (int i = 0; i < mFeatureUsers.size() ; i++) { u = (FeatureUser)mFeatureUsers.get(i); if (uid == u.mUid && pid == u.mPid && networkType == u.mNetworkType && TextUtils.equals(feature, u.mFeature)) { found = true; break; } } } if (found && u != null) { // stop regardless of how many other time this proc had called start return stopUsingNetworkFeature(u, true); } else { // none found! if (DBG) Slog.d(TAG, "ignoring stopUsingNetworkFeature - not a live request"); return 1; } } private int stopUsingNetworkFeature(FeatureUser u, boolean ignoreDups) { int networkType = u.mNetworkType; String feature = u.mFeature; int pid = u.mPid; int uid = u.mUid; NetworkStateTracker tracker = null; boolean callTeardown = false; // used to carry our decision outside of sync block if (DBG) { Slog.d(TAG, "stopUsingNetworkFeature for net " + networkType + ": " + feature); } if (!ConnectivityManager.isNetworkTypeValid(networkType)) { return -1; } // need to link the mFeatureUsers list with the mNetRequestersPids state in this // sync block synchronized(this) { // check if this process still has an outstanding start request if (!mFeatureUsers.contains(u)) { if (DBG) Slog.d(TAG, "ignoring - this process has no outstanding requests"); return 1; } u.unlinkDeathRecipient(); mFeatureUsers.remove(mFeatureUsers.indexOf(u)); // If we care about duplicate requests, check for that here. // // This is done to support the extension of a request - the app // can request we start the network feature again and renew the // auto-shutoff delay. Normal "stop" calls from the app though // do not pay attention to duplicate requests - in effect the // API does not refcount and a single stop will counter multiple starts. if (ignoreDups == false) { for (int i = 0; i < mFeatureUsers.size() ; i++) { FeatureUser x = (FeatureUser)mFeatureUsers.get(i); if (x.mUid == u.mUid && x.mPid == u.mPid && x.mNetworkType == u.mNetworkType && TextUtils.equals(x.mFeature, u.mFeature)) { if (DBG) Slog.d(TAG, "ignoring stopUsingNetworkFeature as dup is found"); return 1; } } } // TODO - move to MobileDataStateTracker int usedNetworkType = networkType; if (networkType == ConnectivityManager.TYPE_MOBILE) { if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_MMS)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_MMS; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_SUPL)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_SUPL; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_DUN; } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_HIPRI)) { usedNetworkType = ConnectivityManager.TYPE_MOBILE_HIPRI; } } tracker = mNetTrackers[usedNetworkType]; if (tracker == null) { if (DBG) Slog.d(TAG, "ignoring - no known tracker for net type " + usedNetworkType); return -1; } if (usedNetworkType != networkType) { Integer currentPid = new Integer(pid); mNetRequestersPids[usedNetworkType].remove(currentPid); reassessPidDns(pid, true); if (mNetRequestersPids[usedNetworkType].size() != 0) { if (DBG) Slog.d(TAG, "not tearing down special network - " + "others still using it"); return 1; } callTeardown = true; } } if (DBG) Slog.d(TAG, "Doing network teardown"); if (callTeardown) { tracker.teardown(); return 1; } else { // do it the old fashioned way return tracker.stopUsingNetworkFeature(feature, pid, uid); } } /** * @deprecated use requestRouteToHostAddress instead * * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. * @param networkType the type of the network over which traffic to the * specified host is to be routed * @param hostAddress the IP address of the host to which the route is * desired * @return {@code true} on success, {@code false} on failure */ public boolean requestRouteToHost(int networkType, int hostAddress) { InetAddress inetAddress = NetworkUtils.intToInetAddress(hostAddress); if (inetAddress == null) { return false; } return requestRouteToHostAddress(networkType, inetAddress.getHostAddress()); } /** * Ensure that a network route exists to deliver traffic to the specified * host via the specified network interface. * @param networkType the type of the network over which traffic to the * specified host is to be routed * @param hostAddress the IP address of the host to which the route is * desired * @return {@code true} on success, {@code false} on failure */ public boolean requestRouteToHostAddress(int networkType, String hostAddress) { enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType)) { return false; } NetworkStateTracker tracker = mNetTrackers[networkType]; if (tracker == null || !tracker.getNetworkInfo().isConnected() || tracker.isTeardownRequested()) { if (DBG) { Slog.d(TAG, "requestRouteToHostAddress on down network " + "(" + networkType + ") - dropped"); } return false; } try { InetAddress inetAddress = InetAddress.getByName(hostAddress); return tracker.requestRouteToHost(inetAddress); } catch (UnknownHostException e) { return false; } } /** * @see ConnectivityManager#getBackgroundDataSetting() */ public boolean getBackgroundDataSetting() { return Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.BACKGROUND_DATA, 1) == 1; } /** * @see ConnectivityManager#setBackgroundDataSetting(boolean) */ public void setBackgroundDataSetting(boolean allowBackgroundDataUsage) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING, "ConnectivityService"); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_BACKGROUND_DATA, (allowBackgroundDataUsage ? ENABLED : DISABLED), 0)); } private void handleSetBackgroundData(boolean enabled) { if (enabled != getBackgroundDataSetting()) { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BACKGROUND_DATA, enabled ? 1 : 0); Intent broadcast = new Intent( ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); mContext.sendBroadcast(broadcast); } } /** * @see ConnectivityManager#getMobileDataEnabled() */ public boolean getMobileDataEnabled() { enforceAccessPermission(); boolean retVal = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.MOBILE_DATA, 1) == 1; if (DBG) Slog.d(TAG, "getMobileDataEnabled returning " + retVal); return retVal; } /** * @see ConnectivityManager#setMobileDataEnabled(boolean) */ public void setMobileDataEnabled(boolean enabled) { enforceChangePermission(); if (DBG) Slog.d(TAG, "setMobileDataEnabled(" + enabled + ")"); mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_MOBILE_DATA, (enabled ? ENABLED : DISABLED), 0)); } private void handleSetMobileData(boolean enabled) { if (getMobileDataEnabled() == enabled) return; Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.MOBILE_DATA, enabled ? 1 : 0); if (enabled) { if (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) { if (DBG) { Slog.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_MOBILE]); } mNetTrackers[ConnectivityManager.TYPE_MOBILE].reconnect(); } if (mNetTrackers[ConnectivityManager.TYPE_WIMAX] != null) { if (DBG) { Slog.d(TAG, "starting up " + mNetTrackers[ConnectivityManager.TYPE_WIMAX]); } mNetTrackers[ConnectivityManager.TYPE_WIMAX].reconnect(); } } else { for (NetworkStateTracker nt : mNetTrackers) { if (nt == null) continue; int netType = nt.getNetworkInfo().getType(); if (mNetAttributes[netType].mRadio == ConnectivityManager.TYPE_MOBILE) { if (DBG) Slog.d(TAG, "tearing down " + nt); nt.teardown(); } } if (mNetTrackers[ConnectivityManager.TYPE_WIMAX] != null) { mNetTrackers[ConnectivityManager.TYPE_WIMAX].teardown(); } } } private int getNumConnectedNetworks() { int numConnectedNets = 0; for (NetworkStateTracker nt : mNetTrackers) { if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { ++numConnectedNets; } } return numConnectedNets; } private void enforceAccessPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "ConnectivityService"); } private void enforceChangePermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "ConnectivityService"); } // TODO Make this a special check when it goes public private void enforceTetherChangePermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_NETWORK_STATE, "ConnectivityService"); } private void enforceTetherAccessPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, "ConnectivityService"); } /** * Handle a {@code DISCONNECTED} event. If this pertains to the non-active * network, we ignore it. If it is for the active network, we send out a * broadcast. But first, we check whether it might be possible to connect * to a different network. * @param info the {@code NetworkInfo} for the network */ private void handleDisconnect(NetworkInfo info) { int prevNetType = info.getType(); if (DBG) Slog.d(TAG,"Got Network Disconnected from Driver nwtype="+prevNetType); mNetTrackers[prevNetType].setTeardownRequested(false); /* * If the disconnected network is not the active one, then don't report * this as a loss of connectivity. What probably happened is that we're * getting the disconnect for a network that we explicitly disabled * in accordance with network preference policies. */ if (!mNetAttributes[prevNetType].isDefault()) { List pids = mNetRequestersPids[prevNetType]; for (int i = 0; i.) based * on the highest priority active net which this process requested. * If there aren't any, clear it out */ private void reassessPidDns(int myPid, boolean doBump) { if (DBG) Slog.d(TAG, "reassessPidDns for pid " + myPid); for(int i : mPriorityList) { if (mNetAttributes[i].isDefault()) { continue; } NetworkStateTracker nt = mNetTrackers[i]; if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { List pids = mNetRequestersPids[i]; for (int j=0; j= 0; x--) { int networkType = mPriorityList[x]; NetworkStateTracker netTracker = mNetTrackers[networkType]; if (netTracker != null && netTracker.getNetworkInfo().isConnected() && !netTracker.isTeardownRequested()) { String[] dnsLst = netTracker.getNameServers(); if (mNetAttributes[networkType].isDefault()) { if (DBG) Slog.d(TAG, "handleDnsConfigurationChange isDefault=" + networkType); for (String dns : dnsLst) { if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { if (DBG) Slog.d(TAG, "adding dns " + dns + " for " + netTracker.getNetworkInfo().getTypeName()); SystemProperties.set("net.dns" + k++, dns); } } mNumDnsEntries = k; } } } bumpDns(); return; } // add default net's dns entries NetworkStateTracker nt = mNetTrackers[netType]; if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { String[] dnsList = nt.getNameServers(); if (mNetAttributes[netType].isDefault()) { int j = 1; for (String dns : dnsList) { if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { if (DBG) { Slog.d(TAG, "adding dns " + dns + " for " + nt.getNetworkInfo().getTypeName()); } SystemProperties.set("net.dns" + j++, dns); } } for (int k=j ; k 50 ? "connected" : "disconnected") + " (" + percentage + ") on " + "network Type " + networkType + " at " + GregorianCalendar.getInstance().getTime(); mInetLog.add(s); while(mInetLog.size() > INET_CONDITION_LOG_MAX_SIZE) { mInetLog.remove(0); } } mHandler.sendMessage(mHandler.obtainMessage( EVENT_INET_CONDITION_CHANGE, networkType, percentage)); } private void handleInetConditionChange(int netType, int condition) { if (DBG) { Slog.d(TAG, "Inet connectivity change, net=" + netType + ", condition=" + condition + ",mActiveDefaultNetwork=" + mActiveDefaultNetwork); } if (mActiveDefaultNetwork == -1) { if (DBG) Slog.d(TAG, "no active default network - aborting"); return; } if (mActiveDefaultNetwork != netType) { if (DBG) Slog.d(TAG, "given net not default - aborting"); return; } mDefaultInetCondition = condition; int delay; if (mInetConditionChangeInFlight == false) { if (DBG) Slog.d(TAG, "starting a change hold"); // setup a new hold to debounce this if (mDefaultInetCondition > 50) { delay = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.INET_CONDITION_DEBOUNCE_UP_DELAY, 500); } else { delay = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.INET_CONDITION_DEBOUNCE_DOWN_DELAY, 3000); } mInetConditionChangeInFlight = true; mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_INET_CONDITION_HOLD_END, mActiveDefaultNetwork, mDefaultConnectionSequence), delay); } else { // we've set the new condition, when this hold ends that will get // picked up if (DBG) Slog.d(TAG, "currently in hold - not setting new end evt"); } } private void handleInetConditionHoldEnd(int netType, int sequence) { if (DBG) { Slog.d(TAG, "Inet hold end, net=" + netType + ", condition =" + mDefaultInetCondition + ", published condition =" + mDefaultInetConditionPublished); } mInetConditionChangeInFlight = false; if (mActiveDefaultNetwork == -1) { if (DBG) Slog.d(TAG, "no active default network - aborting"); return; } if (mDefaultConnectionSequence != sequence) { if (DBG) Slog.d(TAG, "event hold for obsolete network - aborting"); return; } if (mDefaultInetConditionPublished == mDefaultInetCondition) { if (DBG) Slog.d(TAG, "no change in condition - aborting"); return; } NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); if (networkInfo.isConnected() == false) { if (DBG) Slog.d(TAG, "default network not connected - aborting"); return; } mDefaultInetConditionPublished = mDefaultInetCondition; sendInetConditionBroadcast(networkInfo); return; } /* CNE related methods. */ public void startCne() { if (!mCneStarted) { if (isCneAware()) { Slog.v(TAG, "CNE is starting up"); if (SystemProperties.get(ILinkManager.UseCne, "none") .equalsIgnoreCase("reference")) { mLinkManager = new LinkManager(mContext, this); } else if (SystemProperties.get(ILinkManager.UseCne, "none") .equalsIgnoreCase("vendor")) { // TODO: remove this before release mLinkManager = new CNE(mContext, this); } mLinkManager.sendDefaultNwPref2Cne(mNetworkPreference); mCneStarted = true; } else { Slog.v(TAG, "CNE is disabled."); } } else { Slog.e(TAG, "CNE already Started"); } } /** @hide * Has CNE been started on this device? * @return true of CNE has been started, otherwise false */ public boolean isCneStarted() { return mCneStarted; } /** @hide * Check if this android device is CNE aware. * @return true if CNE is enabled on this device, otherwise false */ public boolean isCneAware() { boolean isUsingVendorCne = SystemProperties.get(ILinkManager.UseCne, "none").equalsIgnoreCase("vendor"); boolean isUsingReferenceCne = SystemProperties.get(ILinkManager.UseCne, "none").equalsIgnoreCase("reference"); return (isUsingVendorCne || isUsingReferenceCne); } /** {@hide} */ public boolean bringUpRat(int ratType){ Slog.d(TAG, "Bring Up Rat called for rat=" + ratType); int networkType = 0; if (ratType == CNE.CNE_RAT_WLAN){ networkType = ConnectivityManager.TYPE_WIFI; } else if (ratType == CNE.CNE_RAT_WWAN) { if (!getMobileDataEnabled()) { if (DBG) Slog.d(TAG, "mobile data service disabled"); return false; } networkType = ConnectivityManager.TYPE_MOBILE; } else { Slog.d(TAG, "Unknown RatType = " + ratType); return false; } return reconnect(networkType); } /** * Used by LinkManager to control network connections * @param networkType * @return success */ public boolean reconnect(int networkType) { NetworkStateTracker network = mNetTrackers[networkType]; try{ network.setTeardownRequested(true); Slog.d(TAG, "Sending Network Connection Request to Driver."); network.reconnect(); return true; } catch(NullPointerException e){ Slog.d(TAG, "network Obj is Null" + e); e.printStackTrace(); } return false; } /** * Used by LinkManager to control network connections * @param networkType * @return success */ public boolean teardown(int networkType) { // TODO check input bounds if (networkType == ConnectivityManager.TYPE_WIFI) { mNetTrackers[networkType].resetTornDownbyConnMgr(); } return teardown(mNetTrackers[networkType]); } /** {@hide} */ public boolean bringDownRat(int ratType) { int networkType = 0; if (ratType == CNE.CNE_RAT_WLAN) { networkType = ConnectivityManager.TYPE_WIFI; WifiStateTracker network = (WifiStateTracker) mNetTrackers[networkType]; if (!network.hasWifiLocks()) { network.resetTornDownbyConnMgr(); return teardown(network); } else { Slog.d(TAG, "WifiLocks active not issuing bring down"); } } else if (ratType == CNE.CNE_RAT_WWAN) { networkType = ConnectivityManager.TYPE_MOBILE; NetworkStateTracker network = mNetTrackers[networkType]; return teardown(network); } else { Slog.d(TAG, "Unknown RatType = " + ratType); } return false; } /** {@hide} */ public boolean getLink_LP(int role, Map linkReqs, int mPid, IBinder listener) { if (mLinkManager != null) { return mLinkManager.getLink_LP(role, linkReqs, mPid, listener); } else { Slog.d(TAG, "mCneService is null"); return false; } } /** {@hide} */ public boolean reportLinkSatisfaction_LP(int role, int mPid, LinkInfo info, boolean isSatisfied, boolean isNotifyBetterCon) { if (mLinkManager != null) { return mLinkManager.reportLinkSatisfaction_LP(role, mPid, info, isSatisfied, isNotifyBetterCon); } else { Slog.d(TAG, "mCneService is null"); return false; } } /** {@hide} */ public boolean switchLink_LP(int role, int mPid, LinkInfo info, boolean isNotifyBetterLink) { if (mLinkManager != null) { return mLinkManager.switchLink_LP(role, mPid, info, isNotifyBetterLink); } else { Slog.d(TAG, "mCneService is null"); return false; } } /** {@hide} */ public boolean rejectSwitch_LP(int role, int mPid, LinkInfo info, boolean isNotifyBetterLink) { if (mLinkManager != null) { return mLinkManager.rejectSwitch_LP(role, mPid, info, isNotifyBetterLink); } else { Slog.d(TAG, "mCneService is null"); return false; } } /** {@hide} */ public boolean releaseLink_LP(int role, int mPid) { if (mLinkManager != null) { return mLinkManager.releaseLink_LP(role, mPid); } else { Slog.d(TAG, "mCneService is null"); return false; } } /** {@hide} * This function will be used by apps to request to start FMC * @param listener callback to notifier * @return {@code true} if the request has been accepted, * {@code false} otherwise. A return value of true does NOT mean that a * FMC is available for the app to use. That will delivered via * the FmcNotifier. */ public boolean startFmc(IBinder listener){ if(mLinkManager != null) { return mLinkManager.startFmc(listener); } else { Slog.d(TAG, "mCneService is null while calling startFmc"); return false; } } /** {@hide} * This function will be used by apps to stop FMC. * @return {@code true} if the request has been accepted by Cne * framework, {@code false} otherwise. */ public boolean stopFmc(IBinder listener){ if(mLinkManager != null) { return mLinkManager.stopFmc(listener); } else { Slog.d(TAG, "mCneService is null while calling startFmc"); return false; } } /** {@hide} * This function will be used by apps to request to start FMC * @param listener callback to notifier * @return {@code true} if there was a last sent status * {@code false} otherwise. */ public boolean getFmcStatus(IBinder listener){ if(mLinkManager != null) { return mLinkManager.getFmcStatus(listener); } else { Slog.d(TAG, "mCneService is null while calling startFmc"); return false; } } /** {@hide} * Used by LinkManager to set the default route. * Forwards the request to NetworkStateTrackers. */ public void setDefaultRoute(int network) { // add default route for this network Slog.d(TAG, "setDefaultRoute, network=" + network + ",len=" + mNetTrackers.length); mNetTrackers[network].addDefaultRoute(); } /** {@hide} * Used by LinkManager to set the default route per ipversion * Forwards the request to NetworkStateTrackers. */ public void setDefaultRoute(int network, IPVersion ipv) { // add default route for this network Slog.d(TAG, "setDefaultRoute, network=" + network + " ipv:" + ipv + ",len=" + mNetTrackers.length); mNetTrackers[network].addDefaultRoute(ipv); } /* * LinkSocket code is below here. */ /** * Starts the process of getting a new link for the LinkSocket in a different thread. * * @return A unique id that the socket will use for further communication. */ public int requestLink(LinkCapabilities capabilities, IBinder binder) { android.util.Log.v(TAG, "requestLink(capabilities, callback)"); return mLinkManager.requestLink(capabilities, binder); } /** * Dissociates a LinkSocket with a given link. */ public void releaseLink(int id) { android.util.Log.v(TAG, "releaseLink(id=" + id + ")"); mLinkManager.releaseLink(id); } /** * Triggers QoS transaction using the specified local port */ public boolean requestQoS(int id, int localPort, String localAddress) { android.util.Log.v(TAG, "requestQoS(aport)"); return mLinkManager.requestQoS(id, localPort, localAddress); } public LinkCapabilities requestCapabilities(int id, int[] capability_keys) { android.util.Log.v(TAG, "requestCapabilities(id=" + id + ", capabilities)"); int netType; ExtraLinkCapabilities cap = new ExtraLinkCapabilities(); for (int key : capability_keys) { String temp = null; switch (key) { case LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW: if ((temp = mLinkManager.getMinAvailableForwardBandwidth(id)) != null) cap.put(LinkCapabilities.Key.RO_MIN_AVAILABLE_FWD_BW,temp); break; case LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW: if ((temp = mLinkManager.getMaxAvailableForwardBandwidth(id)) != null) cap.put(LinkCapabilities.Key.RO_MAX_AVAILABLE_FWD_BW, temp); break; case LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW: if ((temp = mLinkManager.getMinAvailableReverseBandwidth(id)) != null) cap.put(LinkCapabilities.Key.RO_MIN_AVAILABLE_REV_BW, temp); break; case LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW: if ((temp = mLinkManager.getMaxAvailableReverseBandwidth(id)) != null) cap.put(LinkCapabilities.Key.RO_MAX_AVAILABLE_REV_BW, temp); break; case LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY: if ((temp = mLinkManager.getCurrentFwdLatency(id)) != null) cap.put(LinkCapabilities.Key.RO_CURRENT_FWD_LATENCY, temp); break; case LinkCapabilities.Key.RO_CURRENT_REV_LATENCY: if ((temp = mLinkManager.getCurrentRevLatency(id)) != null) cap.put(LinkCapabilities.Key.RO_CURRENT_REV_LATENCY, temp); break; case LinkCapabilities.Key.RO_NETWORK_TYPE: cap.put(LinkCapabilities.Key.RO_NETWORK_TYPE, Integer.toString(mLinkManager.getNetworkType(id))); break; case LinkCapabilities.Key.RO_BOUND_INTERFACE: netType = mLinkManager.getNetworkType(id); if (netType > 0) { cap.put(LinkCapabilities.Key.RO_BOUND_INTERFACE, mNetTrackers[netType].getInterfaceName(IPVersion.INET)); } else { cap.put(LinkCapabilities.Key.RO_BOUND_INTERFACE, "unknown"); } break; case LinkCapabilities.Key.RO_PHYSICAL_INTERFACE: netType = mLinkManager.getNetworkType(id); if (netType > 0) { cap.put(LinkCapabilities.Key.RO_PHYSICAL_INTERFACE, mNetTrackers[netType].getInterfaceName(IPVersion.INET)); } else { cap.put(LinkCapabilities.Key.RO_PHYSICAL_INTERFACE, "unknown"); } break; case LinkCapabilities.Key.RO_QOS_STATE: if ((temp = mLinkManager.getQosState(id)) != null) cap.put(LinkCapabilities.Key.RO_QOS_STATE, temp); break; } } return cap; } public void setTrackedCapabilities(int id, int[] capabilities) { android.util.Log.v(TAG, "setTrackedCapabilities(id=" + id + ", capabilities)"); /* * we need to discuss this method */ } }