| /* | |
| * 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.bluetooth.BluetoothTetheringDataTracker; | |
| 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.DummyDataStateTracker; | |
| import android.net.EthernetDataTracker; | |
| import android.net.IConnectivityManager; | |
| import android.net.LinkProperties; | |
| import android.net.MobileDataStateTracker; | |
| import android.net.NetworkInfo; | |
| import android.net.NetworkStateTracker; | |
| import android.net.NetworkUtils; | |
| import android.net.Proxy; | |
| import android.net.ProxyProperties; | |
| import android.net.vpn.VpnManager; | |
| 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.UnknownHostException; | |
| import java.util.ArrayList; | |
| import java.util.Collection; | |
| import java.util.concurrent.atomic.AtomicBoolean; | |
| 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 AtomicBoolean mBackgroundDataEnabled = new AtomicBoolean(true); | |
| 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()); | |
| mBackgroundDataEnabled.set(Settings.Secure.getInt(context.getContentResolver(), | |
| Settings.Secure.BACKGROUND_DATA, 1) == 1); | |
| // setup our unique device name | |
| if (TextUtils.isEmpty(SystemProperties.get("net.hostname"))) { | |
| 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 = NetworkUtils.numericToInetAddress(dns); | |
| } catch (IllegalArgumentException 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. | |
| */ | |
| 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); | |
| break; | |
| case ConnectivityManager.TYPE_DUMMY: | |
| mNetTrackers[netType] = new DummyDataStateTracker(netType, | |
| mNetAttributes[netType].mName); | |
| mNetTrackers[netType].startMonitoring(context, mHandler); | |
| break; | |
| case ConnectivityManager.TYPE_BLUETOOTH: | |
| mNetTrackers[netType] = BluetoothTetheringDataTracker.getInstance(); | |
| mNetTrackers[netType].startMonitoring(context, mHandler); | |
| break; | |
| case ConnectivityManager.TYPE_ETHERNET: | |
| mNetTrackers[netType] = EthernetDataTracker.getInstance(); | |
| mNetTrackers[netType].startMonitoring(context, mHandler); | |
| 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(); | |
| VpnManager.startVpnService(context); | |
| } | |
| /** | |
| * 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 (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) || | |
| TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) { | |
| 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) { | |
| Integer currentPid = new Integer(getCallingPid()); | |
| if (usedNetworkType != networkType) { | |
| NetworkStateTracker radio = mNetTrackers[networkType]; | |
| NetworkInfo ni = network.getNetworkInfo(); | |
| if (ni.isAvailable() == false) { | |
| if (DBG) log("special network not available"); | |
| if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) { | |
| return Phone.APN_TYPE_NOT_AVAILABLE; | |
| } else { | |
| // else make the attempt anyway - probably giving REQUEST_STARTED below | |
| } | |
| } | |
| 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 { | |
| // need to remember this unsupported request so we respond appropriately on stop | |
| synchronized(this) { | |
| mFeatureUsers.add(f); | |
| if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) { | |
| // this gets used for per-pid dns when connected | |
| mNetRequestersPids[usedNetworkType].add(currentPid); | |
| } | |
| } | |
| 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) || | |
| TextUtils.equals(feature, Phone.FEATURE_ENABLE_DUN_ALWAYS)) { | |
| 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; | |
| } else { | |
| if (DBG) log("not a known feature - dropping"); | |
| } | |
| } | |
| 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 mBackgroundDataEnabled.get(); | |
| } | |
| /** | |
| * @see ConnectivityManager#setBackgroundDataSetting(boolean) | |
| */ | |
| public void setBackgroundDataSetting(boolean allowBackgroundDataUsage) { | |
| mContext.enforceCallingOrSelfPermission( | |
| android.Manifest.permission.CHANGE_BACKGROUND_DATA_SETTING, | |
| "ConnectivityService"); | |
| mBackgroundDataEnabled.set(allowBackgroundDataUsage); | |
| mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_BACKGROUND_DATA, | |
| (allowBackgroundDataUsage ? ENABLED : DISABLED), 0)); | |
| } | |
| private void handleSetBackgroundData(boolean enabled) { | |
| 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() { | |
| // TODO: This detail should probably be in DataConnectionTracker's | |
| // which is where we store the value and maybe make this | |
| // asynchronous. | |
| 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 (mNetTrackers[ConnectivityManager.TYPE_MOBILE] != null) { | |
| if (DBG) { | |
| Slog.d(TAG, mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled); | |
| } | |
| mNetTrackers[ConnectivityManager.TYPE_MOBILE].setDataEnable(enabled); | |
| } | |
| } | |
| 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<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.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()); | |
| } | |
| if (mNetAttributes[prevNetType].isDefault()) { | |
| tryFailover(prevNetType); | |
| if (mActiveDefaultNetwork != -1) { | |
| NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); | |
| intent.putExtra(ConnectivityManager.EXTRA_OTHER_NETWORK_INFO, switchTo); | |
| } else { | |
| mDefaultInetConditionPublished = 0; // we're not connected anymore | |
| intent.putExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, true); | |
| } | |
| } | |
| intent.putExtra(ConnectivityManager.EXTRA_INET_CONDITION, mDefaultInetConditionPublished); | |
| // do this before we broadcast the change | |
| handleConnectivityChange(prevNetType); | |
| sendStickyBroadcast(intent); | |
| /* | |
| * If the failover network is already connected, then immediately send | |
| * out a followup broadcast indicating successful failover | |
| */ | |
| if (mActiveDefaultNetwork != -1) { | |
| sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo()); | |
| } | |
| } | |
| private void tryFailover(int prevNetType) { | |
| /* | |
| * If this is a default network, check if other defaults are available. | |
| * Try to reconnect on all available and let them hash it out when | |
| * more than one connects. | |
| */ | |
| if (mNetAttributes[prevNetType].isDefault()) { | |
| if (mActiveDefaultNetwork == prevNetType) { | |
| mActiveDefaultNetwork = -1; | |
| } | |
| // don't signal a reconnect for anything lower or equal priority than our | |
| // current connected default | |
| // TODO - don't filter by priority now - nice optimization but risky | |
| // int currentPriority = -1; | |
| // if (mActiveDefaultNetwork != -1) { | |
| // currentPriority = mNetAttributes[mActiveDefaultNetwork].mPriority; | |
| // } | |
| for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) { | |
| if (checkType == prevNetType) continue; | |
| if (mNetAttributes[checkType] == null) continue; | |
| if (!mNetAttributes[checkType].isDefault()) continue; | |
| // Enabling the isAvailable() optimization caused mobile to not get | |
| // selected if it was in the middle of error handling. Specifically | |
| // a moble connection that took 30 seconds to complete the DEACTIVATE_DATA_CALL | |
| // would not be available and we wouldn't get connected to anything. | |
| // So removing the isAvailable() optimization below for now. TODO: This | |
| // optimization should work and we need to investigate why it doesn't work. | |
| // This could be related to how DEACTIVATE_DATA_CALL is reporting its | |
| // complete before it is really complete. | |
| // if (!mNetTrackers[checkType].isAvailable()) continue; | |
| // if (currentPriority >= mNetAttributes[checkType].mPriority) continue; | |
| NetworkStateTracker checkTracker = mNetTrackers[checkType]; | |
| NetworkInfo checkInfo = checkTracker.getNetworkInfo(); | |
| if (!checkInfo.isConnectedOrConnecting() || checkTracker.isTeardownRequested()) { | |
| checkInfo.setFailover(true); | |
| checkTracker.reconnect(); | |
| } | |
| if (DBG) log("Attempting to switch to " + checkInfo.getTypeName()); | |
| } | |
| } | |
| } | |
| 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); | |
| } | |
| if (mNetAttributes[info.getType()].isDefault()) { | |
| tryFailover(info.getType()); | |
| if (mActiveDefaultNetwork != -1) { | |
| NetworkInfo switchTo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(); | |
| 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 (mActiveDefaultNetwork != -1) { | |
| sendConnectedBroadcast(mNetTrackers[mActiveDefaultNetwork].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<InetAddress> 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(); | |
| if (TextUtils.isEmpty(interfaceName)) return; | |
| for (InetAddress gateway : p.getGateways()) { | |
| if (NetworkUtils.addHostRoute(interfaceName, gateway, null) && | |
| NetworkUtils.addDefaultRoute(interfaceName, gateway)) { | |
| if (DBG) { | |
| NetworkInfo networkInfo = nt.getNetworkInfo(); | |
| log("addDefaultRoute for " + networkInfo.getTypeName() + | |
| " (" + interfaceName + "), GatewayAddr=" + gateway.getHostAddress()); | |
| } | |
| } | |
| } | |
| } | |
| 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) { | |
| if (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<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) 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<pids.size(); j++) { | |
| Integer pid = (Integer)pids.get(j); | |
| if (pid.intValue() == myPid) { | |
| Collection<InetAddress> 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, ""); | |
| } | |
| } | |
| // return true if results in a change | |
| private boolean writePidDns(Collection <InetAddress> dnses, int pid) { | |
| int j = 1; | |
| boolean changed = false; | |
| for (InetAddress dns : dnses) { | |
| String dnsString = dns.getHostAddress(); | |
| if (changed || !dnsString.equals(SystemProperties.get("net.dns" + j + "." + pid))) { | |
| changed = true; | |
| SystemProperties.set("net.dns" + j++ + "." + pid, dns.getHostAddress()); | |
| } | |
| } | |
| return changed; | |
| } | |
| 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); | |
| /* | |
| * Connectivity events can happen before boot has completed ... | |
| */ | |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); | |
| 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<InetAddress> dnses = p.getDnses(); | |
| boolean changed = false; | |
| if (mNetAttributes[netType].isDefault()) { | |
| int j = 1; | |
| if (dnses.size() == 0 && mDefaultDns != null) { | |
| String dnsString = mDefaultDns.getHostAddress(); | |
| if (!dnsString.equals(SystemProperties.get("net.dns1"))) { | |
| if (DBG) { | |
| log("no dns provided - using " + dnsString); | |
| } | |
| changed = true; | |
| SystemProperties.set("net.dns1", dnsString); | |
| } | |
| j++; | |
| } else { | |
| for (InetAddress dns : dnses) { | |
| String dnsString = dns.getHostAddress(); | |
| if (!changed && dnsString.equals(SystemProperties.get("net.dns" + j))) { | |
| j++; | |
| continue; | |
| } | |
| if (DBG) { | |
| log("adding dns " + dns + " for " + | |
| nt.getNetworkInfo().getTypeName()); | |
| } | |
| changed = true; | |
| SystemProperties.set("net.dns" + j++, dnsString); | |
| } | |
| } | |
| for (int k=j ; k<mNumDnsEntries; k++) { | |
| if (changed || !TextUtils.isEmpty(SystemProperties.get("net.dns" + k))) { | |
| if (DBG) log("erasing net.dns" + k); | |
| changed = true; | |
| 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); | |
| changed = writePidDns(dnses, pid.intValue()); | |
| } | |
| } | |
| if (changed) 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(); | |
| synchronized (this) { | |
| pw.println("NetworkTranstionWakeLock is currently " + | |
| (mNetTransitionWakeLock.isHeld() ? "" : "not ") + "held."); | |
| pw.println("It was last requested for "+mNetTransitionWakeLockCausedBy); | |
| } | |
| pw.println(); | |
| mTethering.dump(fd, pw, args); | |
| if (mInetLog != null) { | |
| pw.println(); | |
| pw.println("Inet condition reports:"); | |
| for(int i = 0; i < mInetLog.size(); i++) { | |
| pw.println(mInetLog.get(i)); | |
| } | |
| } | |
| } | |
| // must be stateless - things change under us. | |
| private class MyHandler extends Handler { | |
| public MyHandler(Looper looper) { | |
| super(looper); | |
| } | |
| @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 | |
| log("Dropping ConnectivityChange for " + | |
| info.getTypeName() + ": " + | |
| state + "/" + info.getDetailedState()); | |
| } | |
| return; | |
| } | |
| mNetAttributes[type].mLastState = state; | |
| if (DBG) log("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_CONFIGURATION_CHANGED: | |
| info = (NetworkInfo) msg.obj; | |
| type = info.getType(); | |
| handleConnectivityChange(type); | |
| break; | |
| case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: | |
| String causedBy = null; | |
| synchronized (ConnectivityService.this) { | |
| if (msg.arg1 == mNetTransitionWakeLockSerialNumber && | |
| mNetTransitionWakeLock.isHeld()) { | |
| mNetTransitionWakeLock.release(); | |
| causedBy = mNetTransitionWakeLockCausedBy; | |
| } | |
| } | |
| if (causedBy != null) { | |
| log("NetTransition Wakelock for " + causedBy + " released by timeout"); | |
| } | |
| break; | |
| case EVENT_RESTORE_DEFAULT_NETWORK: | |
| FeatureUser u = (FeatureUser)msg.obj; | |
| u.expire(); | |
| break; | |
| case EVENT_INET_CONDITION_CHANGE: | |
| { | |
| int netType = msg.arg1; | |
| int condition = msg.arg2; | |
| handleInetConditionChange(netType, condition); | |
| break; | |
| } | |
| case EVENT_INET_CONDITION_HOLD_END: | |
| { | |
| int netType = msg.arg1; | |
| int sequence = msg.arg2; | |
| handleInetConditionHoldEnd(netType, sequence); | |
| break; | |
| } | |
| case EVENT_SET_NETWORK_PREFERENCE: | |
| { | |
| int preference = msg.arg1; | |
| handleSetNetworkPreference(preference); | |
| break; | |
| } | |
| case EVENT_SET_BACKGROUND_DATA: | |
| { | |
| boolean enabled = (msg.arg1 == ENABLED); | |
| handleSetBackgroundData(enabled); | |
| break; | |
| } | |
| case EVENT_SET_MOBILE_DATA: | |
| { | |
| boolean enabled = (msg.arg1 == ENABLED); | |
| handleSetMobileData(enabled); | |
| break; | |
| } | |
| case EVENT_APPLY_GLOBAL_HTTP_PROXY: | |
| { | |
| handleDeprecatedGlobalHttpProxy(); | |
| } | |
| } | |
| } | |
| } | |
| // 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]; | |
| } | |
| } | |
| public String[] getTetherableBluetoothRegexs() { | |
| enforceTetherAccessPermission(); | |
| if (isTetheringSupported()) { | |
| return mTethering.getTetherableBluetoothRegexs(); | |
| } 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; | |
| } | |
| // An API NetworkStateTrackers can call when they lose their network. | |
| // This will automatically be cleared after X seconds or a network becomes CONNECTED, | |
| // whichever happens first. The timer is started by the first caller and not | |
| // restarted by subsequent callers. | |
| public void requestNetworkTransitionWakelock(String forWhom) { | |
| enforceConnectivityInternalPermission(); | |
| synchronized (this) { | |
| if (mNetTransitionWakeLock.isHeld()) return; | |
| mNetTransitionWakeLockSerialNumber++; | |
| mNetTransitionWakeLock.acquire(); | |
| mNetTransitionWakeLockCausedBy = forWhom; | |
| } | |
| mHandler.sendMessageDelayed(mHandler.obtainMessage( | |
| EVENT_CLEAR_NET_TRANSITION_WAKELOCK, | |
| mNetTransitionWakeLockSerialNumber, 0), | |
| mNetTransitionWakeLockTimeout); | |
| return; | |
| } | |
| // 100 percent is full good, 0 is full bad. | |
| public void reportInetCondition(int networkType, int percentage) { | |
| if (DBG) log("reportNetworkCondition(" + networkType + ", " + percentage + ")"); | |
| mContext.enforceCallingOrSelfPermission( | |
| android.Manifest.permission.STATUS_BAR, | |
| "ConnectivityService"); | |
| if (DBG) { | |
| int pid = getCallingPid(); | |
| int uid = getCallingUid(); | |
| String s = pid + "(" + uid + ") reports inet is " + | |
| (percentage > 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) { | |
| if (proxy == null) proxy = new ProxyProperties("", 0, ""); | |
| log("sending Proxy Broadcast for " + proxy); | |
| Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); | |
| intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | | |
| Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); | |
| intent.putExtra(Proxy.EXTRA_PROXY_INFO, proxy); | |
| mContext.sendStickyBroadcast(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); | |
| } | |
| } |