/* * Copyright (C) 2008 The Android Open Source Project * * 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 android.app.Notification; import android.app.NotificationManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.MobileDataStateTracker; import android.net.NetworkInfo; import android.net.NetworkStateTracker; import android.net.wifi.WifiStateTracker; 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 com.android.internal.telephony.Phone; import com.android.server.connectivity.Tethering; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.List; /** * @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; private int mNumDnsEntries; private boolean mTestMode; private static ConnectivityService sServiceInstance; private Handler mHandler; // list of DeathRecipients used to make sure features are turned off when // a process dies private List mFeatureUsers; private boolean mSystemReady; private Intent mInitialBroadcast; 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; 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); } /** * Sets the preferred network. * @param preference the new preference */ public synchronized void setNetworkPreference(int preference) { enforceChangePermission(); if (ConnectivityManager.isNetworkTypeValid(preference) && mNetAttributes[preference] != null && mNetAttributes[preference].isDefault()) { if (mNetworkPreference != preference) { persistNetworkPreference(preference); mNetworkPreference = preference; enforcePreference(); } } } public int getNetworkPreference() { enforceAccessPermission(); return mNetworkPreference; } private void persistNetworkPreference(int networkPreference) { final ContentResolver cr = mContext.getContentResolver(); Settings.Secure.putInt(cr, Settings.Secure.NETWORK_PREFERENCE, networkPreference); } 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(); for (int type=0; type <= ConnectivityManager.MAX_NETWORK_TYPE; type++) { if (mNetAttributes[type] == null || !mNetAttributes[type].isDefault()) { continue; } NetworkStateTracker t = mNetTrackers[type]; NetworkInfo info = t.getNetworkInfo(); if (info.isConnected()) { if (DBG && type != mActiveDefaultNetwork) Slog.e(TAG, "connected default network is not " + "mActiveDefaultNetwork!"); return info; } } 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 { int mNetworkType; String mFeature; IBinder mBinder; int mPid; int mUid; 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( NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK, f), getRestoreDefaultNetworkDelay()); if ((ni.isConnectedOrConnecting() == true) && !network.isTeardownRequested()) { if (ni.isConnected() == true) { // add the pid-specific dns handleDnsConfigurationChange(); 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( NetworkStateTracker.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); } } /** * 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) { enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType)) { return false; } NetworkStateTracker tracker = mNetTrackers[networkType]; if (tracker == null || !tracker.getNetworkInfo().isConnected() || tracker.isTeardownRequested()) { if (DBG) { Slog.d(TAG, "requestRouteToHost on down network (" + networkType + ") - dropped"); } return false; } return tracker.requestRouteToHost(hostAddress); } /** * @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"); if (getBackgroundDataSetting() == allowBackgroundDataUsage) return; Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.BACKGROUND_DATA, allowBackgroundDataUsage ? 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 synchronized void setMobileDataEnabled(boolean enabled) { enforceChangePermission(); if (DBG) Slog.d(TAG, "setMobileDataEnabled(" + 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(); } } 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(); } } } } 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(); 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<pids.size(); i++) { Integer pid = (Integer)pids.get(i); // will remove them because the net's no longer connected // need to do this now as only now do we know the pids and // can properly null things that are no longer referenced. reassessPidDns(pid.intValue(), false); } } Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); info.setFailover(false); } if (info.getReason() != null) { intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); } if (info.getExtraInfo() != null) { intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); } NetworkStateTracker newNet = null; if (mNetAttributes[prevNetType].isDefault()) { newNet = tryFailover(prevNetType); if (newNet != null) { NetworkInfo switchTo = newNet.getNetworkInfo(); intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); } else { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } } // do this before we broadcast the change handleConnectivityChange(); sendStickyBroadcast(intent); /* * If the failover network is already connected, then immediately send * out a followup broadcast indicating successful failover */ if (newNet != null && newNet.getNetworkInfo().isConnected()) { sendConnectedBroadcast(newNet.getNetworkInfo()); } } // returns null if no failover available private NetworkStateTracker tryFailover(int prevNetType) { /* * If this is a default network, check if other defaults are available * or active */ NetworkStateTracker newNet = null; if (mNetAttributes[prevNetType].isDefault()) { if (mActiveDefaultNetwork == prevNetType) { mActiveDefaultNetwork = -1; } int newType = -1; int newPriority = -1; boolean noMobileData = !getMobileDataEnabled(); for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) { if (checkType == prevNetType) continue; if (mNetAttributes[checkType] == null) continue; if (mNetAttributes[checkType].mRadio == ConnectivityManager.TYPE_MOBILE && noMobileData) { if (DBG) { Slog.d(TAG, "not failing over to mobile type " + checkType + " because Mobile Data Disabled"); } continue; } if (mNetAttributes[checkType].isDefault()) { /* TODO - if we have multiple nets we could use * we may want to put more thought into which we choose */ if (checkType == mNetworkPreference) { newType = checkType; break; } if (mNetAttributes[checkType].mPriority > newPriority) { newType = checkType; newPriority = mNetAttributes[newType].mPriority; } } } if (newType != -1) { newNet = mNetTrackers[newType]; /** * See if the other network is available to fail over to. * If is not available, we enable it anyway, so that it * will be able to connect when it does become available, * but we report a total loss of connectivity rather than * report that we are attempting to fail over. */ if (newNet.isAvailable()) { NetworkInfo switchTo = newNet.getNetworkInfo(); switchTo.setFailover(true); if (!switchTo.isConnectedOrConnecting() || newNet.isTeardownRequested()) { newNet.reconnect(); } if (DBG) { if (switchTo.isConnected()) { Slog.v(TAG, "Switching to already connected " + switchTo.getTypeName()); } else { Slog.v(TAG, "Attempting to switch to " + switchTo.getTypeName()); } } } else { newNet.reconnect(); newNet = null; // not officially avail.. try anyway, but // report no failover } } } return newNet; } private void sendConnectedBroadcast(NetworkInfo info) { Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); info.setFailover(false); } if (info.getReason() != null) { intent.putExtra(ConnectivityManager.EXTRA_REASON, info.getReason()); } if (info.getExtraInfo() != null) { intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, info.getExtraInfo()); } sendStickyBroadcast(intent); } /** * Called when an attempt to fail over to another network has failed. * @param info the {@link NetworkInfo} for the failed network */ private void handleConnectionFailure(NetworkInfo info) { mNetTrackers[info.getType()].setTeardownRequested(false); String reason = info.getReason(); String extraInfo = info.getExtraInfo(); if (DBG) { String reasonText; if (reason == null) { reasonText = "."; } else { reasonText = " (" + reason + ")."; } Slog.v(TAG, "Attempt to connect to " + info.getTypeName() + " failed" + reasonText); } Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, info); if (getActiveNetworkInfo() == null) { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } if (reason != null) { intent.putExtra(ConnectivityManager.EXTRA_REASON, reason); } if (extraInfo != null) { intent.putExtra(ConnectivityManager.EXTRA_EXTRA_INFO, extraInfo); } if (info.isFailover()) { intent.putExtra(ConnectivityManager.EXTRA_IS_FAILOVER, true); info.setFailover(false); } NetworkStateTracker newNet = null; if (mNetAttributes[info.getType()].isDefault()) { newNet = tryFailover(info.getType()); if (newNet != null) { NetworkInfo switchTo = newNet.getNetworkInfo(); intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); } else { intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } } // do this before we broadcast the change handleConnectivityChange(); sendStickyBroadcast(intent); /* * If the failover network is already connected, then immediately send * out a followup broadcast indicating successful failover */ if (newNet != null && newNet.getNetworkInfo().isConnected()) { sendConnectedBroadcast(newNet.getNetworkInfo()); } } private void sendStickyBroadcast(Intent intent) { synchronized(this) { if (!mSystemReady) { mInitialBroadcast = new Intent(intent); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); mContext.sendStickyBroadcast(intent); } } void systemReady() { synchronized(this) { mSystemReady = true; if (mInitialBroadcast != null) { mContext.sendStickyBroadcast(mInitialBroadcast); mInitialBroadcast = null; } } } private void handleConnect(NetworkInfo info) { int type = info.getType(); // snapshot isFailover, because sendConnectedBroadcast() resets it boolean isFailover = info.isFailover(); NetworkStateTracker thisNet = mNetTrackers[type]; // if this is a default net and other default is running // kill the one not preferred if (mNetAttributes[type].isDefault()) { if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != type) { if ((type != mNetworkPreference && mNetAttributes[mActiveDefaultNetwork].mPriority > mNetAttributes[type].mPriority) || mNetworkPreference == mActiveDefaultNetwork) { // don't accept this one if (DBG) Slog.v(TAG, "Not broadcasting CONNECT_ACTION " + "to torn down network " + info.getTypeName()); teardown(thisNet); return; } else { // tear down the other NetworkStateTracker otherNet = mNetTrackers[mActiveDefaultNetwork]; if (DBG) Slog.v(TAG, "Policy requires " + otherNet.getNetworkInfo().getTypeName() + " teardown"); if (!teardown(otherNet)) { Slog.e(TAG, "Network declined teardown request"); return; } if (isFailover) { otherNet.releaseWakeLock(); } } } mActiveDefaultNetwork = type; } thisNet.setTeardownRequested(false); thisNet.updateNetworkSettings(); handleConnectivityChange(); sendConnectedBroadcast(info); } private void handleScanResultsAvailable(NetworkInfo info) { int networkType = info.getType(); if (networkType != ConnectivityManager.TYPE_WIFI) { if (DBG) Slog.v(TAG, "Got ScanResultsAvailable for " + info.getTypeName() + " network. Don't know how to handle."); } mNetTrackers[networkType].interpretScanResultsAvailable(); } private void handleNotificationChange(boolean visible, int id, Notification notification) { NotificationManager notificationManager = (NotificationManager) mContext .getSystemService(Context.NOTIFICATION_SERVICE); if (visible) { notificationManager.notify(id, notification); } else { notificationManager.cancel(id); } } /** * After any kind of change in the connectivity state of any network, * make sure that anything that depends on the connectivity state of * more than one network is set up correctly. We're mainly concerned * with making sure that the list of DNS servers is set up according * to which networks are connected, and ensuring that the right routing * table entries exist. */ private void handleConnectivityChange() { /* * If a non-default network is enabled, add the host routes that * will allow it's DNS servers to be accessed. Only * If both mobile and wifi are enabled, add the host routes that * will allow MMS traffic to pass on the mobile network. But * remove the default route for the mobile network, so that there * will be only one default route, to ensure that all traffic * except MMS will travel via Wi-Fi. */ handleDnsConfigurationChange(); for (int netType : mPriorityList) { if (mNetTrackers[netType].getNetworkInfo().isConnected()) { if (mNetAttributes[netType].isDefault()) { mNetTrackers[netType].addDefaultRoute(); } else { mNetTrackers[netType].addPrivateDnsRoutes(); } } else { if (mNetAttributes[netType].isDefault()) { mNetTrackers[netType].removeDefaultRoute(); } else { mNetTrackers[netType].removePrivateDnsRoutes(); } } } } /** * Adjust the per-process dns entries (net.dns<x>.<pid>) 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<pids.size(); j++) { Integer pid = (Integer)pids.get(j); if (pid.intValue() == myPid) { String[] dnsList = nt.getNameServers(); writePidDns(dnsList, myPid); if (doBump) { bumpDns(); } return; } } } } // nothing found - delete for (int i = 1; ; i++) { String prop = "net.dns" + i + "." + myPid; if (SystemProperties.get(prop).length() == 0) { if (doBump) { bumpDns(); } return; } SystemProperties.set(prop, ""); } } private void writePidDns(String[] dnsList, int pid) { int j = 1; for (String dns : dnsList) { if (dns != null && !TextUtils.equals(dns, "0.0.0.0")) { SystemProperties.set("net.dns" + j++ + "." + pid, dns); } } } private void bumpDns() { /* * Bump the property that tells the name resolver library to reread * the DNS server list from the properties. */ String propVal = SystemProperties.get("net.dnschange"); int n = 0; if (propVal.length() != 0) { try { n = Integer.parseInt(propVal); } catch (NumberFormatException e) {} } SystemProperties.set("net.dnschange", "" + (n+1)); } private void handleDnsConfigurationChange() { // add default net's dns entries for (int x = mPriorityList.length-1; x>= 0; x--) { int netType = mPriorityList[x]; 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<mNumDnsEntries; k++) { if (DBG) Slog.d(TAG, "erasing net.dns" + k); SystemProperties.set("net.dns" + k, ""); } mNumDnsEntries = j; } else { // set per-pid dns for attached secondary nets List pids = mNetRequestersPids[netType]; for (int y=0; y< pids.size(); y++) { Integer pid = (Integer)pids.get(y); writePidDns(dnsList, pid.intValue()); } } } } bumpDns(); } private int getRestoreDefaultNetworkDelay() { String restoreDefaultNetworkDelayStr = SystemProperties.get( NETWORK_RESTORE_DELAY_PROP_NAME); if(restoreDefaultNetworkDelayStr != null && restoreDefaultNetworkDelayStr.length() != 0) { try { return Integer.valueOf(restoreDefaultNetworkDelayStr); } catch (NumberFormatException e) { } } return RESTORE_DEFAULT_NETWORK_DELAY; } @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) { pw.println("Permission Denial: can't dump ConnectivityService " + "from from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()); return; } pw.println(); for (NetworkStateTracker nst : mNetTrackers) { if (nst != null) { if (nst.getNetworkInfo().isConnected()) { pw.println("Active network: " + nst.getNetworkInfo(). getTypeName()); } pw.println(nst.getNetworkInfo()); pw.println(nst); pw.println(); } } pw.println("Network Requester Pids:"); for (int net : mPriorityList) { String pidString = net + ": "; for (Object pid : mNetRequestersPids[net]) { pidString = pidString + pid.toString() + ", "; } pw.println(pidString); } pw.println(); pw.println("FeatureUsers:"); for (Object requester : mFeatureUsers) { pw.println(requester.toString()); } pw.println(); mTethering.dump(fd, pw, args); } // must be stateless - things change under us. private class MyHandler extends Handler { @Override public void handleMessage(Message msg) { NetworkInfo info; switch (msg.what) { case NetworkStateTracker.EVENT_STATE_CHANGED: info = (NetworkInfo) msg.obj; int type = info.getType(); NetworkInfo.State state = info.getState(); // only do this optimization for wifi. It going into scan mode for location // services generates alot of noise. Meanwhile the mms apn won't send out // subsequent notifications when on default cellular because it never // disconnects.. so only do this to wifi notifications. Fixed better when the // APN notifications are standardized. if (mNetAttributes[type].mLastState == state && mNetAttributes[type].mRadio == ConnectivityManager.TYPE_WIFI) { if (DBG) { // TODO - remove this after we validate the dropping doesn't break // anything Slog.d(TAG, "Dropping ConnectivityChange for " + info.getTypeName() + ": " + state + "/" + info.getDetailedState()); } return; } mNetAttributes[type].mLastState = state; if (DBG) Slog.d(TAG, "ConnectivityChange for " + info.getTypeName() + ": " + state + "/" + info.getDetailedState()); // Connectivity state changed: // [31-13] Reserved for future use // [12-9] Network subtype (for mobile network, as defined // by TelephonyManager) // [8-3] Detailed state ordinal (as defined by // NetworkInfo.DetailedState) // [2-0] Network type (as defined by ConnectivityManager) int eventLogParam = (info.getType() & 0x7) | ((info.getDetailedState().ordinal() & 0x3f) << 3) | (info.getSubtype() << 9); EventLog.writeEvent(EventLogTags.CONNECTIVITY_STATE_CHANGED, eventLogParam); if (info.getDetailedState() == NetworkInfo.DetailedState.FAILED) { handleConnectionFailure(info); } else if (state == NetworkInfo.State.DISCONNECTED) { handleDisconnect(info); } else if (state == NetworkInfo.State.SUSPENDED) { // TODO: need to think this over. // the logic here is, handle SUSPENDED the same as // DISCONNECTED. The only difference being we are // broadcasting an intent with NetworkInfo that's // suspended. This allows the applications an // opportunity to handle DISCONNECTED and SUSPENDED // differently, or not. handleDisconnect(info); } else if (state == NetworkInfo.State.CONNECTED) { handleConnect(info); } break; case NetworkStateTracker.EVENT_SCAN_RESULTS_AVAILABLE: info = (NetworkInfo) msg.obj; handleScanResultsAvailable(info); break; case NetworkStateTracker.EVENT_NOTIFICATION_CHANGED: handleNotificationChange(msg.arg1 == 1, msg.arg2, (Notification) msg.obj); case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: handleDnsConfigurationChange(); break; case NetworkStateTracker.EVENT_ROAMING_CHANGED: // fill me in break; case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: // fill me in break; case NetworkStateTracker.EVENT_RESTORE_DEFAULT_NETWORK: FeatureUser u = (FeatureUser)msg.obj; u.expire(); break; } } } // javadoc from interface public int tether(String iface) { enforceTetherChangePermission(); if (isTetheringSupported()) { return mTethering.tether(iface); } else { return ConnectivityManager.TETHER_ERROR_UNSUPPORTED; } } // javadoc from interface public int untether(String iface) { enforceTetherChangePermission(); if (isTetheringSupported()) { return mTethering.untether(iface); } else { return ConnectivityManager.TETHER_ERROR_UNSUPPORTED; } } // javadoc from interface public int getLastTetherError(String iface) { enforceTetherAccessPermission(); if (isTetheringSupported()) { return mTethering.getLastTetherError(iface); } else { return ConnectivityManager.TETHER_ERROR_UNSUPPORTED; } } // TODO - proper iface API for selection by property, inspection, etc public String[] getTetherableUsbRegexs() { enforceTetherAccessPermission(); if (isTetheringSupported()) { return mTethering.getTetherableUsbRegexs(); } else { return new String[0]; } } public String[] getTetherableWifiRegexs() { enforceTetherAccessPermission(); if (isTetheringSupported()) { return mTethering.getTetherableWifiRegexs(); } else { return new String[0]; } } // TODO - move iface listing, queries, etc to new module // javadoc from interface public String[] getTetherableIfaces() { enforceTetherAccessPermission(); return mTethering.getTetherableIfaces(); } public String[] getTetheredIfaces() { enforceTetherAccessPermission(); return mTethering.getTetheredIfaces(); } public String[] getTetheringErroredIfaces() { enforceTetherAccessPermission(); return mTethering.getErroredIfaces(); } // if ro.tether.denied = true we default to no tethering // gservices could set the secure setting to 1 though to enable it on a build where it // had previously been turned off. public boolean isTetheringSupported() { enforceTetherAccessPermission(); int defaultVal = (SystemProperties.get("ro.tether.denied").equals("true") ? 0 : 1); boolean tetherEnabledInSettings = (Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.TETHER_SUPPORTED, defaultVal) != 0); return tetherEnabledInSettings && mTetheringConfigValid; } }