/* * 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.database.ContentObserver; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.MobileDataStateTracker; import android.net.NetworkInfo; import android.net.LinkProperties; import android.net.NetworkStateTracker; import android.net.NetworkUtils; import android.net.Proxy; import android.net.ProxyProperties; import android.net.wifi.WifiStateTracker; import android.os.Binder; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; 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.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.GregorianCalendar; 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[]; private WifiWatchdogService mWifiWatchdogService; // 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; /** * used internally to clear a wakelock when transitioning * from one net to another */ private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = MAX_NETWORK_STATE_TRACKER_EVENT + 8; /** * used internally to reload global proxy settings */ private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = MAX_NETWORK_STATE_TRACKER_EVENT + 9; 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 PowerManager.WakeLock mNetTransitionWakeLock; private String mNetTransitionWakeLockCausedBy = ""; private int mNetTransitionWakeLockSerialNumber; private int mNetTransitionWakeLockTimeout; private InetAddress mDefaultDns; // used in DBG mode to track inet condition reports private static final int INET_CONDITION_LOG_MAX_SIZE = 15; private ArrayList mInetLog; // track the current default http proxy - tell the world if we get a new one (real change) private ProxyProperties mDefaultProxy = null; // track the global proxy. private ProxyProperties mGlobalProxy = null; private final Object mGlobalProxyLock = new Object(); private SettingsObserver mSettingsObserver; 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; public static synchronized ConnectivityService getInstance(Context context) { if (sServiceInstance == null) { sServiceInstance = new ConnectivityService(context); } return sServiceInstance; } private ConnectivityService(Context context) { if (DBG) log("ConnectivityService starting up"); HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread"); handlerThread.start(); mHandler = new MyHandler(handlerThread.getLooper()); // 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); } // read our default dns server ip String dns = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.DEFAULT_DNS_SERVER); if (dns == null || dns.length() == 0) { dns = context.getResources().getString( com.android.internal.R.string.config_default_dns_server); } try { mDefaultDns = InetAddress.getByName(dns); } catch (UnknownHostException e) { loge("Error setting defaultDns using " + dns); } mContext = context; PowerManager powerManager = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); mNetTransitionWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); mNetTransitionWakeLockTimeout = mContext.getResources().getInteger( com.android.internal.R.integer.config_networkTransitionTimeout); mNetTrackers = new NetworkStateTracker[ ConnectivityManager.MAX_NETWORK_TYPE+1]; 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) { loge("Error in radioAttributes - ignoring attempt to define type " + r.mType); continue; } if (mRadioAttributes[r.mType] != null) { loge("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) { loge("Error in networkAttributes - ignoring attempt to define type " + n.mType); continue; } if (mNetAttributes[n.mType] != null) { loge("Error in networkAttributes - ignoring attempt to redefine type " + n.mType); continue; } if (mRadioAttributes[n.mRadio] == null) { loge("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) log("Starting Wifi Service."); WifiStateTracker wst = new WifiStateTracker(); WifiService wifiService = new WifiService(context); ServiceManager.addService(Context.WIFI_SERVICE, wifiService); wifiService.checkAndStartWifi(); mNetTrackers[ConnectivityManager.TYPE_WIFI] = wst; wst.startMonitoring(context, mHandler); //TODO: as part of WWS refactor, create only when needed mWifiWatchdogService = new WifiWatchdogService(context); break; case ConnectivityManager.TYPE_MOBILE: mNetTrackers[netType] = new MobileDataStateTracker(netType, mNetAttributes[netType].mName); mNetTrackers[netType].startMonitoring(context, mHandler); if (noMobileData) { if (DBG) log("tearing down Mobile networks due to setting"); mNetTrackers[netType].teardown(); } break; default: loge("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.getTetherableBluetoothRegexs().length != 0) && mTethering.getUpstreamIfaceRegexs().length != 0); if (DBG) { mInetLog = new ArrayList(); } mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY); mSettingsObserver.observe(mContext); loadGlobalProxy(); } /** * 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; } 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) { log("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) { loge("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; } /** * Return LinkProperties for the active (i.e., connected) default * network interface. It is assumed that at most one default network * is active at a time. If more than one is active, it is indeterminate * which will be returned. * @return the ip properties for the active network, or {@code null} if * none is active */ public LinkProperties getActiveLinkProperties() { 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()) { return t.getLinkProperties(); } } return null; } public LinkProperties getLinkProperties(int networkType) { enforceAccessPermission(); if (ConnectivityManager.isNetworkTypeValid(networkType)) { NetworkStateTracker t = mNetTrackers[networkType]; if (t != null) return t.getLinkProperties(); } return null; } 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() { log("ConnectivityService FeatureUser binderDied(" + mNetworkType + ", " + mFeature + ", " + mBinder + "), created " + (System.currentTimeMillis() - mCreateTime) + " mSec ago"); stopUsingNetworkFeature(this, false); } public void expire() { log("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) { log("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) log("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) log("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) log("special network already active"); return Phone.APN_ALREADY_ACTIVE; } if (DBG) log("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) log("reconnecting to special network"); network.reconnect(); return Phone.APN_REQUEST_STARTED; } else { return -1; } } 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) log("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) { log("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) log("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) log("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) log("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) log("not tearing down special network - " + "others still using it"); return 1; } callTeardown = true; } } if (DBG) log("Doing network teardown"); if (callTeardown) { tracker.teardown(); return 1; } else { return -1; } } /** * @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.getAddress()); } /** * 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, byte[] hostAddress) { enforceChangePermission(); if (!ConnectivityManager.isNetworkTypeValid(networkType)) { return false; } NetworkStateTracker tracker = mNetTrackers[networkType]; if (tracker == null || !tracker.getNetworkInfo().isConnected() || tracker.isTeardownRequested()) { if (DBG) { log("requestRouteToHostAddress on down network " + "(" + networkType + ") - dropped"); } return false; } try { InetAddress addr = InetAddress.getByAddress(hostAddress); return addHostRoute(tracker, addr); } catch (UnknownHostException e) {} return false; } /** * Ensure that a network route exists to deliver traffic to the specified * host via the mobile data network. * @param hostAddress the IP address of the host to which the route is desired, * in network byte order. * TODO - deprecate * @return {@code true} on success, {@code false} on failure */ private boolean addHostRoute(NetworkStateTracker nt, InetAddress hostAddress) { if (nt.getNetworkInfo().getType() == ConnectivityManager.TYPE_WIFI) { return false; } LinkProperties p = nt.getLinkProperties(); if (p == null) return false; String interfaceName = p.getInterfaceName(); if (DBG) { log("Requested host route to " + hostAddress + "(" + interfaceName + ")"); } if (interfaceName != null) { return NetworkUtils.addHostRoute(interfaceName, hostAddress, null); } else { if (DBG) loge("addHostRoute failed due to null interface name"); 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) log("getMobileDataEnabled returning " + retVal); return retVal; } /** * @see ConnectivityManager#setMobileDataEnabled(boolean) */ public void setMobileDataEnabled(boolean enabled) { enforceChangePermission(); if (DBG) log("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) { log("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) log("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"); } private void enforceConnectivityInternalPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CONNECTIVITY_INTERNAL, "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 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()) { log("Switching to already connected " + switchTo.getTypeName()); } else { log("Attempting to switch to " + switchTo.getTypeName()); } } } else { newNet.reconnect(); newNet = null; // not officially avail.. try anyway, but // report no failover } } else { loge("Network failover failing."); } } return newNet; } private void sendConnectedBroadcast(NetworkInfo info) { sendGeneralBroadcast(info, ConnectivityManager.CONNECTIVITY_ACTION); } private void sendInetConditionBroadcast(NetworkInfo info) { sendGeneralBroadcast(info, ConnectivityManager.INET_CONDITION_ACTION); } private void sendGeneralBroadcast(NetworkInfo info, String bcastType) { Intent intent = new Intent(bcastType); 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()); } intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished); 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(); String reasonText; if (reason == null) { reasonText = "."; } else { reasonText = " (" + reason + ")."; } loge("Attempt to connect to " + info.getTypeName() + " failed" + reasonText); Intent intent = new Intent(ConnectivityManager.CONNECTIVITY_ACTION); 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(); if (!switchTo.isConnected()) { // if the other net is connected they've already reset this and perhaps // even gotten a positive report we don't want to overwrite, but if not // we need to clear this now to turn our cellular sig strength white mDefaultInetConditionPublished = 0; } intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); } else { mDefaultInetConditionPublished = 0; intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); } } intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished); 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; } } // load the global proxy at startup mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY)); } 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) { log("Not broadcasting CONNECT_ACTION " + "to torn down network " + info.getTypeName()); } teardown(thisNet); return; } else { // tear down the other NetworkStateTracker otherNet = mNetTrackers[mActiveDefaultNetwork]; if (DBG) { log("Policy requires " + otherNet.getNetworkInfo().getTypeName() + " teardown"); } if (!teardown(otherNet)) { loge("Network declined teardown request"); return; } } } synchronized (ConnectivityService.this) { // have a new default network, release the transition wakelock in a second // if it's held. The second pause is to allow apps to reconnect over the // new network if (mNetTransitionWakeLock.isHeld()) { mHandler.sendMessageDelayed(mHandler.obtainMessage( EVENT_CLEAR_NET_TRANSITION_WAKELOCK, mNetTransitionWakeLockSerialNumber, 0), 1000); } } mActiveDefaultNetwork = type; // this will cause us to come up initially as unconnected and switching // to connected after our normal pause unless somebody reports us as reall // disconnected mDefaultInetConditionPublished = 0; mDefaultConnectionSequence++; mInetConditionChangeInFlight = false; // Don't do this - if we never sign in stay, grey //reportNetworkCondition(mActiveDefaultNetwork, 100); } thisNet.setTeardownRequested(false); updateNetworkSettings(thisNet); handleConnectivityChange(type); sendConnectedBroadcast(info); } /** * After a change in the connectivity state of a network. 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(int netType) { /* * If a non-default network is enabled, add the host routes that * will allow it's DNS servers to be accessed. */ handleDnsConfigurationChange(netType); if (mNetTrackers[netType].getNetworkInfo().isConnected()) { if (mNetAttributes[netType].isDefault()) { handleApplyDefaultProxy(netType); addDefaultRoute(mNetTrackers[netType]); } else { addPrivateDnsRoutes(mNetTrackers[netType]); } } else { if (mNetAttributes[netType].isDefault()) { removeDefaultRoute(mNetTrackers[netType]); } else { removePrivateDnsRoutes(mNetTrackers[netType]); } } } private void addPrivateDnsRoutes(NetworkStateTracker nt) { boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet(); LinkProperties p = nt.getLinkProperties(); if (p == null) return; String interfaceName = p.getInterfaceName(); if (DBG) { log("addPrivateDnsRoutes for " + nt + "(" + interfaceName + ") - mPrivateDnsRouteSet = " + privateDnsRouteSet); } if (interfaceName != null && !privateDnsRouteSet) { Collection dnsList = p.getDnses(); for (InetAddress dns : dnsList) { if (DBG) log(" adding " + dns); NetworkUtils.addHostRoute(interfaceName, dns, null); } nt.privateDnsRouteSet(true); } } private void removePrivateDnsRoutes(NetworkStateTracker nt) { // TODO - we should do this explicitly but the NetUtils api doesnt // support this yet - must remove all. No worse than before LinkProperties p = nt.getLinkProperties(); if (p == null) return; String interfaceName = p.getInterfaceName(); boolean privateDnsRouteSet = nt.isPrivateDnsRouteSet(); if (interfaceName != null && privateDnsRouteSet) { if (DBG) { log("removePrivateDnsRoutes for " + nt.getNetworkInfo().getTypeName() + " (" + interfaceName + ")"); } NetworkUtils.removeHostRoutes(interfaceName); nt.privateDnsRouteSet(false); } } private void addDefaultRoute(NetworkStateTracker nt) { LinkProperties p = nt.getLinkProperties(); if (p == null) return; String interfaceName = p.getInterfaceName(); InetAddress defaultGatewayAddr = p.getGateway(); if ((interfaceName != null) && (defaultGatewayAddr != null )) { if (!NetworkUtils.addDefaultRoute(interfaceName, defaultGatewayAddr) && DBG) { NetworkInfo networkInfo = nt.getNetworkInfo(); log("addDefaultRoute for " + networkInfo.getTypeName() + " (" + interfaceName + "), GatewayAddr=" + defaultGatewayAddr); } } } public void removeDefaultRoute(NetworkStateTracker nt) { LinkProperties p = nt.getLinkProperties(); if (p == null) return; String interfaceName = p.getInterfaceName(); if (interfaceName != null) { if ((NetworkUtils.removeDefaultRoute(interfaceName) >= 0) && DBG) { NetworkInfo networkInfo = nt.getNetworkInfo(); log("removeDefaultRoute for " + networkInfo.getTypeName() + " (" + interfaceName + ")"); } } } /** * Reads the network specific TCP buffer sizes from SystemProperties * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system * wide use */ public void updateNetworkSettings(NetworkStateTracker nt) { String key = nt.getTcpBufferSizesPropName(); String bufferSizes = SystemProperties.get(key); if (bufferSizes.length() == 0) { loge(key + " not found in system properties. Using defaults"); // Setting to default values so we won't be stuck to previous values key = "net.tcp.buffersize.default"; bufferSizes = SystemProperties.get(key); } // Set values in kernel if (bufferSizes.length() != 0) { if (DBG) { log("Setting TCP values: [" + bufferSizes + "] which comes from [" + key + "]"); } setBufferSize(bufferSizes); } } /** * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max] * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem * * @param bufferSizes in the format of "readMin, readInitial, readMax, * writeMin, writeInitial, writeMax" */ private void setBufferSize(String bufferSizes) { try { String[] values = bufferSizes.split(","); if (values.length == 6) { final String prefix = "/sys/kernel/ipv4/tcp_"; stringToFile(prefix + "rmem_min", values[0]); stringToFile(prefix + "rmem_def", values[1]); stringToFile(prefix + "rmem_max", values[2]); stringToFile(prefix + "wmem_min", values[3]); stringToFile(prefix + "wmem_def", values[4]); stringToFile(prefix + "wmem_max", values[5]); } else { loge("Invalid buffersize string: " + bufferSizes); } } catch (IOException e) { loge("Can't set tcp buffer sizes:" + e); } } /** * Writes string to file. Basically same as "echo -n $string > $filename" * * @param filename * @param string * @throws IOException */ private void stringToFile(String filename, String string) throws IOException { FileWriter out = new FileWriter(filename); try { out.write(string); } finally { out.close(); } } /** * Adjust the per-process dns entries (net.dns.) 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) log("reassessPidDns for pid " + myPid); for(int i : mPriorityList) { if (mNetAttributes[i].isDefault()) { continue; } NetworkStateTracker nt = mNetTrackers[i]; if (nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { LinkProperties p = nt.getLinkProperties(); if (p == null) continue; List pids = mNetRequestersPids[i]; for (int j=0; j dnses = p.getDnses(); writePidDns(dnses, 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(Collection dnses, int pid) { int j = 1; for (InetAddress dns : dnses) { SystemProperties.set("net.dns" + j++ + "." + pid, dns.getHostAddress()); } } 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)); /* * Tell the VMs to toss their DNS caches */ Intent intent = new Intent(Intent.ACTION_CLEAR_DNS_CACHE); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); mContext.sendBroadcast(intent); } private void handleDnsConfigurationChange(int netType) { // add default net's dns entries NetworkStateTracker nt = mNetTrackers[netType]; if (nt != null && nt.getNetworkInfo().isConnected() && !nt.isTeardownRequested()) { LinkProperties p = nt.getLinkProperties(); if (p == null) return; Collection dnses = p.getDnses(); if (mNetAttributes[netType].isDefault()) { int j = 1; if (dnses.size() == 0 && mDefaultDns != null) { if (DBG) { log("no dns provided - using " + mDefaultDns.getHostAddress()); } SystemProperties.set("net.dns1", mDefaultDns.getHostAddress()); j++; } else { for (InetAddress dns : dnses) { if (DBG) { log("adding dns " + dns + " for " + nt.getNetworkInfo().getTypeName()); } SystemProperties.set("net.dns" + j++, dns.getHostAddress()); } } 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) { log("Inet connectivity change, net=" + netType + ", condition=" + condition + ",mActiveDefaultNetwork=" + mActiveDefaultNetwork); } if (mActiveDefaultNetwork == -1) { if (DBG) log("no active default network - aborting"); return; } if (mActiveDefaultNetwork != netType) { if (DBG) log("given net not default - aborting"); return; } mDefaultInetCondition = condition; int delay; if (mInetConditionChangeInFlight == false) { if (DBG) log("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) log("currently in hold - not setting new end evt"); } } private void handleInetConditionHoldEnd(int netType, int sequence) { if (DBG) { log("Inet hold end, net=" + netType + ", condition =" + mDefaultInetCondition + ", published condition =" + mDefaultInetConditionPublished); } mInetConditionChangeInFlight = false; if (mActiveDefaultNetwork == -1) { if (DBG) log("no active default network - aborting"); return; } if (mDefaultConnectionSequence != sequence) { if (DBG) log("event hold for obsolete network - aborting"); return; } if (mDefaultInetConditionPublished == mDefaultInetCondition) { if (DBG) log("no change in condition - aborting"); return; } NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); if (networkInfo.isConnected() == false) { if (DBG) log("default network not connected - aborting"); return; } mDefaultInetConditionPublished = mDefaultInetCondition; sendInetConditionBroadcast(networkInfo); return; } public synchronized ProxyProperties getProxy() { if (mGlobalProxy != null) return mGlobalProxy; if (mDefaultProxy != null) return mDefaultProxy; return null; } public void setGlobalProxy(ProxyProperties proxyProperties) { enforceChangePermission(); synchronized (mGlobalProxyLock) { if (proxyProperties == mGlobalProxy) return; if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return; if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return; String host = ""; int port = 0; String exclList = ""; if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) { mGlobalProxy = new ProxyProperties(proxyProperties); host = mGlobalProxy.getHost(); port = mGlobalProxy.getPort(); exclList = mGlobalProxy.getExclusionList(); } else { mGlobalProxy = null; } ContentResolver res = mContext.getContentResolver(); Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST, host); Settings.Secure.putInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, port); Settings.Secure.putString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclList); } if (mGlobalProxy == null) { proxyProperties = mDefaultProxy; } sendProxyBroadcast(proxyProperties); } private void loadGlobalProxy() { ContentResolver res = mContext.getContentResolver(); String host = Settings.Secure.getString(res, Settings.Secure.GLOBAL_HTTP_PROXY_HOST); int port = Settings.Secure.getInt(res, Settings.Secure.GLOBAL_HTTP_PROXY_PORT, 0); String exclList = Settings.Secure.getString(res, Settings.Secure.GLOBAL_HTTP_PROXY_EXCLUSION_LIST); if (!TextUtils.isEmpty(host)) { ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList); synchronized (mGlobalProxyLock) { mGlobalProxy = proxyProperties; } } } public ProxyProperties getGlobalProxy() { synchronized (mGlobalProxyLock) { return mGlobalProxy; } } private void handleApplyDefaultProxy(int type) { // check if new default - push it out to all VM if so ProxyProperties proxy = mNetTrackers[type].getLinkProperties().getHttpProxy(); synchronized (this) { if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return; if (mDefaultProxy == proxy) return; if (!TextUtils.isEmpty(proxy.getHost())) { mDefaultProxy = proxy; } else { mDefaultProxy = null; } } if (DBG) log("changing default proxy to " + proxy); if ((proxy == null && mGlobalProxy == null) || proxy.equals(mGlobalProxy)) return; if (mGlobalProxy != null) return; sendProxyBroadcast(proxy); } private void handleDeprecatedGlobalHttpProxy() { String proxy = Settings.Secure.getString(mContext.getContentResolver(), Settings.Secure.HTTP_PROXY); if (!TextUtils.isEmpty(proxy)) { String data[] = proxy.split(":"); String proxyHost = data[0]; int proxyPort = 8080; if (data.length > 1) { try { proxyPort = Integer.parseInt(data[1]); } catch (NumberFormatException e) { return; } } ProxyProperties p = new ProxyProperties(data[0], proxyPort, ""); setGlobalProxy(p); } } private void sendProxyBroadcast(ProxyProperties proxy) { log("sending Proxy Broadcast for " + proxy); Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy); mContext.sendBroadcast(intent); } private static class SettingsObserver extends ContentObserver { private int mWhat; private Handler mHandler; SettingsObserver(Handler handler, int what) { super(handler); mHandler = handler; mWhat = what; } void observe(Context context) { ContentResolver resolver = context.getContentResolver(); resolver.registerContentObserver(Settings.Secure.getUriFor( Settings.Secure.HTTP_PROXY), false, this); } @Override public void onChange(boolean selfChange) { mHandler.obtainMessage(mWhat).sendToTarget(); } } private void log(String s) { Slog.d(TAG, s); } private void loge(String s) { Slog.e(TAG, s); } }