summaryrefslogtreecommitdiffstats
path: root/services/core/java/com/android/server/ConnectivityService.java
diff options
context:
space:
mode:
Diffstat (limited to 'services/core/java/com/android/server/ConnectivityService.java')
-rw-r--r--services/core/java/com/android/server/ConnectivityService.java4909
1 files changed, 4909 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
new file mode 100644
index 0000000..6574898
--- /dev/null
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -0,0 +1,4909 @@
+/*
+ * 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 static android.Manifest.permission.MANAGE_NETWORK_POLICY;
+import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
+import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_DUMMY;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
+import static android.net.ConnectivityManager.TYPE_WIFI;
+import static android.net.ConnectivityManager.TYPE_WIMAX;
+import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.ConnectivityManager.isNetworkTypeValid;
+import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL;
+import static android.net.NetworkPolicyManager.RULE_REJECT_METERED;
+
+import android.app.AlarmManager;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothTetheringDataTracker;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.CaptivePortalTracker;
+import android.net.ConnectivityManager;
+import android.net.DummyDataStateTracker;
+import android.net.EthernetDataTracker;
+import android.net.IConnectivityManager;
+import android.net.INetworkManagementEventObserver;
+import android.net.INetworkPolicyListener;
+import android.net.INetworkPolicyManager;
+import android.net.INetworkStatsService;
+import android.net.LinkAddress;
+import android.net.LinkProperties;
+import android.net.LinkProperties.CompareResult;
+import android.net.LinkQualityInfo;
+import android.net.MobileDataStateTracker;
+import android.net.NetworkConfig;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkQuotaInfo;
+import android.net.NetworkState;
+import android.net.NetworkStateTracker;
+import android.net.NetworkUtils;
+import android.net.Proxy;
+import android.net.ProxyProperties;
+import android.net.RouteInfo;
+import android.net.SamplingDataTracker;
+import android.net.Uri;
+import android.net.wifi.WifiStateTracker;
+import android.net.wimax.WimaxManagerConstants;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.INetworkManagementService;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.security.Credentials;
+import android.security.KeyStore;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.Xml;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.net.LegacyVpnInfo;
+import com.android.internal.net.VpnConfig;
+import com.android.internal.net.VpnProfile;
+import com.android.internal.telephony.DctConstants;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
+import com.android.server.am.BatteryStatsService;
+import com.android.server.connectivity.DataConnectionStats;
+import com.android.server.connectivity.Nat464Xlat;
+import com.android.server.connectivity.PacManager;
+import com.android.server.connectivity.Tethering;
+import com.android.server.connectivity.Vpn;
+import com.android.server.net.BaseNetworkObserver;
+import com.android.server.net.LockdownVpnTracker;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Sets;
+
+import dalvik.system.DexClassLoader;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Constructor;
+import java.net.HttpURLConnection;
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+
+/**
+ * @hide
+ */
+public class ConnectivityService extends IConnectivityManager.Stub {
+ private static final String TAG = "ConnectivityService";
+
+ private static final boolean DBG = true;
+ private static final boolean VDBG = false;
+
+ private static final boolean LOGD_RULES = false;
+
+ // TODO: create better separation between radio types and network types
+
+ // 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";
+
+ // Default value if FAIL_FAST_TIME_MS is not set
+ private static final int DEFAULT_FAIL_FAST_TIME_MS = 1 * 60 * 1000;
+ // system property that can override DEFAULT_FAIL_FAST_TIME_MS
+ private static final String FAIL_FAST_TIME_MS =
+ "persist.radio.fail_fast_time_ms";
+
+ private static final String ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED =
+ "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED";
+
+ private static final int SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE = 0;
+
+ private PendingIntent mSampleIntervalElapsedIntent;
+
+ // Set network sampling interval at 12 minutes, this way, even if the timers get
+ // aggregated, it will fire at around 15 minutes, which should allow us to
+ // aggregate this timer with other timers (specially the socket keep alive timers)
+ private static final int DEFAULT_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 12 * 60);
+
+ // start network sampling a minute after booting ...
+ private static final int DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 60);
+
+ AlarmManager mAlarmManager;
+
+ // used in recursive route setting to add gateways for the host for which
+ // a host route was requested.
+ private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10;
+
+ private Tethering mTethering;
+
+ private KeyStore mKeyStore;
+
+ @GuardedBy("mVpns")
+ private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>();
+ private VpnCallback mVpnCallback = new VpnCallback();
+
+ private boolean mLockdownEnabled;
+ private LockdownVpnTracker mLockdownTracker;
+
+ private Nat464Xlat mClat;
+
+ /** Lock around {@link #mUidRules} and {@link #mMeteredIfaces}. */
+ private Object mRulesLock = new Object();
+ /** Currently active network rules by UID. */
+ private SparseIntArray mUidRules = new SparseIntArray();
+ /** Set of ifaces that are costly. */
+ private HashSet<String> mMeteredIfaces = Sets.newHashSet();
+
+ /**
+ * Sometimes we want to refer to the individual network state
+ * trackers separately, and sometimes we just want to treat them
+ * abstractly.
+ */
+ private NetworkStateTracker mNetTrackers[];
+
+ /* Handles captive portal check on a network */
+ private CaptivePortalTracker mCaptivePortalTracker;
+
+ /**
+ * The link properties that define the current links
+ */
+ private LinkProperties mCurrentLinkProperties[];
+
+ /**
+ * 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<Integer> mNetRequestersPids[];
+
+ // priority order of the nettrackers
+ // (excluding dynamically set mNetworkPreference)
+ // TODO - move mNetworkTypePreference into this
+ private int[] mPriorityList;
+
+ private Context mContext;
+ private int mNetworkPreference;
+ private int mActiveDefaultNetwork = -1;
+ // 0 is full bad, 100 is full good
+ private int mDefaultInetCondition = 0;
+ private int mDefaultInetConditionPublished = 0;
+ private boolean mInetConditionChangeInFlight = false;
+ private int mDefaultConnectionSequence = 0;
+
+ private Object mDnsLock = new Object();
+ private int mNumDnsEntries;
+
+ private boolean mTestMode;
+ private static ConnectivityService sServiceInstance;
+
+ private INetworkManagementService mNetd;
+ private INetworkPolicyManager mPolicyManager;
+
+ private static final int ENABLED = 1;
+ private static final int DISABLED = 0;
+
+ private static final boolean ADD = true;
+ private static final boolean REMOVE = false;
+
+ private static final boolean TO_DEFAULT_TABLE = true;
+ private static final boolean TO_SECONDARY_TABLE = false;
+
+ private static final boolean EXEMPT = true;
+ private static final boolean UNEXEMPT = false;
+
+ /**
+ * used internally as a delayed event to make us switch back to the
+ * default network
+ */
+ private static final int EVENT_RESTORE_DEFAULT_NETWORK = 1;
+
+ /**
+ * used internally to change our mobile data enabled flag
+ */
+ private static final int EVENT_CHANGE_MOBILE_DATA_ENABLED = 2;
+
+ /**
+ * used internally to change our network preference setting
+ * arg1 = networkType to prefer
+ */
+ private static final int EVENT_SET_NETWORK_PREFERENCE = 3;
+
+ /**
+ * used internally to synchronize inet condition reports
+ * arg1 = networkType
+ * arg2 = condition (0 bad, 100 good)
+ */
+ private static final int EVENT_INET_CONDITION_CHANGE = 4;
+
+ /**
+ * used internally to mark the end of inet condition hold periods
+ * arg1 = networkType
+ */
+ private static final int EVENT_INET_CONDITION_HOLD_END = 5;
+
+ /**
+ * used internally to set enable/disable cellular data
+ * arg1 = ENBALED or DISABLED
+ */
+ private static final int EVENT_SET_MOBILE_DATA = 7;
+
+ /**
+ * used internally to clear a wakelock when transitioning
+ * from one net to another
+ */
+ private static final int EVENT_CLEAR_NET_TRANSITION_WAKELOCK = 8;
+
+ /**
+ * used internally to reload global proxy settings
+ */
+ private static final int EVENT_APPLY_GLOBAL_HTTP_PROXY = 9;
+
+ /**
+ * used internally to set external dependency met/unmet
+ * arg1 = ENABLED (met) or DISABLED (unmet)
+ * arg2 = NetworkType
+ */
+ private static final int EVENT_SET_DEPENDENCY_MET = 10;
+
+ /**
+ * used internally to send a sticky broadcast delayed.
+ */
+ private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 11;
+
+ /**
+ * Used internally to
+ * {@link NetworkStateTracker#setPolicyDataEnable(boolean)}.
+ */
+ private static final int EVENT_SET_POLICY_DATA_ENABLE = 12;
+
+ private static final int EVENT_VPN_STATE_CHANGED = 13;
+
+ /**
+ * Used internally to disable fail fast of mobile data
+ */
+ private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14;
+
+ /**
+ * user internally to indicate that data sampling interval is up
+ */
+ private static final int EVENT_SAMPLE_INTERVAL_ELAPSED = 15;
+
+ /**
+ * PAC manager has received new port.
+ */
+ private static final int EVENT_PROXY_HAS_CHANGED = 16;
+
+ /** Handler used for internal events. */
+ private InternalHandler mHandler;
+ /** Handler used for incoming {@link NetworkStateTracker} events. */
+ private NetworkStateTrackerHandler mTrackerHandler;
+
+ // list of DeathRecipients used to make sure features are turned off when
+ // a process dies
+ private List<FeatureUser> mFeatureUsers;
+
+ private boolean mSystemReady;
+ private Intent mInitialBroadcast;
+
+ private PowerManager.WakeLock mNetTransitionWakeLock;
+ private String mNetTransitionWakeLockCausedBy = "";
+ private int mNetTransitionWakeLockSerialNumber;
+ private int mNetTransitionWakeLockTimeout;
+
+ private InetAddress mDefaultDns;
+
+ // Lock for protecting access to mAddedRoutes and mExemptAddresses
+ private final Object mRoutesLock = new Object();
+
+ // this collection is used to refcount the added routes - if there are none left
+ // it's time to remove the route from the route table
+ @GuardedBy("mRoutesLock")
+ private Collection<RouteInfo> mAddedRoutes = new ArrayList<RouteInfo>();
+
+ // this collection corresponds to the entries of mAddedRoutes that have routing exemptions
+ // used to handle cleanup of exempt rules
+ @GuardedBy("mRoutesLock")
+ private Collection<LinkAddress> mExemptAddresses = new ArrayList<LinkAddress>();
+
+ // 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;
+ private Object mProxyLock = new Object();
+ private boolean mDefaultProxyDisabled = false;
+
+ // track the global proxy.
+ private ProxyProperties mGlobalProxy = null;
+
+ private PacManager mPacManager = null;
+
+ private SettingsObserver mSettingsObserver;
+
+ NetworkConfig[] mNetConfigs;
+ 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;
+
+ // the set of network types that can only be enabled by system/sig apps
+ List mProtectedNetworks;
+
+ private DataConnectionStats mDataConnectionStats;
+
+ private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
+
+ TelephonyManager mTelephonyManager;
+
+ public ConnectivityService(Context context, INetworkManagementService netd,
+ INetworkStatsService statsService, INetworkPolicyManager policyManager) {
+ // Currently, omitting a NetworkFactory will create one internally
+ // TODO: create here when we have cleaner WiMAX support
+ this(context, netd, statsService, policyManager, null);
+ }
+
+ public ConnectivityService(Context context, INetworkManagementService netManager,
+ INetworkStatsService statsService, INetworkPolicyManager policyManager,
+ NetworkFactory netFactory) {
+ if (DBG) log("ConnectivityService starting up");
+
+ HandlerThread handlerThread = new HandlerThread("ConnectivityServiceThread");
+ handlerThread.start();
+ mHandler = new InternalHandler(handlerThread.getLooper());
+ mTrackerHandler = new NetworkStateTrackerHandler(handlerThread.getLooper());
+
+ if (netFactory == null) {
+ netFactory = new DefaultNetworkFactory(context, mTrackerHandler);
+ }
+
+ // 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.Global.getString(context.getContentResolver(),
+ Settings.Global.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 = checkNotNull(context, "missing Context");
+ mNetd = checkNotNull(netManager, "missing INetworkManagementService");
+ mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
+ mKeyStore = KeyStore.getInstance();
+ mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+
+ try {
+ mPolicyManager.registerListener(mPolicyListener);
+ } catch (RemoteException e) {
+ // ouch, no rules updates means some processes may never get network
+ loge("unable to register INetworkPolicyListener" + e.toString());
+ }
+
+ final PowerManager powerManager = (PowerManager) context.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];
+ mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1];
+
+ mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1];
+ mNetConfigs = new NetworkConfig[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 (VDBG) log("raString=" + raString + " r=" + r);
+ 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;
+ }
+
+ // TODO: What is the "correct" way to do determine if this is a wifi only device?
+ boolean wifiOnly = SystemProperties.getBoolean("ro.radio.noril", false);
+ log("wifiOnly=" + wifiOnly);
+ String[] naStrings = context.getResources().getStringArray(
+ com.android.internal.R.array.networkAttributes);
+ for (String naString : naStrings) {
+ try {
+ NetworkConfig n = new NetworkConfig(naString);
+ if (VDBG) log("naString=" + naString + " config=" + n);
+ if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) {
+ loge("Error in networkAttributes - ignoring attempt to define type " +
+ n.type);
+ continue;
+ }
+ if (wifiOnly && ConnectivityManager.isNetworkTypeMobile(n.type)) {
+ log("networkAttributes - ignoring mobile as this dev is wifiOnly " +
+ n.type);
+ continue;
+ }
+ if (mNetConfigs[n.type] != null) {
+ loge("Error in networkAttributes - ignoring attempt to redefine type " +
+ n.type);
+ continue;
+ }
+ if (mRadioAttributes[n.radio] == null) {
+ loge("Error in networkAttributes - ignoring attempt to use undefined " +
+ "radio " + n.radio + " in network type " + n.type);
+ continue;
+ }
+ mNetConfigs[n.type] = n;
+ mNetworksDefined++;
+ } catch(Exception e) {
+ // ignore it - leave the entry null
+ }
+ }
+ if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
+
+ mProtectedNetworks = new ArrayList<Integer>();
+ int[] protectedNetworks = context.getResources().getIntArray(
+ com.android.internal.R.array.config_protectedNetworks);
+ for (int p : protectedNetworks) {
+ if ((mNetConfigs[p] != null) && (mProtectedNetworks.contains(p) == false)) {
+ mProtectedNetworks.add(p);
+ } else {
+ if (DBG) loge("Ignoring protectedNetwork " + p);
+ }
+ }
+
+ // high priority first
+ mPriorityList = new int[mNetworksDefined];
+ {
+ int insertionPoint = mNetworksDefined-1;
+ int currentLowest = 0;
+ int nextLowest = 0;
+ while (insertionPoint > -1) {
+ for (NetworkConfig na : mNetConfigs) {
+ if (na == null) continue;
+ if (na.priority < currentLowest) continue;
+ if (na.priority > currentLowest) {
+ if (na.priority < nextLowest || nextLowest == 0) {
+ nextLowest = na.priority;
+ }
+ continue;
+ }
+ mPriorityList[insertionPoint--] = na.type;
+ }
+ currentLowest = nextLowest;
+ nextLowest = 0;
+ }
+ }
+
+ // Update mNetworkPreference according to user mannually first then overlay config.xml
+ mNetworkPreference = getPersistedNetworkPreference();
+ if (mNetworkPreference == -1) {
+ for (int n : mPriorityList) {
+ if (mNetConfigs[n].isDefault() && ConnectivityManager.isNetworkTypeValid(n)) {
+ mNetworkPreference = n;
+ break;
+ }
+ }
+ if (mNetworkPreference == -1) {
+ throw new IllegalStateException(
+ "You should set at least one default Network in config.xml!");
+ }
+ }
+
+ mNetRequestersPids =
+ (List<Integer> [])new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
+ for (int i : mPriorityList) {
+ mNetRequestersPids[i] = new ArrayList<Integer>();
+ }
+
+ mFeatureUsers = new ArrayList<FeatureUser>();
+
+ mTestMode = SystemProperties.get("cm.test.mode").equals("true")
+ && SystemProperties.get("ro.build.type").equals("eng");
+
+ // Create and start trackers for hard-coded networks
+ for (int targetNetworkType : mPriorityList) {
+ final NetworkConfig config = mNetConfigs[targetNetworkType];
+ final NetworkStateTracker tracker;
+ try {
+ tracker = netFactory.createTracker(targetNetworkType, config);
+ mNetTrackers[targetNetworkType] = tracker;
+ } catch (IllegalArgumentException e) {
+ Slog.e(TAG, "Problem creating " + getNetworkTypeName(targetNetworkType)
+ + " tracker: " + e);
+ continue;
+ }
+
+ tracker.startMonitoring(context, mTrackerHandler);
+ if (config.isDefault()) {
+ tracker.reconnect();
+ }
+ }
+
+ mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper());
+
+ //set up the listener for user state for creating user VPNs
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_STARTING);
+ intentFilter.addAction(Intent.ACTION_USER_STOPPING);
+ mContext.registerReceiverAsUser(
+ mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
+ mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler);
+
+ try {
+ mNetd.registerObserver(mTethering);
+ mNetd.registerObserver(mDataActivityObserver);
+ mNetd.registerObserver(mClat);
+ } catch (RemoteException e) {
+ loge("Error registering observer :" + e);
+ }
+
+ if (DBG) {
+ mInetLog = new ArrayList();
+ }
+
+ mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
+ mSettingsObserver.observe(mContext);
+
+ mDataConnectionStats = new DataConnectionStats(mContext);
+ mDataConnectionStats.startMonitoring();
+
+ // start network sampling ..
+ Intent intent = new Intent(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED, null);
+ mSampleIntervalElapsedIntent = PendingIntent.getBroadcast(mContext,
+ SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE, intent, 0);
+
+ mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
+ setAlarm(DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS * 1000, mSampleIntervalElapsedIntent);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED);
+ mContext.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED)) {
+ mHandler.sendMessage(mHandler.obtainMessage
+ (EVENT_SAMPLE_INTERVAL_ELAPSED));
+ }
+ }
+ },
+ new IntentFilter(filter));
+
+ mPacManager = new PacManager(mContext, mHandler, EVENT_PROXY_HAS_CHANGED);
+
+ filter = new IntentFilter();
+ filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
+ mContext.registerReceiver(mProvisioningReceiver, filter);
+ }
+
+ /**
+ * Factory that creates {@link NetworkStateTracker} instances using given
+ * {@link NetworkConfig}.
+ */
+ public interface NetworkFactory {
+ public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config);
+ }
+
+ private static class DefaultNetworkFactory implements NetworkFactory {
+ private final Context mContext;
+ private final Handler mTrackerHandler;
+
+ public DefaultNetworkFactory(Context context, Handler trackerHandler) {
+ mContext = context;
+ mTrackerHandler = trackerHandler;
+ }
+
+ @Override
+ public NetworkStateTracker createTracker(int targetNetworkType, NetworkConfig config) {
+ switch (config.radio) {
+ case TYPE_WIFI:
+ return new WifiStateTracker(targetNetworkType, config.name);
+ case TYPE_MOBILE:
+ return new MobileDataStateTracker(targetNetworkType, config.name);
+ case TYPE_DUMMY:
+ return new DummyDataStateTracker(targetNetworkType, config.name);
+ case TYPE_BLUETOOTH:
+ return BluetoothTetheringDataTracker.getInstance();
+ case TYPE_WIMAX:
+ return makeWimaxStateTracker(mContext, mTrackerHandler);
+ case TYPE_ETHERNET:
+ return EthernetDataTracker.getInstance();
+ default:
+ throw new IllegalArgumentException(
+ "Trying to create a NetworkStateTracker for an unknown radio type: "
+ + config.radio);
+ }
+ }
+ }
+
+ /**
+ * Loads external WiMAX library and registers as system service, returning a
+ * {@link NetworkStateTracker} for WiMAX. Caller is still responsible for
+ * invoking {@link NetworkStateTracker#startMonitoring(Context, Handler)}.
+ */
+ private static NetworkStateTracker makeWimaxStateTracker(
+ Context context, Handler trackerHandler) {
+ // Initialize Wimax
+ DexClassLoader wimaxClassLoader;
+ Class wimaxStateTrackerClass = null;
+ Class wimaxServiceClass = null;
+ Class wimaxManagerClass;
+ String wimaxJarLocation;
+ String wimaxLibLocation;
+ String wimaxManagerClassName;
+ String wimaxServiceClassName;
+ String wimaxStateTrackerClassName;
+
+ NetworkStateTracker wimaxStateTracker = null;
+
+ boolean isWimaxEnabled = context.getResources().getBoolean(
+ com.android.internal.R.bool.config_wimaxEnabled);
+
+ if (isWimaxEnabled) {
+ try {
+ wimaxJarLocation = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxServiceJarLocation);
+ wimaxLibLocation = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxNativeLibLocation);
+ wimaxManagerClassName = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxManagerClassname);
+ wimaxServiceClassName = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxServiceClassname);
+ wimaxStateTrackerClassName = context.getResources().getString(
+ com.android.internal.R.string.config_wimaxStateTrackerClassname);
+
+ if (DBG) log("wimaxJarLocation: " + wimaxJarLocation);
+ wimaxClassLoader = new DexClassLoader(wimaxJarLocation,
+ new ContextWrapper(context).getCacheDir().getAbsolutePath(),
+ wimaxLibLocation, ClassLoader.getSystemClassLoader());
+
+ try {
+ wimaxManagerClass = wimaxClassLoader.loadClass(wimaxManagerClassName);
+ wimaxStateTrackerClass = wimaxClassLoader.loadClass(wimaxStateTrackerClassName);
+ wimaxServiceClass = wimaxClassLoader.loadClass(wimaxServiceClassName);
+ } catch (ClassNotFoundException ex) {
+ loge("Exception finding Wimax classes: " + ex.toString());
+ return null;
+ }
+ } catch(Resources.NotFoundException ex) {
+ loge("Wimax Resources does not exist!!! ");
+ return null;
+ }
+
+ try {
+ if (DBG) log("Starting Wimax Service... ");
+
+ Constructor wmxStTrkrConst = wimaxStateTrackerClass.getConstructor
+ (new Class[] {Context.class, Handler.class});
+ wimaxStateTracker = (NetworkStateTracker) wmxStTrkrConst.newInstance(
+ context, trackerHandler);
+
+ Constructor wmxSrvConst = wimaxServiceClass.getDeclaredConstructor
+ (new Class[] {Context.class, wimaxStateTrackerClass});
+ wmxSrvConst.setAccessible(true);
+ IBinder svcInvoker = (IBinder)wmxSrvConst.newInstance(context, wimaxStateTracker);
+ wmxSrvConst.setAccessible(false);
+
+ ServiceManager.addService(WimaxManagerConstants.WIMAX_SERVICE, svcInvoker);
+
+ } catch(Exception ex) {
+ loge("Exception creating Wimax classes: " + ex.toString());
+ return null;
+ }
+ } else {
+ loge("Wimax is not enabled or not added to the network attributes!!! ");
+ return null;
+ }
+
+ return wimaxStateTracker;
+ }
+
+ /**
+ * Sets the preferred network.
+ * @param preference the new preference
+ */
+ public void setNetworkPreference(int preference) {
+ enforceChangePermission();
+
+ mHandler.sendMessage(
+ mHandler.obtainMessage(EVENT_SET_NETWORK_PREFERENCE, preference, 0));
+ }
+
+ public int getNetworkPreference() {
+ enforceAccessPermission();
+ int preference;
+ synchronized(this) {
+ preference = mNetworkPreference;
+ }
+ return preference;
+ }
+
+ private void handleSetNetworkPreference(int preference) {
+ if (ConnectivityManager.isNetworkTypeValid(preference) &&
+ mNetConfigs[preference] != null &&
+ mNetConfigs[preference].isDefault()) {
+ if (mNetworkPreference != preference) {
+ final ContentResolver cr = mContext.getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.NETWORK_PREFERENCE, preference);
+ synchronized(this) {
+ mNetworkPreference = preference;
+ }
+ enforcePreference();
+ }
+ }
+ }
+
+ private int getConnectivityChangeDelay() {
+ final ContentResolver cr = mContext.getContentResolver();
+
+ /** Check system properties for the default value then use secure settings value, if any. */
+ int defaultDelay = SystemProperties.getInt(
+ "conn." + Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+ ConnectivityManager.CONNECTIVITY_CHANGE_DELAY_DEFAULT);
+ return Settings.Global.getInt(cr, Settings.Global.CONNECTIVITY_CHANGE_DELAY,
+ defaultDelay);
+ }
+
+ private int getPersistedNetworkPreference() {
+ final ContentResolver cr = mContext.getContentResolver();
+
+ final int networkPrefSetting = Settings.Global
+ .getInt(cr, Settings.Global.NETWORK_PREFERENCE, -1);
+
+ return networkPrefSetting;
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ /**
+ * Check if UID should be blocked from using the network represented by the
+ * given {@link NetworkStateTracker}.
+ */
+ private boolean isNetworkBlocked(NetworkStateTracker tracker, int uid) {
+ final String iface = tracker.getLinkProperties().getInterfaceName();
+
+ final boolean networkCostly;
+ final int uidRules;
+ synchronized (mRulesLock) {
+ networkCostly = mMeteredIfaces.contains(iface);
+ uidRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+ }
+
+ if (networkCostly && (uidRules & RULE_REJECT_METERED) != 0) {
+ return true;
+ }
+
+ // no restrictive rules; network is visible
+ return false;
+ }
+
+ /**
+ * Return a filtered {@link NetworkInfo}, potentially marked
+ * {@link DetailedState#BLOCKED} based on
+ * {@link #isNetworkBlocked(NetworkStateTracker, int)}.
+ */
+ private NetworkInfo getFilteredNetworkInfo(NetworkStateTracker tracker, int uid) {
+ NetworkInfo info = tracker.getNetworkInfo();
+ if (isNetworkBlocked(tracker, uid)) {
+ // network is blocked; clone and override state
+ info = new NetworkInfo(info);
+ info.setDetailedState(DetailedState.BLOCKED, null, null);
+ }
+ if (mLockdownTracker != null) {
+ info = mLockdownTracker.augmentNetworkInfo(info);
+ }
+ return info;
+ }
+
+ /**
+ * 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
+ */
+ @Override
+ public NetworkInfo getActiveNetworkInfo() {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
+ }
+
+ /**
+ * Find the first Provisioning network.
+ *
+ * @return NetworkInfo or null if none.
+ */
+ private NetworkInfo getProvisioningNetworkInfo() {
+ enforceAccessPermission();
+
+ // Find the first Provisioning Network
+ NetworkInfo provNi = null;
+ for (NetworkInfo ni : getAllNetworkInfo()) {
+ if (ni.isConnectedToProvisioningNetwork()) {
+ provNi = ni;
+ break;
+ }
+ }
+ if (DBG) log("getProvisioningNetworkInfo: X provNi=" + provNi);
+ return provNi;
+ }
+
+ /**
+ * Find the first Provisioning network or the ActiveDefaultNetwork
+ * if there is no Provisioning network
+ *
+ * @return NetworkInfo or null if none.
+ */
+ @Override
+ public NetworkInfo getProvisioningOrActiveNetworkInfo() {
+ enforceAccessPermission();
+
+ NetworkInfo provNi = getProvisioningNetworkInfo();
+ if (provNi == null) {
+ final int uid = Binder.getCallingUid();
+ provNi = getNetworkInfo(mActiveDefaultNetwork, uid);
+ }
+ if (DBG) log("getProvisioningOrActiveNetworkInfo: X provNi=" + provNi);
+ return provNi;
+ }
+
+ public NetworkInfo getActiveNetworkInfoUnfiltered() {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(mActiveDefaultNetwork)) {
+ final NetworkStateTracker tracker = mNetTrackers[mActiveDefaultNetwork];
+ if (tracker != null) {
+ return tracker.getNetworkInfo();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public NetworkInfo getActiveNetworkInfoForUid(int uid) {
+ enforceConnectivityInternalPermission();
+ return getNetworkInfo(mActiveDefaultNetwork, uid);
+ }
+
+ @Override
+ public NetworkInfo getNetworkInfo(int networkType) {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ return getNetworkInfo(networkType, uid);
+ }
+
+ private NetworkInfo getNetworkInfo(int networkType, int uid) {
+ NetworkInfo info = null;
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ info = getFilteredNetworkInfo(tracker, uid);
+ }
+ }
+ return info;
+ }
+
+ @Override
+ public NetworkInfo[] getAllNetworkInfo() {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ final ArrayList<NetworkInfo> result = Lists.newArrayList();
+ synchronized (mRulesLock) {
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ result.add(getFilteredNetworkInfo(tracker, uid));
+ }
+ }
+ }
+ return result.toArray(new NetworkInfo[result.size()]);
+ }
+
+ @Override
+ public boolean isNetworkSupported(int networkType) {
+ enforceAccessPermission();
+ return (isNetworkTypeValid(networkType) && (mNetTrackers[networkType] != null));
+ }
+
+ /**
+ * 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
+ */
+ @Override
+ public LinkProperties getActiveLinkProperties() {
+ return getLinkProperties(mActiveDefaultNetwork);
+ }
+
+ @Override
+ public LinkProperties getLinkProperties(int networkType) {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ return tracker.getLinkProperties();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public NetworkState[] getAllNetworkState() {
+ enforceAccessPermission();
+ final int uid = Binder.getCallingUid();
+ final ArrayList<NetworkState> result = Lists.newArrayList();
+ synchronized (mRulesLock) {
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ final NetworkInfo info = getFilteredNetworkInfo(tracker, uid);
+ result.add(new NetworkState(
+ info, tracker.getLinkProperties(), tracker.getLinkCapabilities()));
+ }
+ }
+ }
+ return result.toArray(new NetworkState[result.size()]);
+ }
+
+ private NetworkState getNetworkStateUnchecked(int networkType) {
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ return new NetworkState(tracker.getNetworkInfo(), tracker.getLinkProperties(),
+ tracker.getLinkCapabilities());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public NetworkQuotaInfo getActiveNetworkQuotaInfo() {
+ enforceAccessPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ final NetworkState state = getNetworkStateUnchecked(mActiveDefaultNetwork);
+ if (state != null) {
+ try {
+ return mPolicyManager.getNetworkQuotaInfo(state);
+ } catch (RemoteException e) {
+ }
+ }
+ return null;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public boolean isActiveNetworkMetered() {
+ enforceAccessPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ return isNetworkMeteredUnchecked(mActiveDefaultNetwork);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private boolean isNetworkMeteredUnchecked(int networkType) {
+ final NetworkState state = getNetworkStateUnchecked(networkType);
+ if (state != null) {
+ try {
+ return mPolicyManager.isNetworkMetered(state);
+ } catch (RemoteException e) {
+ }
+ }
+ return false;
+ }
+
+ 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);
+ }
+
+ private INetworkManagementEventObserver mDataActivityObserver = new BaseNetworkObserver() {
+ @Override
+ public void interfaceClassDataActivityChanged(String label, boolean active) {
+ int deviceType = Integer.parseInt(label);
+ sendDataActivityBroadcast(deviceType, active);
+ }
+ };
+
+ /**
+ * 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() {
+ if (VDBG) {
+ log("ConnectivityService FeatureUser expire(" +
+ mNetworkType + ", " + mFeature + ", " + mBinder +"), created " +
+ (System.currentTimeMillis() - mCreateTime) + " mSec ago");
+ }
+ stopUsingNetworkFeature(this, false);
+ }
+
+ public boolean isSameUser(FeatureUser u) {
+ if (u == null) return false;
+
+ return isSameUser(u.mPid, u.mUid, u.mNetworkType, u.mFeature);
+ }
+
+ public boolean isSameUser(int pid, int uid, int networkType, String feature) {
+ if ((mPid == pid) && (mUid == uid) && (mNetworkType == networkType) &&
+ TextUtils.equals(mFeature, feature)) {
+ return true;
+ }
+ return 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) {
+ long startTime = 0;
+ if (DBG) {
+ startTime = SystemClock.elapsedRealtime();
+ }
+ if (VDBG) {
+ log("startUsingNetworkFeature for net " + networkType + ": " + feature + ", uid="
+ + Binder.getCallingUid());
+ }
+ enforceChangePermission();
+ try {
+ if (!ConnectivityManager.isNetworkTypeValid(networkType) ||
+ mNetConfigs[networkType] == null) {
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ FeatureUser f = new FeatureUser(networkType, feature, binder);
+
+ // TODO - move this into individual networktrackers
+ int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
+
+ if (mLockdownEnabled) {
+ // Since carrier APNs usually aren't available from VPN
+ // endpoint, mark them as unavailable.
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ }
+
+ if (mProtectedNetworks.contains(usedNetworkType)) {
+ enforceConnectivityInternalPermission();
+ }
+
+ // if UID is restricted, don't allow them to bring up metered APNs
+ final boolean networkMetered = isNetworkMeteredUnchecked(usedNetworkType);
+ final int uidRules;
+ synchronized (mRulesLock) {
+ uidRules = mUidRules.get(Binder.getCallingUid(), RULE_ALLOW_ALL);
+ }
+ if (networkMetered && (uidRules & RULE_REJECT_METERED) != 0) {
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+
+ NetworkStateTracker network = mNetTrackers[usedNetworkType];
+ if (network != null) {
+ Integer currentPid = new Integer(getCallingPid());
+ if (usedNetworkType != networkType) {
+ NetworkInfo ni = network.getNetworkInfo();
+
+ if (ni.isAvailable() == false) {
+ if (!TextUtils.equals(feature,Phone.FEATURE_ENABLE_DUN_ALWAYS)) {
+ if (DBG) log("special network not available ni=" + ni.getTypeName());
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ } else {
+ // else make the attempt anyway - probably giving REQUEST_STARTED below
+ if (DBG) {
+ log("special network not available, but try anyway ni=" +
+ ni.getTypeName());
+ }
+ }
+ }
+
+ int restoreTimer = getRestoreDefaultNetworkDelay(usedNetworkType);
+
+ synchronized(this) {
+ boolean addToList = true;
+ if (restoreTimer < 0) {
+ // In case there is no timer is specified for the feature,
+ // make sure we don't add duplicate entry with the same request.
+ for (FeatureUser u : mFeatureUsers) {
+ if (u.isSameUser(f)) {
+ // Duplicate user is found. Do not add.
+ addToList = false;
+ break;
+ }
+ }
+ }
+
+ if (addToList) mFeatureUsers.add(f);
+ if (!mNetRequestersPids[usedNetworkType].contains(currentPid)) {
+ // this gets used for per-pid dns when connected
+ mNetRequestersPids[usedNetworkType].add(currentPid);
+ }
+ }
+
+ if (restoreTimer >= 0) {
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_RESTORE_DEFAULT_NETWORK, f), restoreTimer);
+ }
+
+ if ((ni.isConnectedOrConnecting() == true) &&
+ !network.isTeardownRequested()) {
+ if (ni.isConnected() == true) {
+ final long token = Binder.clearCallingIdentity();
+ try {
+ // add the pid-specific dns
+ handleDnsConfigurationChange(usedNetworkType);
+ if (VDBG) log("special network already active");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return PhoneConstants.APN_ALREADY_ACTIVE;
+ }
+ if (VDBG) log("special network already connecting");
+ return PhoneConstants.APN_REQUEST_STARTED;
+ }
+
+ // check if the radio in play can make another contact
+ // assume if cannot for now
+
+ if (DBG) {
+ log("startUsingNetworkFeature reconnecting to " + networkType + ": " +
+ feature);
+ }
+ if (network.reconnect()) {
+ if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_STARTED");
+ return PhoneConstants.APN_REQUEST_STARTED;
+ } else {
+ if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_FAILED");
+ return PhoneConstants.APN_REQUEST_FAILED;
+ }
+ } 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);
+ }
+ }
+ if (DBG) log("startUsingNetworkFeature X: return -1 unsupported feature.");
+ return -1;
+ }
+ }
+ if (DBG) log("startUsingNetworkFeature X: return APN_TYPE_NOT_AVAILABLE");
+ return PhoneConstants.APN_TYPE_NOT_AVAILABLE;
+ } finally {
+ if (DBG) {
+ final long execTime = SystemClock.elapsedRealtime() - startTime;
+ if (execTime > 250) {
+ loge("startUsingNetworkFeature took too long: " + execTime + "ms");
+ } else {
+ if (VDBG) log("startUsingNetworkFeature took " + execTime + "ms");
+ }
+ }
+ }
+ }
+
+ // 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 (FeatureUser x : mFeatureUsers) {
+ if (x.isSameUser(pid, uid, networkType, feature)) {
+ u = x;
+ found = true;
+ break;
+ }
+ }
+ }
+ if (found && u != null) {
+ if (VDBG) log("stopUsingNetworkFeature: X");
+ // stop regardless of how many other time this proc had called start
+ return stopUsingNetworkFeature(u, true);
+ } else {
+ // none found!
+ if (VDBG) log("stopUsingNetworkFeature: X not a live request, ignoring");
+ 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 (VDBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature);
+ }
+
+ if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
+ if (DBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+ ", net is invalid");
+ }
+ 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 (VDBG) {
+ log("stopUsingNetworkFeature: this process has no outstanding requests" +
+ ", ignoring");
+ }
+ 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 (FeatureUser x : mFeatureUsers) {
+ if (x.isSameUser(u)) {
+ if (VDBG) log("stopUsingNetworkFeature: dup is found, ignoring");
+ return 1;
+ }
+ }
+ }
+
+ // TODO - move to individual network trackers
+ int usedNetworkType = convertFeatureToNetworkType(networkType, feature);
+
+ tracker = mNetTrackers[usedNetworkType];
+ if (tracker == null) {
+ if (DBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+ " no known tracker for used net type " + usedNetworkType);
+ }
+ return -1;
+ }
+ if (usedNetworkType != networkType) {
+ Integer currentPid = new Integer(pid);
+ mNetRequestersPids[usedNetworkType].remove(currentPid);
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ reassessPidDns(pid, true);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ flushVmDnsCache();
+ if (mNetRequestersPids[usedNetworkType].size() != 0) {
+ if (VDBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+ " others still using it");
+ }
+ return 1;
+ }
+ callTeardown = true;
+ } else {
+ if (DBG) {
+ log("stopUsingNetworkFeature: net " + networkType + ": " + feature +
+ " not a known feature - dropping");
+ }
+ }
+ }
+
+ if (callTeardown) {
+ if (DBG) {
+ log("stopUsingNetworkFeature: teardown net " + networkType + ": " + feature);
+ }
+ 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 (mProtectedNetworks.contains(networkType)) {
+ enforceConnectivityInternalPermission();
+ }
+
+ if (!ConnectivityManager.isNetworkTypeValid(networkType)) {
+ if (DBG) log("requestRouteToHostAddress on invalid network: " + networkType);
+ return false;
+ }
+ NetworkStateTracker tracker = mNetTrackers[networkType];
+ DetailedState netState = DetailedState.DISCONNECTED;
+ if (tracker != null) {
+ netState = tracker.getNetworkInfo().getDetailedState();
+ }
+
+ if ((netState != DetailedState.CONNECTED &&
+ netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
+ tracker.isTeardownRequested()) {
+ if (VDBG) {
+ log("requestRouteToHostAddress on down network "
+ + "(" + networkType + ") - dropped"
+ + " tracker=" + tracker
+ + " netState=" + netState
+ + " isTeardownRequested="
+ + ((tracker != null) ? tracker.isTeardownRequested() : "tracker:null"));
+ }
+ return false;
+ }
+ final long token = Binder.clearCallingIdentity();
+ try {
+ InetAddress addr = InetAddress.getByAddress(hostAddress);
+ LinkProperties lp = tracker.getLinkProperties();
+ boolean ok = addRouteToAddress(lp, addr, EXEMPT);
+ if (DBG) log("requestRouteToHostAddress ok=" + ok);
+ return ok;
+ } catch (UnknownHostException e) {
+ if (DBG) log("requestRouteToHostAddress got " + e.toString());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ if (DBG) log("requestRouteToHostAddress X bottom return false");
+ return false;
+ }
+
+ private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable,
+ boolean exempt) {
+ return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt);
+ }
+
+ private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
+ return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT);
+ }
+
+ private boolean addRouteToAddress(LinkProperties lp, InetAddress addr, boolean exempt) {
+ return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt);
+ }
+
+ private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) {
+ return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE, UNEXEMPT);
+ }
+
+ private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
+ boolean toDefaultTable, boolean exempt) {
+ RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
+ if (bestRoute == null) {
+ bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName());
+ } else {
+ String iface = bestRoute.getInterface();
+ if (bestRoute.getGateway().equals(addr)) {
+ // if there is no better route, add the implied hostroute for our gateway
+ bestRoute = RouteInfo.makeHostRoute(addr, iface);
+ } else {
+ // if we will connect to this through another route, add a direct route
+ // to it's gateway
+ bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
+ }
+ }
+ return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt);
+ }
+
+ private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
+ boolean toDefaultTable, boolean exempt) {
+ if ((lp == null) || (r == null)) {
+ if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
+ return false;
+ }
+
+ if (cycleCount > MAX_HOSTROUTE_CYCLE_COUNT) {
+ loge("Error modifying route - too much recursion");
+ return false;
+ }
+
+ String ifaceName = r.getInterface();
+ if(ifaceName == null) {
+ loge("Error modifying route - no interface name");
+ return false;
+ }
+ if (r.hasGateway()) {
+ RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), r.getGateway());
+ if (bestRoute != null) {
+ if (bestRoute.getGateway().equals(r.getGateway())) {
+ // if there is no better route, add the implied hostroute for our gateway
+ bestRoute = RouteInfo.makeHostRoute(r.getGateway(), ifaceName);
+ } else {
+ // if we will connect to our gateway through another route, add a direct
+ // route to it's gateway
+ bestRoute = RouteInfo.makeHostRoute(r.getGateway(),
+ bestRoute.getGateway(),
+ ifaceName);
+ }
+ modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt);
+ }
+ }
+ if (doAdd) {
+ if (VDBG) log("Adding " + r + " for interface " + ifaceName);
+ try {
+ if (toDefaultTable) {
+ synchronized (mRoutesLock) {
+ // only track default table - only one apps can effect
+ mAddedRoutes.add(r);
+ mNetd.addRoute(ifaceName, r);
+ if (exempt) {
+ LinkAddress dest = r.getDestination();
+ if (!mExemptAddresses.contains(dest)) {
+ mNetd.setHostExemption(dest);
+ mExemptAddresses.add(dest);
+ }
+ }
+ }
+ } else {
+ mNetd.addSecondaryRoute(ifaceName, r);
+ }
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (DBG) loge("Exception trying to add a route: " + e);
+ return false;
+ }
+ } else {
+ // if we remove this one and there are no more like it, then refcount==0 and
+ // we can remove it from the table
+ if (toDefaultTable) {
+ synchronized (mRoutesLock) {
+ mAddedRoutes.remove(r);
+ if (mAddedRoutes.contains(r) == false) {
+ if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+ try {
+ mNetd.removeRoute(ifaceName, r);
+ LinkAddress dest = r.getDestination();
+ if (mExemptAddresses.contains(dest)) {
+ mNetd.clearHostExemption(dest);
+ mExemptAddresses.remove(dest);
+ }
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (VDBG) loge("Exception trying to remove a route: " + e);
+ return false;
+ }
+ } else {
+ if (VDBG) log("not removing " + r + " as it's still in use");
+ }
+ }
+ } else {
+ if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+ try {
+ mNetd.removeSecondaryRoute(ifaceName, r);
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (VDBG) loge("Exception trying to remove a route: " + e);
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @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.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.MOBILE_DATA, 1) == 1;
+ if (VDBG) log("getMobileDataEnabled returning " + retVal);
+ return retVal;
+ }
+
+ public void setDataDependency(int networkType, boolean met) {
+ enforceConnectivityInternalPermission();
+
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_SET_DEPENDENCY_MET,
+ (met ? ENABLED : DISABLED), networkType));
+ }
+
+ private void handleSetDependencyMet(int networkType, boolean met) {
+ if (mNetTrackers[networkType] != null) {
+ if (DBG) {
+ log("handleSetDependencyMet(" + networkType + ", " + met + ")");
+ }
+ mNetTrackers[networkType].setDependencyMet(met);
+ }
+ }
+
+ private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+ @Override
+ public void onUidRulesChanged(int uid, int uidRules) {
+ // caller is NPMS, since we only register with them
+ if (LOGD_RULES) {
+ log("onUidRulesChanged(uid=" + uid + ", uidRules=" + uidRules + ")");
+ }
+
+ synchronized (mRulesLock) {
+ // skip update when we've already applied rules
+ final int oldRules = mUidRules.get(uid, RULE_ALLOW_ALL);
+ if (oldRules == uidRules) return;
+
+ mUidRules.put(uid, uidRules);
+ }
+
+ // TODO: notify UID when it has requested targeted updates
+ }
+
+ @Override
+ public void onMeteredIfacesChanged(String[] meteredIfaces) {
+ // caller is NPMS, since we only register with them
+ if (LOGD_RULES) {
+ log("onMeteredIfacesChanged(ifaces=" + Arrays.toString(meteredIfaces) + ")");
+ }
+
+ synchronized (mRulesLock) {
+ mMeteredIfaces.clear();
+ for (String iface : meteredIfaces) {
+ mMeteredIfaces.add(iface);
+ }
+ }
+ }
+
+ @Override
+ public void onRestrictBackgroundChanged(boolean restrictBackground) {
+ // caller is NPMS, since we only register with them
+ if (LOGD_RULES) {
+ log("onRestrictBackgroundChanged(restrictBackground=" + restrictBackground + ")");
+ }
+
+ // kick off connectivity change broadcast for active network, since
+ // global background policy change is radical.
+ final int networkType = mActiveDefaultNetwork;
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ final NetworkInfo info = tracker.getNetworkInfo();
+ if (info != null && info.isConnected()) {
+ sendConnectedBroadcast(info);
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * @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 (VDBG) {
+ log(mNetTrackers[ConnectivityManager.TYPE_MOBILE].toString() + enabled);
+ }
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE].setUserDataEnable(enabled);
+ }
+ if (mNetTrackers[ConnectivityManager.TYPE_WIMAX] != null) {
+ if (VDBG) {
+ log(mNetTrackers[ConnectivityManager.TYPE_WIMAX].toString() + enabled);
+ }
+ mNetTrackers[ConnectivityManager.TYPE_WIMAX].setUserDataEnable(enabled);
+ }
+ }
+
+ @Override
+ public void setPolicyDataEnable(int networkType, boolean enabled) {
+ // only someone like NPMS should only be calling us
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, TAG);
+
+ mHandler.sendMessage(mHandler.obtainMessage(
+ EVENT_SET_POLICY_DATA_ENABLE, networkType, (enabled ? ENABLED : DISABLED)));
+ }
+
+ private void handleSetPolicyDataEnable(int networkType, boolean enabled) {
+ if (isNetworkTypeValid(networkType)) {
+ final NetworkStateTracker tracker = mNetTrackers[networkType];
+ if (tracker != null) {
+ tracker.setPolicyDataEnable(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");
+ }
+
+ private void enforceMarkNetworkSocketPermission() {
+ //Media server special case
+ if (Binder.getCallingUid() == Process.MEDIA_UID) {
+ return;
+ }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.MARK_NETWORK_SOCKET,
+ "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);
+
+ // Remove idletimer previously setup in {@code handleConnect}
+ removeDataActivityTracking(prevNetType);
+
+ /*
+ * 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 (!mNetConfigs[prevNetType].isDefault()) {
+ List<Integer> pids = mNetRequestersPids[prevNetType];
+ for (Integer pid : pids) {
+ // 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, new NetworkInfo(info));
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+ 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 (mNetConfigs[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);
+
+ // Reset interface if no other connections are using the same interface
+ boolean doReset = true;
+ LinkProperties linkProperties = mNetTrackers[prevNetType].getLinkProperties();
+ if (linkProperties != null) {
+ String oldIface = linkProperties.getInterfaceName();
+ if (TextUtils.isEmpty(oldIface) == false) {
+ for (NetworkStateTracker networkStateTracker : mNetTrackers) {
+ if (networkStateTracker == null) continue;
+ NetworkInfo networkInfo = networkStateTracker.getNetworkInfo();
+ if (networkInfo.isConnected() && networkInfo.getType() != prevNetType) {
+ LinkProperties l = networkStateTracker.getLinkProperties();
+ if (l == null) continue;
+ if (oldIface.equals(l.getInterfaceName())) {
+ doReset = false;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // do this before we broadcast the change
+ handleConnectivityChange(prevNetType, doReset);
+
+ final Intent immediateIntent = new Intent(intent);
+ immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
+ sendStickyBroadcast(immediateIntent);
+ sendStickyBroadcastDelayed(intent, getConnectivityChangeDelay());
+ /*
+ * If the failover network is already connected, then immediately send
+ * out a followup broadcast indicating successful failover
+ */
+ if (mActiveDefaultNetwork != -1) {
+ sendConnectedBroadcastDelayed(mNetTrackers[mActiveDefaultNetwork].getNetworkInfo(),
+ getConnectivityChangeDelay());
+ }
+ }
+
+ 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 (mNetConfigs[prevNetType].isDefault()) {
+ if (mActiveDefaultNetwork == prevNetType) {
+ if (DBG) {
+ log("tryFailover: set mActiveDefaultNetwork=-1, prevNetType=" + 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 = mNetConfigs[mActiveDefaultNetwork].mPriority;
+// }
+
+ for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) {
+ if (checkType == prevNetType) continue;
+ if (mNetConfigs[checkType] == null) continue;
+ if (!mNetConfigs[checkType].isDefault()) continue;
+ if (mNetTrackers[checkType] == null) 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 >= mNetConfigs[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());
+ }
+ }
+ }
+
+ public void sendConnectedBroadcast(NetworkInfo info) {
+ enforceConnectivityInternalPermission();
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
+ }
+
+ private void sendConnectedBroadcastDelayed(NetworkInfo info, int delayMs) {
+ sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
+ sendGeneralBroadcastDelayed(info, CONNECTIVITY_ACTION, delayMs);
+ }
+
+ private void sendInetConditionBroadcast(NetworkInfo info) {
+ sendGeneralBroadcast(info, ConnectivityManager.INET_CONDITION_ACTION);
+ }
+
+ private Intent makeGeneralIntent(NetworkInfo info, String bcastType) {
+ if (mLockdownTracker != null) {
+ info = mLockdownTracker.augmentNetworkInfo(info);
+ }
+
+ Intent intent = new Intent(bcastType);
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_INFO, new NetworkInfo(info));
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+ 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);
+ return intent;
+ }
+
+ private void sendGeneralBroadcast(NetworkInfo info, String bcastType) {
+ sendStickyBroadcast(makeGeneralIntent(info, bcastType));
+ }
+
+ private void sendGeneralBroadcastDelayed(NetworkInfo info, String bcastType, int delayMs) {
+ sendStickyBroadcastDelayed(makeGeneralIntent(info, bcastType), delayMs);
+ }
+
+ private void sendDataActivityBroadcast(int deviceType, boolean active) {
+ Intent intent = new Intent(ConnectivityManager.ACTION_DATA_ACTIVITY_CHANGE);
+ intent.putExtra(ConnectivityManager.EXTRA_DEVICE_TYPE, deviceType);
+ intent.putExtra(ConnectivityManager.EXTRA_IS_ACTIVE, active);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendOrderedBroadcastAsUser(intent, UserHandle.ALL,
+ RECEIVE_DATA_ACTIVITY_CHANGE, null, null, 0, null, null);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ /**
+ * 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, new NetworkInfo(info));
+ intent.putExtra(ConnectivityManager.EXTRA_NETWORK_TYPE, info.getType());
+ 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 (mNetConfigs[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);
+
+ final Intent immediateIntent = new Intent(intent);
+ immediateIntent.setAction(CONNECTIVITY_ACTION_IMMEDIATE);
+ sendStickyBroadcast(immediateIntent);
+ 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);
+ if (VDBG) {
+ log("sendStickyBroadcast: action=" + intent.getAction());
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+ }
+
+ private void sendStickyBroadcastDelayed(Intent intent, int delayMs) {
+ if (delayMs <= 0) {
+ sendStickyBroadcast(intent);
+ } else {
+ if (VDBG) {
+ log("sendStickyBroadcastDelayed: delayMs=" + delayMs + ", action="
+ + intent.getAction());
+ }
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(
+ EVENT_SEND_STICKY_BROADCAST_INTENT, intent), delayMs);
+ }
+ }
+
+ void systemReady() {
+ mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this);
+ loadGlobalProxy();
+
+ synchronized(this) {
+ mSystemReady = true;
+ if (mInitialBroadcast != null) {
+ mContext.sendStickyBroadcastAsUser(mInitialBroadcast, UserHandle.ALL);
+ mInitialBroadcast = null;
+ }
+ }
+ // load the global proxy at startup
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_APPLY_GLOBAL_HTTP_PROXY));
+
+ // Try bringing up tracker, but if KeyStore isn't ready yet, wait
+ // for user to unlock device.
+ if (!updateLockdownVpn()) {
+ final IntentFilter filter = new IntentFilter(Intent.ACTION_USER_PRESENT);
+ mContext.registerReceiver(mUserPresentReceiver, filter);
+ }
+ }
+
+ private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ // Try creating lockdown tracker, since user present usually means
+ // unlocked keystore.
+ if (updateLockdownVpn()) {
+ mContext.unregisterReceiver(this);
+ }
+ }
+ };
+
+ private boolean isNewNetTypePreferredOverCurrentNetType(int type) {
+ if (((type != mNetworkPreference)
+ && (mNetConfigs[mActiveDefaultNetwork].priority > mNetConfigs[type].priority))
+ || (mNetworkPreference == mActiveDefaultNetwork)) {
+ return false;
+ }
+ return true;
+ }
+
+ private void handleConnect(NetworkInfo info) {
+ final int newNetType = info.getType();
+
+ setupDataActivityTracking(newNetType);
+
+ // snapshot isFailover, because sendConnectedBroadcast() resets it
+ boolean isFailover = info.isFailover();
+ final NetworkStateTracker thisNet = mNetTrackers[newNetType];
+ final String thisIface = thisNet.getLinkProperties().getInterfaceName();
+
+ if (VDBG) {
+ log("handleConnect: E newNetType=" + newNetType + " thisIface=" + thisIface
+ + " isFailover" + isFailover);
+ }
+
+ // if this is a default net and other default is running
+ // kill the one not preferred
+ if (mNetConfigs[newNetType].isDefault()) {
+ if (mActiveDefaultNetwork != -1 && mActiveDefaultNetwork != newNetType) {
+ if (isNewNetTypePreferredOverCurrentNetType(newNetType)) {
+ // 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");
+ teardown(thisNet);
+ return;
+ }
+ } else {
+ // don't accept this one
+ if (VDBG) {
+ log("Not broadcasting CONNECT_ACTION " +
+ "to torn down network " + info.getTypeName());
+ }
+ teardown(thisNet);
+ 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 = newNetType;
+ // 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);
+ updateMtuSizeSettings(thisNet);
+ handleConnectivityChange(newNetType, false);
+ sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay());
+
+ // notify battery stats service about this network
+ if (thisIface != null) {
+ try {
+ BatteryStatsService.getService().noteNetworkInterfaceType(thisIface, newNetType);
+ } catch (RemoteException e) {
+ // ignored; service lives in system_server
+ }
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
+ enforceConnectivityInternalPermission();
+ if (DBG) log("captivePortalCheckCompleted: ni=" + info + " captive=" + isCaptivePortal);
+ mNetTrackers[info.getType()].captivePortalCheckCompleted(isCaptivePortal);
+ }
+
+ /**
+ * Setup data activity tracking for the given network interface.
+ *
+ * Every {@code setupDataActivityTracking} should be paired with a
+ * {@link removeDataActivityTracking} for cleanup.
+ */
+ private void setupDataActivityTracking(int type) {
+ final NetworkStateTracker thisNet = mNetTrackers[type];
+ final String iface = thisNet.getLinkProperties().getInterfaceName();
+
+ final int timeout;
+
+ if (ConnectivityManager.isNetworkTypeMobile(type)) {
+ timeout = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DATA_ACTIVITY_TIMEOUT_MOBILE,
+ 0);
+ // Canonicalize mobile network type
+ type = ConnectivityManager.TYPE_MOBILE;
+ } else if (ConnectivityManager.TYPE_WIFI == type) {
+ timeout = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DATA_ACTIVITY_TIMEOUT_WIFI,
+ 0);
+ } else {
+ // do not track any other networks
+ timeout = 0;
+ }
+
+ if (timeout > 0 && iface != null) {
+ try {
+ mNetd.addIdleTimer(iface, timeout, Integer.toString(type));
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * Remove data activity tracking when network disconnects.
+ */
+ private void removeDataActivityTracking(int type) {
+ final NetworkStateTracker net = mNetTrackers[type];
+ final String iface = net.getLinkProperties().getInterfaceName();
+
+ if (iface != null && (ConnectivityManager.isNetworkTypeMobile(type) ||
+ ConnectivityManager.TYPE_WIFI == type)) {
+ try {
+ // the call fails silently if no idletimer setup for this interface
+ mNetd.removeIdleTimer(iface);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ /**
+ * 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, boolean doReset) {
+ int resetMask = doReset ? NetworkUtils.RESET_ALL_ADDRESSES : 0;
+ boolean exempt = ConnectivityManager.isNetworkTypeExempt(netType);
+ if (VDBG) {
+ log("handleConnectivityChange: netType=" + netType + " doReset=" + doReset
+ + " resetMask=" + resetMask);
+ }
+
+ /*
+ * If a non-default network is enabled, add the host routes that
+ * will allow it's DNS servers to be accessed.
+ */
+ handleDnsConfigurationChange(netType);
+
+ LinkProperties curLp = mCurrentLinkProperties[netType];
+ LinkProperties newLp = null;
+
+ if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
+ newLp = mNetTrackers[netType].getLinkProperties();
+ if (VDBG) {
+ log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
+ " doReset=" + doReset + " resetMask=" + resetMask +
+ "\n curLp=" + curLp +
+ "\n newLp=" + newLp);
+ }
+
+ if (curLp != null) {
+ if (curLp.isIdenticalInterfaceName(newLp)) {
+ CompareResult<LinkAddress> car = curLp.compareAddresses(newLp);
+ if ((car.removed.size() != 0) || (car.added.size() != 0)) {
+ for (LinkAddress linkAddr : car.removed) {
+ if (linkAddr.getAddress() instanceof Inet4Address) {
+ resetMask |= NetworkUtils.RESET_IPV4_ADDRESSES;
+ }
+ if (linkAddr.getAddress() instanceof Inet6Address) {
+ resetMask |= NetworkUtils.RESET_IPV6_ADDRESSES;
+ }
+ }
+ if (DBG) {
+ log("handleConnectivityChange: addresses changed" +
+ " linkProperty[" + netType + "]:" + " resetMask=" + resetMask +
+ "\n car=" + car);
+ }
+ } else {
+ if (DBG) {
+ log("handleConnectivityChange: address are the same reset per doReset" +
+ " linkProperty[" + netType + "]:" +
+ " resetMask=" + resetMask);
+ }
+ }
+ } else {
+ resetMask = NetworkUtils.RESET_ALL_ADDRESSES;
+ if (DBG) {
+ log("handleConnectivityChange: interface not not equivalent reset both" +
+ " linkProperty[" + netType + "]:" +
+ " resetMask=" + resetMask);
+ }
+ }
+ }
+ if (mNetConfigs[netType].isDefault()) {
+ handleApplyDefaultProxy(newLp.getHttpProxy());
+ }
+ } else {
+ if (VDBG) {
+ log("handleConnectivityChange: changed linkProperty[" + netType + "]:" +
+ " doReset=" + doReset + " resetMask=" + resetMask +
+ "\n curLp=" + curLp +
+ "\n newLp= null");
+ }
+ }
+ mCurrentLinkProperties[netType] = newLp;
+ boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault(), exempt);
+
+ if (resetMask != 0 || resetDns) {
+ if (VDBG) log("handleConnectivityChange: resetting");
+ if (curLp != null) {
+ if (VDBG) log("handleConnectivityChange: resetting curLp=" + curLp);
+ for (String iface : curLp.getAllInterfaceNames()) {
+ if (TextUtils.isEmpty(iface) == false) {
+ if (resetMask != 0) {
+ if (DBG) log("resetConnections(" + iface + ", " + resetMask + ")");
+ NetworkUtils.resetConnections(iface, resetMask);
+
+ // Tell VPN the interface is down. It is a temporary
+ // but effective fix to make VPN aware of the change.
+ if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) {
+ synchronized(mVpns) {
+ for (int i = 0; i < mVpns.size(); i++) {
+ mVpns.valueAt(i).interfaceStatusChanged(iface, false);
+ }
+ }
+ }
+ }
+ if (resetDns) {
+ flushVmDnsCache();
+ if (VDBG) log("resetting DNS cache for " + iface);
+ try {
+ mNetd.flushInterfaceDnsCache(iface);
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (DBG) loge("Exception resetting dns cache: " + e);
+ }
+ }
+ } else {
+ loge("Can't reset connection for type "+netType);
+ }
+ }
+ }
+ }
+
+ // Update 464xlat state.
+ NetworkStateTracker tracker = mNetTrackers[netType];
+ if (mClat.requiresClat(netType, tracker)) {
+
+ // If the connection was previously using clat, but is not using it now, stop the clat
+ // daemon. Normally, this happens automatically when the connection disconnects, but if
+ // the disconnect is not reported, or if the connection's LinkProperties changed for
+ // some other reason (e.g., handoff changes the IP addresses on the link), it would
+ // still be running. If it's not running, then stopping it is a no-op.
+ if (Nat464Xlat.isRunningClat(curLp) && !Nat464Xlat.isRunningClat(newLp)) {
+ mClat.stopClat();
+ }
+ // If the link requires clat to be running, then start the daemon now.
+ if (mNetTrackers[netType].getNetworkInfo().isConnected()) {
+ mClat.startClat(tracker);
+ } else {
+ mClat.stopClat();
+ }
+ }
+
+ // TODO: Temporary notifying upstread change to Tethering.
+ // @see bug/4455071
+ /** Notify TetheringService if interface name has been changed. */
+ if (TextUtils.equals(mNetTrackers[netType].getNetworkInfo().getReason(),
+ PhoneConstants.REASON_LINK_PROPERTIES_CHANGED)) {
+ if (isTetheringSupported()) {
+ mTethering.handleTetherIfaceChange();
+ }
+ }
+ }
+
+ /**
+ * Add and remove routes using the old properties (null if not previously connected),
+ * new properties (null if becoming disconnected). May even be double null, which
+ * is a noop.
+ * Uses isLinkDefault to determine if default routes should be set or conversely if
+ * host routes should be set to the dns servers
+ * returns a boolean indicating the routes changed
+ */
+ private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp,
+ boolean isLinkDefault, boolean exempt) {
+ Collection<RouteInfo> routesToAdd = null;
+ CompareResult<InetAddress> dnsDiff = new CompareResult<InetAddress>();
+ CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>();
+ if (curLp != null) {
+ // check for the delta between the current set and the new
+ routeDiff = curLp.compareAllRoutes(newLp);
+ dnsDiff = curLp.compareDnses(newLp);
+ } else if (newLp != null) {
+ routeDiff.added = newLp.getAllRoutes();
+ dnsDiff.added = newLp.getDnses();
+ }
+
+ boolean routesChanged = (routeDiff.removed.size() != 0 || routeDiff.added.size() != 0);
+
+ for (RouteInfo r : routeDiff.removed) {
+ if (isLinkDefault || ! r.isDefaultRoute()) {
+ if (VDBG) log("updateRoutes: default remove route r=" + r);
+ removeRoute(curLp, r, TO_DEFAULT_TABLE);
+ }
+ if (isLinkDefault == false) {
+ // remove from a secondary route table
+ removeRoute(curLp, r, TO_SECONDARY_TABLE);
+ }
+ }
+
+ if (!isLinkDefault) {
+ // handle DNS routes
+ if (routesChanged) {
+ // routes changed - remove all old dns entries and add new
+ if (curLp != null) {
+ for (InetAddress oldDns : curLp.getDnses()) {
+ removeRouteToAddress(curLp, oldDns);
+ }
+ }
+ if (newLp != null) {
+ for (InetAddress newDns : newLp.getDnses()) {
+ addRouteToAddress(newLp, newDns, exempt);
+ }
+ }
+ } else {
+ // no change in routes, check for change in dns themselves
+ for (InetAddress oldDns : dnsDiff.removed) {
+ removeRouteToAddress(curLp, oldDns);
+ }
+ for (InetAddress newDns : dnsDiff.added) {
+ addRouteToAddress(newLp, newDns, exempt);
+ }
+ }
+ }
+
+ for (RouteInfo r : routeDiff.added) {
+ if (isLinkDefault || ! r.isDefaultRoute()) {
+ addRoute(newLp, r, TO_DEFAULT_TABLE, exempt);
+ } else {
+ // add to a secondary route table
+ addRoute(newLp, r, TO_SECONDARY_TABLE, UNEXEMPT);
+
+ // many radios add a default route even when we don't want one.
+ // remove the default route unless somebody else has asked for it
+ String ifaceName = newLp.getInterfaceName();
+ synchronized (mRoutesLock) {
+ if (!TextUtils.isEmpty(ifaceName) && !mAddedRoutes.contains(r)) {
+ if (VDBG) log("Removing " + r + " for interface " + ifaceName);
+ try {
+ mNetd.removeRoute(ifaceName, r);
+ } catch (Exception e) {
+ // never crash - catch them all
+ if (DBG) loge("Exception trying to remove a route: " + e);
+ }
+ }
+ }
+ }
+ }
+
+ return routesChanged;
+ }
+
+ /**
+ * Reads the network specific MTU size from reources.
+ * and set it on it's iface.
+ */
+ private void updateMtuSizeSettings(NetworkStateTracker nt) {
+ final String iface = nt.getLinkProperties().getInterfaceName();
+ final int mtu = nt.getLinkProperties().getMtu();
+
+ if (mtu < 68 || mtu > 10000) {
+ loge("Unexpected mtu value: " + nt);
+ return;
+ }
+
+ try {
+ if (VDBG) log("Setting MTU size: " + iface + ", " + mtu);
+ mNetd.setMtu(iface, mtu);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception in setMtu()" + e);
+ }
+ }
+
+ /**
+ * Reads the network specific TCP buffer sizes from SystemProperties
+ * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
+ * wide use
+ */
+ private void updateNetworkSettings(NetworkStateTracker nt) {
+ String key = nt.getTcpBufferSizesPropName();
+ String bufferSizes = key == null ? null : SystemProperties.get(key);
+
+ if (TextUtils.isEmpty(bufferSizes)) {
+ if (VDBG) log(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 (VDBG) {
+ 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_";
+ FileUtils.stringToFile(prefix + "rmem_min", values[0]);
+ FileUtils.stringToFile(prefix + "rmem_def", values[1]);
+ FileUtils.stringToFile(prefix + "rmem_max", values[2]);
+ FileUtils.stringToFile(prefix + "wmem_min", values[3]);
+ FileUtils.stringToFile(prefix + "wmem_def", values[4]);
+ FileUtils.stringToFile(prefix + "wmem_max", values[5]);
+ } else {
+ loge("Invalid buffersize string: " + bufferSizes);
+ }
+ } catch (IOException e) {
+ loge("Can't set tcp buffer sizes:" + e);
+ }
+ }
+
+ /**
+ * 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 pid, boolean doBump)
+ {
+ if (VDBG) log("reassessPidDns for pid " + pid);
+ Integer myPid = new Integer(pid);
+ for(int i : mPriorityList) {
+ if (mNetConfigs[i].isDefault()) {
+ continue;
+ }
+ NetworkStateTracker nt = mNetTrackers[i];
+ if (nt.getNetworkInfo().isConnected() &&
+ !nt.isTeardownRequested()) {
+ LinkProperties p = nt.getLinkProperties();
+ if (p == null) continue;
+ if (mNetRequestersPids[i].contains(myPid)) {
+ try {
+ mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception reasseses pid dns: " + e);
+ }
+ return;
+ }
+ }
+ }
+ // nothing found - delete
+ try {
+ mNetd.clearDnsInterfaceForPid(pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception clear interface from pid: " + e);
+ }
+ }
+
+ private void flushVmDnsCache() {
+ /*
+ * 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);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ // Caller must grab mDnsLock.
+ private void updateDnsLocked(String network, String iface,
+ Collection<InetAddress> dnses, String domains, boolean defaultDns) {
+ int last = 0;
+ if (dnses.size() == 0 && mDefaultDns != null) {
+ dnses = new ArrayList();
+ dnses.add(mDefaultDns);
+ if (DBG) {
+ loge("no dns provided for " + network + " - using " + mDefaultDns.getHostAddress());
+ }
+ }
+
+ try {
+ mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains);
+ if (defaultDns) {
+ mNetd.setDefaultInterfaceForDns(iface);
+ }
+
+ for (InetAddress dns : dnses) {
+ ++last;
+ String key = "net.dns" + last;
+ String value = dns.getHostAddress();
+ SystemProperties.set(key, value);
+ }
+ for (int i = last + 1; i <= mNumDnsEntries; ++i) {
+ String key = "net.dns" + i;
+ SystemProperties.set(key, "");
+ }
+ mNumDnsEntries = last;
+ } catch (Exception e) {
+ loge("exception setting default dns interface: " + e);
+ }
+ }
+
+ 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();
+ if (mNetConfigs[netType].isDefault()) {
+ String network = nt.getNetworkInfo().getTypeName();
+ synchronized (mDnsLock) {
+ updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains(), true);
+ }
+ } else {
+ try {
+ mNetd.setDnsServersForInterface(p.getInterfaceName(),
+ NetworkUtils.makeStrings(dnses), p.getDomains());
+ } catch (Exception e) {
+ if (DBG) loge("exception setting dns servers: " + e);
+ }
+ // set per-pid dns for attached secondary nets
+ List<Integer> pids = mNetRequestersPids[netType];
+ for (Integer pid : pids) {
+ try {
+ mNetd.setDnsInterfaceForPid(p.getInterfaceName(), pid);
+ } catch (Exception e) {
+ Slog.e(TAG, "exception setting interface for pid: " + e);
+ }
+ }
+ }
+ flushVmDnsCache();
+ }
+ }
+
+ private int getRestoreDefaultNetworkDelay(int networkType) {
+ String restoreDefaultNetworkDelayStr = SystemProperties.get(
+ NETWORK_RESTORE_DELAY_PROP_NAME);
+ if(restoreDefaultNetworkDelayStr != null &&
+ restoreDefaultNetworkDelayStr.length() != 0) {
+ try {
+ return Integer.valueOf(restoreDefaultNetworkDelayStr);
+ } catch (NumberFormatException e) {
+ }
+ }
+ // if the system property isn't set, use the value for the apn type
+ int ret = RESTORE_DEFAULT_NETWORK_DELAY;
+
+ if ((networkType <= ConnectivityManager.MAX_NETWORK_TYPE) &&
+ (mNetConfigs[networkType] != null)) {
+ ret = mNetConfigs[networkType].restoreTime;
+ }
+ return ret;
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
+ 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;
+ }
+
+ // TODO: add locking to get atomic snapshot
+ pw.println();
+ for (int i = 0; i < mNetTrackers.length; i++) {
+ final NetworkStateTracker nst = mNetTrackers[i];
+ if (nst != null) {
+ pw.println("NetworkStateTracker for " + getNetworkTypeName(i) + ":");
+ pw.increaseIndent();
+ if (nst.getNetworkInfo().isConnected()) {
+ pw.println("Active network: " + nst.getNetworkInfo().
+ getTypeName());
+ }
+ pw.println(nst.getNetworkInfo());
+ pw.println(nst.getLinkProperties());
+ pw.println(nst);
+ pw.println();
+ pw.decreaseIndent();
+ }
+ }
+
+ pw.println("Network Requester Pids:");
+ pw.increaseIndent();
+ for (int net : mPriorityList) {
+ String pidString = net + ": ";
+ for (Integer pid : mNetRequestersPids[net]) {
+ pidString = pidString + pid.toString() + ", ";
+ }
+ pw.println(pidString);
+ }
+ pw.println();
+ pw.decreaseIndent();
+
+ pw.println("FeatureUsers:");
+ pw.increaseIndent();
+ for (Object requester : mFeatureUsers) {
+ pw.println(requester.toString());
+ }
+ pw.println();
+ pw.decreaseIndent();
+
+ 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:");
+ pw.increaseIndent();
+ for(int i = 0; i < mInetLog.size(); i++) {
+ pw.println(mInetLog.get(i));
+ }
+ pw.decreaseIndent();
+ }
+ }
+
+ // must be stateless - things change under us.
+ private class NetworkStateTrackerHandler extends Handler {
+ public NetworkStateTrackerHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ NetworkInfo info;
+ switch (msg.what) {
+ case NetworkStateTracker.EVENT_STATE_CHANGED: {
+ info = (NetworkInfo) msg.obj;
+ NetworkInfo.State state = info.getState();
+
+ if (VDBG || (state == NetworkInfo.State.CONNECTED) ||
+ (state == NetworkInfo.State.DISCONNECTED) ||
+ (state == NetworkInfo.State.SUSPENDED)) {
+ log("ConnectivityChange for " +
+ info.getTypeName() + ": " +
+ state + "/" + info.getDetailedState());
+ }
+
+ // Since mobile has the notion of a network/apn that can be used for
+ // provisioning we need to check every time we're connected as
+ // CaptiveProtalTracker won't detected it because DCT doesn't report it
+ // as connected as ACTION_ANY_DATA_CONNECTION_STATE_CHANGED instead its
+ // reported as ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN. Which
+ // is received by MDST and sent here as EVENT_STATE_CHANGED.
+ if (ConnectivityManager.isNetworkTypeMobile(info.getType())
+ && (0 != Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.DEVICE_PROVISIONED, 0))
+ && (((state == NetworkInfo.State.CONNECTED)
+ && (info.getType() == ConnectivityManager.TYPE_MOBILE))
+ || info.isConnectedToProvisioningNetwork())) {
+ log("ConnectivityChange checkMobileProvisioning for"
+ + " TYPE_MOBILE or ProvisioningNetwork");
+ checkMobileProvisioning(CheckMp.MAX_TIMEOUT_MS);
+ }
+
+ EventLogTags.writeConnectivityStateChanged(
+ info.getType(), info.getSubtype(), info.getDetailedState().ordinal());
+
+ if (info.getDetailedState() ==
+ NetworkInfo.DetailedState.FAILED) {
+ handleConnectionFailure(info);
+ } else if (info.isConnectedToProvisioningNetwork()) {
+ /**
+ * TODO: Create ConnectivityManager.TYPE_MOBILE_PROVISIONING
+ * for now its an in between network, its a network that
+ * is actually a default network but we don't want it to be
+ * announced as such to keep background applications from
+ * trying to use it. It turns out that some still try so we
+ * take the additional step of clearing any default routes
+ * to the link that may have incorrectly setup by the lower
+ * levels.
+ */
+ LinkProperties lp = getLinkProperties(info.getType());
+ if (DBG) {
+ log("EVENT_STATE_CHANGED: connected to provisioning network, lp=" + lp);
+ }
+
+ // Clear any default routes setup by the radio so
+ // any activity by applications trying to use this
+ // connection will fail until the provisioning network
+ // is enabled.
+ for (RouteInfo r : lp.getRoutes()) {
+ removeRoute(lp, r, TO_DEFAULT_TABLE);
+ }
+ } 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);
+ }
+ if (mLockdownTracker != null) {
+ mLockdownTracker.onNetworkInfoChanged(info);
+ }
+ break;
+ }
+ case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: {
+ info = (NetworkInfo) msg.obj;
+ // TODO: Temporary allowing network configuration
+ // change not resetting sockets.
+ // @see bug/4455071
+ handleConnectivityChange(info.getType(), false);
+ break;
+ }
+ case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: {
+ info = (NetworkInfo) msg.obj;
+ int type = info.getType();
+ updateNetworkSettings(mNetTrackers[type]);
+ break;
+ }
+ }
+ }
+ }
+
+ private class InternalHandler extends Handler {
+ public InternalHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ NetworkInfo info;
+ switch (msg.what) {
+ 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_MOBILE_DATA: {
+ boolean enabled = (msg.arg1 == ENABLED);
+ handleSetMobileData(enabled);
+ break;
+ }
+ case EVENT_APPLY_GLOBAL_HTTP_PROXY: {
+ handleDeprecatedGlobalHttpProxy();
+ break;
+ }
+ case EVENT_SET_DEPENDENCY_MET: {
+ boolean met = (msg.arg1 == ENABLED);
+ handleSetDependencyMet(msg.arg2, met);
+ break;
+ }
+ case EVENT_SEND_STICKY_BROADCAST_INTENT: {
+ Intent intent = (Intent)msg.obj;
+ sendStickyBroadcast(intent);
+ break;
+ }
+ case EVENT_SET_POLICY_DATA_ENABLE: {
+ final int networkType = msg.arg1;
+ final boolean enabled = msg.arg2 == ENABLED;
+ handleSetPolicyDataEnable(networkType, enabled);
+ break;
+ }
+ case EVENT_VPN_STATE_CHANGED: {
+ if (mLockdownTracker != null) {
+ mLockdownTracker.onVpnStateChanged((NetworkInfo) msg.obj);
+ }
+ break;
+ }
+ case EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: {
+ int tag = mEnableFailFastMobileDataTag.get();
+ if (msg.arg1 == tag) {
+ MobileDataStateTracker mobileDst =
+ (MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+ if (mobileDst != null) {
+ mobileDst.setEnableFailFastMobileData(msg.arg2);
+ }
+ } else {
+ log("EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: stale arg1:" + msg.arg1
+ + " != tag:" + tag);
+ }
+ break;
+ }
+ case EVENT_SAMPLE_INTERVAL_ELAPSED: {
+ handleNetworkSamplingTimeout();
+ break;
+ }
+ case EVENT_PROXY_HAS_CHANGED: {
+ handleApplyDefaultProxy((ProxyProperties)msg.obj);
+ break;
+ }
+ }
+ }
+ }
+
+ // javadoc from interface
+ public int tether(String iface) {
+ enforceTetherChangePermission();
+
+ if (isTetheringSupported()) {
+ return mTethering.tether(iface);
+ } else {
+ return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+ }
+ }
+
+ // javadoc from interface
+ public int untether(String iface) {
+ enforceTetherChangePermission();
+
+ if (isTetheringSupported()) {
+ return mTethering.untether(iface);
+ } else {
+ return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+ }
+ }
+
+ // javadoc from interface
+ public int getLastTetherError(String iface) {
+ enforceTetherAccessPermission();
+
+ if (isTetheringSupported()) {
+ return mTethering.getLastTetherError(iface);
+ } else {
+ return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+ }
+ }
+
+ // TODO - proper iface API for selection by property, inspection, etc
+ public String[] getTetherableUsbRegexs() {
+ enforceTetherAccessPermission();
+ if (isTetheringSupported()) {
+ return mTethering.getTetherableUsbRegexs();
+ } else {
+ return new String[0];
+ }
+ }
+
+ public String[] getTetherableWifiRegexs() {
+ enforceTetherAccessPermission();
+ if (isTetheringSupported()) {
+ return mTethering.getTetherableWifiRegexs();
+ } else {
+ return new String[0];
+ }
+ }
+
+ public String[] getTetherableBluetoothRegexs() {
+ enforceTetherAccessPermission();
+ if (isTetheringSupported()) {
+ return mTethering.getTetherableBluetoothRegexs();
+ } else {
+ return new String[0];
+ }
+ }
+
+ public int setUsbTethering(boolean enable) {
+ enforceTetherChangePermission();
+ if (isTetheringSupported()) {
+ return mTethering.setUsbTethering(enable);
+ } else {
+ return ConnectivityManager.TETHER_ERROR_UNSUPPORTED;
+ }
+ }
+
+ // 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.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.TETHER_SUPPORTED, defaultVal) != 0);
+ return tetherEnabledInSettings && ((mTethering.getTetherableUsbRegexs().length != 0 ||
+ mTethering.getTetherableWifiRegexs().length != 0 ||
+ mTethering.getTetherableBluetoothRegexs().length != 0) &&
+ mTethering.getUpstreamIfaceTypes().length != 0);
+ }
+
+ // 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 (VDBG) 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 (mActiveDefaultNetwork == -1) {
+ if (DBG) log("handleInetConditionChange: no active default network - ignore");
+ return;
+ }
+ if (mActiveDefaultNetwork != netType) {
+ if (DBG) log("handleInetConditionChange: net=" + netType +
+ " != default=" + mActiveDefaultNetwork + " - ignore");
+ return;
+ }
+ if (VDBG) {
+ log("handleInetConditionChange: net=" +
+ netType + ", condition=" + condition +
+ ",mActiveDefaultNetwork=" + mActiveDefaultNetwork);
+ }
+ mDefaultInetCondition = condition;
+ int delay;
+ if (mInetConditionChangeInFlight == false) {
+ if (VDBG) log("handleInetConditionChange: starting a change hold");
+ // setup a new hold to debounce this
+ if (mDefaultInetCondition > 50) {
+ delay = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.INET_CONDITION_DEBOUNCE_UP_DELAY, 500);
+ } else {
+ delay = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.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 (VDBG) log("handleInetConditionChange: currently in hold - not setting new end evt");
+ }
+ }
+
+ private void handleInetConditionHoldEnd(int netType, int sequence) {
+ if (DBG) {
+ log("handleInetConditionHoldEnd: net=" + netType +
+ ", condition=" + mDefaultInetCondition +
+ ", published condition=" + mDefaultInetConditionPublished);
+ }
+ mInetConditionChangeInFlight = false;
+
+ if (mActiveDefaultNetwork == -1) {
+ if (DBG) log("handleInetConditionHoldEnd: no active default network - ignoring");
+ return;
+ }
+ if (mDefaultConnectionSequence != sequence) {
+ if (DBG) log("handleInetConditionHoldEnd: event hold for obsolete network - ignoring");
+ return;
+ }
+ // TODO: Figure out why this optimization sometimes causes a
+ // change in mDefaultInetCondition to be missed and the
+ // UI to not be updated.
+ //if (mDefaultInetConditionPublished == mDefaultInetCondition) {
+ // if (DBG) log("no change in condition - aborting");
+ // return;
+ //}
+ NetworkInfo networkInfo = mNetTrackers[mActiveDefaultNetwork].getNetworkInfo();
+ if (networkInfo.isConnected() == false) {
+ if (DBG) log("handleInetConditionHoldEnd: default network not connected - ignoring");
+ return;
+ }
+ mDefaultInetConditionPublished = mDefaultInetCondition;
+ sendInetConditionBroadcast(networkInfo);
+ return;
+ }
+
+ public ProxyProperties getProxy() {
+ // this information is already available as a world read/writable jvm property
+ // so this API change wouldn't have a benifit. It also breaks the passing
+ // of proxy info to all the JVMs.
+ // enforceAccessPermission();
+ synchronized (mProxyLock) {
+ ProxyProperties ret = mGlobalProxy;
+ if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy;
+ return ret;
+ }
+ }
+
+ public void setGlobalProxy(ProxyProperties proxyProperties) {
+ enforceConnectivityInternalPermission();
+
+ synchronized (mProxyLock) {
+ 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 = "";
+ String pacFileUrl = "";
+ if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) ||
+ !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) {
+ if (!proxyProperties.isValid()) {
+ if (DBG)
+ log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+ return;
+ }
+ mGlobalProxy = new ProxyProperties(proxyProperties);
+ host = mGlobalProxy.getHost();
+ port = mGlobalProxy.getPort();
+ exclList = mGlobalProxy.getExclusionList();
+ if (proxyProperties.getPacFileUrl() != null) {
+ pacFileUrl = proxyProperties.getPacFileUrl();
+ }
+ } else {
+ mGlobalProxy = null;
+ }
+ ContentResolver res = mContext.getContentResolver();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST, host);
+ Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port);
+ Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST,
+ exclList);
+ Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC, pacFileUrl);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ if (mGlobalProxy == null) {
+ proxyProperties = mDefaultProxy;
+ }
+ sendProxyBroadcast(proxyProperties);
+ }
+
+ private void loadGlobalProxy() {
+ ContentResolver res = mContext.getContentResolver();
+ String host = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_HOST);
+ int port = Settings.Global.getInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, 0);
+ String exclList = Settings.Global.getString(res,
+ Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
+ String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC);
+ if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) {
+ ProxyProperties proxyProperties;
+ if (!TextUtils.isEmpty(pacFileUrl)) {
+ proxyProperties = new ProxyProperties(pacFileUrl);
+ } else {
+ proxyProperties = new ProxyProperties(host, port, exclList);
+ }
+ if (!proxyProperties.isValid()) {
+ if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString());
+ return;
+ }
+
+ synchronized (mProxyLock) {
+ mGlobalProxy = proxyProperties;
+ }
+ }
+ }
+
+ public ProxyProperties getGlobalProxy() {
+ // this information is already available as a world read/writable jvm property
+ // so this API change wouldn't have a benifit. It also breaks the passing
+ // of proxy info to all the JVMs.
+ // enforceAccessPermission();
+ synchronized (mProxyLock) {
+ return mGlobalProxy;
+ }
+ }
+
+ private void handleApplyDefaultProxy(ProxyProperties proxy) {
+ if (proxy != null && TextUtils.isEmpty(proxy.getHost())
+ && TextUtils.isEmpty(proxy.getPacFileUrl())) {
+ proxy = null;
+ }
+ synchronized (mProxyLock) {
+ if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
+ if (mDefaultProxy == proxy) return; // catches repeated nulls
+ if (proxy != null && !proxy.isValid()) {
+ if (DBG) log("Invalid proxy properties, ignoring: " + proxy.toString());
+ return;
+ }
+ mDefaultProxy = proxy;
+
+ if (mGlobalProxy != null) return;
+ if (!mDefaultProxyDisabled) {
+ sendProxyBroadcast(proxy);
+ }
+ }
+ }
+
+ private void handleDeprecatedGlobalHttpProxy() {
+ String proxy = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.HTTP_PROXY);
+ if (!TextUtils.isEmpty(proxy)) {
+ String data[] = proxy.split(":");
+ if (data.length == 0) {
+ return;
+ }
+
+ 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, "");
+ if (mPacManager.setCurrentProxyScriptUrl(proxy)) return;
+ if (DBG) 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);
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ 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.Global.getUriFor(
+ Settings.Global.HTTP_PROXY), false, this);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ mHandler.obtainMessage(mWhat).sendToTarget();
+ }
+ }
+
+ private static void log(String s) {
+ Slog.d(TAG, s);
+ }
+
+ private static void loge(String s) {
+ Slog.e(TAG, s);
+ }
+
+ int convertFeatureToNetworkType(int networkType, String feature) {
+ 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;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_FOTA)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_FOTA;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_IMS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_IMS;
+ } else if (TextUtils.equals(feature, Phone.FEATURE_ENABLE_CBS)) {
+ usedNetworkType = ConnectivityManager.TYPE_MOBILE_CBS;
+ } else {
+ Slog.e(TAG, "Can't match any mobile netTracker!");
+ }
+ } else if (networkType == ConnectivityManager.TYPE_WIFI) {
+ if (TextUtils.equals(feature, "p2p")) {
+ usedNetworkType = ConnectivityManager.TYPE_WIFI_P2P;
+ } else {
+ Slog.e(TAG, "Can't match any wifi netTracker!");
+ }
+ } else {
+ Slog.e(TAG, "Unexpected network type");
+ }
+ return usedNetworkType;
+ }
+
+ private static <T> T checkNotNull(T value, String message) {
+ if (value == null) {
+ throw new NullPointerException(message);
+ }
+ return value;
+ }
+
+ /**
+ * Protect a socket from VPN routing rules. This method is used by
+ * VpnBuilder and not available in ConnectivityManager. Permissions
+ * are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public boolean protectVpn(ParcelFileDescriptor socket) {
+ throwIfLockdownEnabled();
+ try {
+ int type = mActiveDefaultNetwork;
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
+ synchronized(mVpns) {
+ mVpns.get(user).protect(socket,
+ mNetTrackers[type].getLinkProperties().getInterfaceName());
+ }
+ return true;
+ }
+ } catch (Exception e) {
+ // ignore
+ } finally {
+ try {
+ socket.close();
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Prepare for a VPN application. This method is used by VpnDialogs
+ * and not available in ConnectivityManager. Permissions are checked
+ * in Vpn class.
+ * @hide
+ */
+ @Override
+ public boolean prepareVpn(String oldPackage, String newPackage) {
+ throwIfLockdownEnabled();
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ return mVpns.get(user).prepare(oldPackage, newPackage);
+ }
+ }
+
+ @Override
+ public void markSocketAsUser(ParcelFileDescriptor socket, int uid) {
+ enforceMarkNetworkSocketPermission();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ int mark = mNetd.getMarkForUid(uid);
+ // Clear the mark on the socket if no mark is needed to prevent socket reuse issues
+ if (mark == -1) {
+ mark = 0;
+ }
+ NetworkUtils.markSocket(socket.getFd(), mark);
+ } catch (RemoteException e) {
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ /**
+ * Configure a TUN interface and return its file descriptor. Parameters
+ * are encoded and opaque to this class. This method is used by VpnBuilder
+ * and not available in ConnectivityManager. Permissions are checked in
+ * Vpn class.
+ * @hide
+ */
+ @Override
+ public ParcelFileDescriptor establishVpn(VpnConfig config) {
+ throwIfLockdownEnabled();
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ return mVpns.get(user).establish(config);
+ }
+ }
+
+ /**
+ * Start legacy VPN, controlling native daemons as needed. Creates a
+ * secondary thread to perform connection work, returning quickly.
+ */
+ @Override
+ public void startLegacyVpn(VpnProfile profile) {
+ throwIfLockdownEnabled();
+ final LinkProperties egress = getActiveLinkProperties();
+ if (egress == null) {
+ throw new IllegalStateException("Missing active network connection");
+ }
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress);
+ }
+ }
+
+ /**
+ * Return the information of the ongoing legacy VPN. This method is used
+ * by VpnSettings and not available in ConnectivityManager. Permissions
+ * are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public LegacyVpnInfo getLegacyVpnInfo() {
+ throwIfLockdownEnabled();
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ return mVpns.get(user).getLegacyVpnInfo();
+ }
+ }
+
+ /**
+ * Returns the information of the ongoing VPN. This method is used by VpnDialogs and
+ * not available in ConnectivityManager.
+ * Permissions are checked in Vpn class.
+ * @hide
+ */
+ @Override
+ public VpnConfig getVpnConfig() {
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ return mVpns.get(user).getVpnConfig();
+ }
+ }
+
+ /**
+ * Callback for VPN subsystem. Currently VPN is not adapted to the service
+ * through NetworkStateTracker since it works differently. For example, it
+ * needs to override DNS servers but never takes the default routes. It
+ * relies on another data network, and it could keep existing connections
+ * alive after reconnecting, switching between networks, or even resuming
+ * from deep sleep. Calls from applications should be done synchronously
+ * to avoid race conditions. As these are all hidden APIs, refactoring can
+ * be done whenever a better abstraction is developed.
+ */
+ public class VpnCallback {
+ private VpnCallback() {
+ }
+
+ public void onStateChanged(NetworkInfo info) {
+ mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget();
+ }
+
+ public void override(String iface, List<String> dnsServers, List<String> searchDomains) {
+ if (dnsServers == null) {
+ restore();
+ return;
+ }
+
+ // Convert DNS servers into addresses.
+ List<InetAddress> addresses = new ArrayList<InetAddress>();
+ for (String address : dnsServers) {
+ // Double check the addresses and remove invalid ones.
+ try {
+ addresses.add(InetAddress.parseNumericAddress(address));
+ } catch (Exception e) {
+ // ignore
+ }
+ }
+ if (addresses.isEmpty()) {
+ restore();
+ return;
+ }
+
+ // Concatenate search domains into a string.
+ StringBuilder buffer = new StringBuilder();
+ if (searchDomains != null) {
+ for (String domain : searchDomains) {
+ buffer.append(domain).append(' ');
+ }
+ }
+ String domains = buffer.toString().trim();
+
+ // Apply DNS changes.
+ synchronized (mDnsLock) {
+ updateDnsLocked("VPN", iface, addresses, domains, false);
+ }
+
+ // Temporarily disable the default proxy (not global).
+ synchronized (mProxyLock) {
+ mDefaultProxyDisabled = true;
+ if (mGlobalProxy == null && mDefaultProxy != null) {
+ sendProxyBroadcast(null);
+ }
+ }
+
+ // TODO: support proxy per network.
+ }
+
+ public void restore() {
+ synchronized (mProxyLock) {
+ mDefaultProxyDisabled = false;
+ if (mGlobalProxy == null && mDefaultProxy != null) {
+ sendProxyBroadcast(mDefaultProxy);
+ }
+ }
+ }
+
+ public void protect(ParcelFileDescriptor socket) {
+ try {
+ final int mark = mNetd.getMarkForProtect();
+ NetworkUtils.markSocket(socket.getFd(), mark);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void setRoutes(String interfaze, List<RouteInfo> routes) {
+ for (RouteInfo route : routes) {
+ try {
+ mNetd.setMarkedForwardingRoute(interfaze, route);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
+ public void setMarkedForwarding(String interfaze) {
+ try {
+ mNetd.setMarkedForwarding(interfaze);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void clearMarkedForwarding(String interfaze) {
+ try {
+ mNetd.clearMarkedForwarding(interfaze);
+ } catch (RemoteException e) {
+ }
+ }
+
+ public void addUserForwarding(String interfaze, int uid, boolean forwardDns) {
+ int uidStart = uid * UserHandle.PER_USER_RANGE;
+ int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
+ addUidForwarding(interfaze, uidStart, uidEnd, forwardDns);
+ }
+
+ public void clearUserForwarding(String interfaze, int uid, boolean forwardDns) {
+ int uidStart = uid * UserHandle.PER_USER_RANGE;
+ int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1;
+ clearUidForwarding(interfaze, uidStart, uidEnd, forwardDns);
+ }
+
+ public void addUidForwarding(String interfaze, int uidStart, int uidEnd,
+ boolean forwardDns) {
+ try {
+ mNetd.setUidRangeRoute(interfaze,uidStart, uidEnd);
+ if (forwardDns) mNetd.setDnsInterfaceForUidRange(interfaze, uidStart, uidEnd);
+ } catch (RemoteException e) {
+ }
+
+ }
+
+ public void clearUidForwarding(String interfaze, int uidStart, int uidEnd,
+ boolean forwardDns) {
+ try {
+ mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd);
+ if (forwardDns) mNetd.clearDnsInterfaceForUidRange(uidStart, uidEnd);
+ } catch (RemoteException e) {
+ }
+
+ }
+ }
+
+ @Override
+ public boolean updateLockdownVpn() {
+ if (Binder.getCallingUid() != Process.SYSTEM_UID) {
+ Slog.w(TAG, "Lockdown VPN only available to AID_SYSTEM");
+ return false;
+ }
+
+ // Tear down existing lockdown if profile was removed
+ mLockdownEnabled = LockdownVpnTracker.isEnabled();
+ if (mLockdownEnabled) {
+ if (!mKeyStore.isUnlocked()) {
+ Slog.w(TAG, "KeyStore locked; unable to create LockdownTracker");
+ return false;
+ }
+
+ final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN));
+ final VpnProfile profile = VpnProfile.decode(
+ profileName, mKeyStore.get(Credentials.VPN + profileName));
+ int user = UserHandle.getUserId(Binder.getCallingUid());
+ synchronized(mVpns) {
+ setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user),
+ profile));
+ }
+ } else {
+ setLockdownTracker(null);
+ }
+
+ return true;
+ }
+
+ /**
+ * Internally set new {@link LockdownVpnTracker}, shutting down any existing
+ * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown.
+ */
+ private void setLockdownTracker(LockdownVpnTracker tracker) {
+ // Shutdown any existing tracker
+ final LockdownVpnTracker existing = mLockdownTracker;
+ mLockdownTracker = null;
+ if (existing != null) {
+ existing.shutdown();
+ }
+
+ try {
+ if (tracker != null) {
+ mNetd.setFirewallEnabled(true);
+ mNetd.setFirewallInterfaceRule("lo", true);
+ mLockdownTracker = tracker;
+ mLockdownTracker.init();
+ } else {
+ mNetd.setFirewallEnabled(false);
+ }
+ } catch (RemoteException e) {
+ // ignored; NMS lives inside system_server
+ }
+ }
+
+ private void throwIfLockdownEnabled() {
+ if (mLockdownEnabled) {
+ throw new IllegalStateException("Unavailable in lockdown mode");
+ }
+ }
+
+ public void supplyMessenger(int networkType, Messenger messenger) {
+ enforceConnectivityInternalPermission();
+
+ if (isNetworkTypeValid(networkType) && mNetTrackers[networkType] != null) {
+ mNetTrackers[networkType].supplyMessenger(messenger);
+ }
+ }
+
+ public int findConnectionTypeForIface(String iface) {
+ enforceConnectivityInternalPermission();
+
+ if (TextUtils.isEmpty(iface)) return ConnectivityManager.TYPE_NONE;
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ LinkProperties lp = tracker.getLinkProperties();
+ if (lp != null && iface.equals(lp.getInterfaceName())) {
+ return tracker.getNetworkInfo().getType();
+ }
+ }
+ }
+ return ConnectivityManager.TYPE_NONE;
+ }
+
+ /**
+ * Have mobile data fail fast if enabled.
+ *
+ * @param enabled DctConstants.ENABLED/DISABLED
+ */
+ private void setEnableFailFastMobileData(int enabled) {
+ int tag;
+
+ if (enabled == DctConstants.ENABLED) {
+ tag = mEnableFailFastMobileDataTag.incrementAndGet();
+ } else {
+ tag = mEnableFailFastMobileDataTag.get();
+ }
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_ENABLE_FAIL_FAST_MOBILE_DATA, tag,
+ enabled));
+ }
+
+ private boolean isMobileDataStateTrackerReady() {
+ MobileDataStateTracker mdst =
+ (MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+ return (mdst != null) && (mdst.isReady());
+ }
+
+ /**
+ * The ResultReceiver resultCode for checkMobileProvisioning (CMP_RESULT_CODE)
+ */
+
+ /**
+ * No connection was possible to the network.
+ * This is NOT a warm sim.
+ */
+ private static final int CMP_RESULT_CODE_NO_CONNECTION = 0;
+
+ /**
+ * A connection was made to the internet, all is well.
+ * This is NOT a warm sim.
+ */
+ private static final int CMP_RESULT_CODE_CONNECTABLE = 1;
+
+ /**
+ * A connection was made but no dns server was available to resolve a name to address.
+ * This is NOT a warm sim since provisioning network is supported.
+ */
+ private static final int CMP_RESULT_CODE_NO_DNS = 2;
+
+ /**
+ * A connection was made but could not open a TCP connection.
+ * This is NOT a warm sim since provisioning network is supported.
+ */
+ private static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 3;
+
+ /**
+ * A connection was made but there was a redirection, we appear to be in walled garden.
+ * This is an indication of a warm sim on a mobile network such as T-Mobile.
+ */
+ private static final int CMP_RESULT_CODE_REDIRECTED = 4;
+
+ /**
+ * The mobile network is a provisioning network.
+ * This is an indication of a warm sim on a mobile network such as AT&T.
+ */
+ private static final int CMP_RESULT_CODE_PROVISIONING_NETWORK = 5;
+
+ private AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false);
+
+ @Override
+ public int checkMobileProvisioning(int suggestedTimeOutMs) {
+ int timeOutMs = -1;
+ if (DBG) log("checkMobileProvisioning: E suggestedTimeOutMs=" + suggestedTimeOutMs);
+ enforceConnectivityInternalPermission();
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ timeOutMs = suggestedTimeOutMs;
+ if (suggestedTimeOutMs > CheckMp.MAX_TIMEOUT_MS) {
+ timeOutMs = CheckMp.MAX_TIMEOUT_MS;
+ }
+
+ // Check that mobile networks are supported
+ if (!isNetworkSupported(ConnectivityManager.TYPE_MOBILE)
+ || !isNetworkSupported(ConnectivityManager.TYPE_MOBILE_HIPRI)) {
+ if (DBG) log("checkMobileProvisioning: X no mobile network");
+ return timeOutMs;
+ }
+
+ // If we're already checking don't do it again
+ // TODO: Add a queue of results...
+ if (mIsCheckingMobileProvisioning.getAndSet(true)) {
+ if (DBG) log("checkMobileProvisioning: X already checking ignore for the moment");
+ return timeOutMs;
+ }
+
+ // Start off with mobile notification off
+ setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null);
+
+ CheckMp checkMp = new CheckMp(mContext, this);
+ CheckMp.CallBack cb = new CheckMp.CallBack() {
+ @Override
+ void onComplete(Integer result) {
+ if (DBG) log("CheckMp.onComplete: result=" + result);
+ NetworkInfo ni =
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo();
+ switch(result) {
+ case CMP_RESULT_CODE_CONNECTABLE:
+ case CMP_RESULT_CODE_NO_CONNECTION:
+ case CMP_RESULT_CODE_NO_DNS:
+ case CMP_RESULT_CODE_NO_TCP_CONNECTION: {
+ if (DBG) log("CheckMp.onComplete: ignore, connected or no connection");
+ break;
+ }
+ case CMP_RESULT_CODE_REDIRECTED: {
+ if (DBG) log("CheckMp.onComplete: warm sim");
+ String url = getMobileProvisioningUrl();
+ if (TextUtils.isEmpty(url)) {
+ url = getMobileRedirectedProvisioningUrl();
+ }
+ if (TextUtils.isEmpty(url) == false) {
+ if (DBG) log("CheckMp.onComplete: warm (redirected), url=" + url);
+ setProvNotificationVisible(true,
+ ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(),
+ url);
+ } else {
+ if (DBG) log("CheckMp.onComplete: warm (redirected), no url");
+ }
+ break;
+ }
+ case CMP_RESULT_CODE_PROVISIONING_NETWORK: {
+ String url = getMobileProvisioningUrl();
+ if (TextUtils.isEmpty(url) == false) {
+ if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), url=" + url);
+ setProvNotificationVisible(true,
+ ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(),
+ url);
+ } else {
+ if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url");
+ }
+ break;
+ }
+ default: {
+ loge("CheckMp.onComplete: ignore unexpected result=" + result);
+ break;
+ }
+ }
+ mIsCheckingMobileProvisioning.set(false);
+ }
+ };
+ CheckMp.Params params =
+ new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb);
+ if (DBG) log("checkMobileProvisioning: params=" + params);
+ checkMp.execute(params);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ if (DBG) log("checkMobileProvisioning: X");
+ }
+ return timeOutMs;
+ }
+
+ static class CheckMp extends
+ AsyncTask<CheckMp.Params, Void, Integer> {
+ private static final String CHECKMP_TAG = "CheckMp";
+
+ // adb shell setprop persist.checkmp.testfailures 1 to enable testing failures
+ private static boolean mTestingFailures;
+
+ // Choosing 4 loops as half of them will use HTTPS and the other half HTTP
+ private static final int MAX_LOOPS = 4;
+
+ // Number of milli-seconds to complete all of the retires
+ public static final int MAX_TIMEOUT_MS = 60000;
+
+ // The socket should retry only 5 seconds, the default is longer
+ private static final int SOCKET_TIMEOUT_MS = 5000;
+
+ // Sleep time for network errors
+ private static final int NET_ERROR_SLEEP_SEC = 3;
+
+ // Sleep time for network route establishment
+ private static final int NET_ROUTE_ESTABLISHMENT_SLEEP_SEC = 3;
+
+ // Short sleep time for polling :(
+ private static final int POLLING_SLEEP_SEC = 1;
+
+ private Context mContext;
+ private ConnectivityService mCs;
+ private TelephonyManager mTm;
+ private Params mParams;
+
+ /**
+ * Parameters for AsyncTask.execute
+ */
+ static class Params {
+ private String mUrl;
+ private long mTimeOutMs;
+ private CallBack mCb;
+
+ Params(String url, long timeOutMs, CallBack cb) {
+ mUrl = url;
+ mTimeOutMs = timeOutMs;
+ mCb = cb;
+ }
+
+ @Override
+ public String toString() {
+ return "{" + " url=" + mUrl + " mTimeOutMs=" + mTimeOutMs + " mCb=" + mCb + "}";
+ }
+ }
+
+ // As explained to me by Brian Carlstrom and Kenny Root, Certificates can be
+ // issued by name or ip address, for Google its by name so when we construct
+ // this HostnameVerifier we'll pass the original Uri and use it to verify
+ // the host. If the host name in the original uril fails we'll test the
+ // hostname parameter just incase things change.
+ static class CheckMpHostnameVerifier implements HostnameVerifier {
+ Uri mOrgUri;
+
+ CheckMpHostnameVerifier(Uri orgUri) {
+ mOrgUri = orgUri;
+ }
+
+ @Override
+ public boolean verify(String hostname, SSLSession session) {
+ HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
+ String orgUriHost = mOrgUri.getHost();
+ boolean retVal = hv.verify(orgUriHost, session) || hv.verify(hostname, session);
+ if (DBG) {
+ log("isMobileOk: hostnameVerify retVal=" + retVal + " hostname=" + hostname
+ + " orgUriHost=" + orgUriHost);
+ }
+ return retVal;
+ }
+ }
+
+ /**
+ * The call back object passed in Params. onComplete will be called
+ * on the main thread.
+ */
+ abstract static class CallBack {
+ // Called on the main thread.
+ abstract void onComplete(Integer result);
+ }
+
+ public CheckMp(Context context, ConnectivityService cs) {
+ if (Build.IS_DEBUGGABLE) {
+ mTestingFailures =
+ SystemProperties.getInt("persist.checkmp.testfailures", 0) == 1;
+ } else {
+ mTestingFailures = false;
+ }
+
+ mContext = context;
+ mCs = cs;
+
+ // Setup access to TelephonyService we'll be using.
+ mTm = (TelephonyManager) mContext.getSystemService(
+ Context.TELEPHONY_SERVICE);
+ }
+
+ /**
+ * Get the default url to use for the test.
+ */
+ public String getDefaultUrl() {
+ // See http://go/clientsdns for usage approval
+ String server = Settings.Global.getString(mContext.getContentResolver(),
+ Settings.Global.CAPTIVE_PORTAL_SERVER);
+ if (server == null) {
+ server = "clients3.google.com";
+ }
+ return "http://" + server + "/generate_204";
+ }
+
+ /**
+ * Detect if its possible to connect to the http url. DNS based detection techniques
+ * do not work at all hotspots. The best way to check is to perform a request to
+ * a known address that fetches the data we expect.
+ */
+ private synchronized Integer isMobileOk(Params params) {
+ Integer result = CMP_RESULT_CODE_NO_CONNECTION;
+ Uri orgUri = Uri.parse(params.mUrl);
+ Random rand = new Random();
+ mParams = params;
+
+ if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
+ result = CMP_RESULT_CODE_NO_CONNECTION;
+ log("isMobileOk: X not mobile capable result=" + result);
+ return result;
+ }
+
+ // See if we've already determined we've got a provisioning connection,
+ // if so we don't need to do anything active.
+ MobileDataStateTracker mdstDefault = (MobileDataStateTracker)
+ mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+ boolean isDefaultProvisioning = mdstDefault.isProvisioningNetwork();
+ log("isMobileOk: isDefaultProvisioning=" + isDefaultProvisioning);
+
+ MobileDataStateTracker mdstHipri = (MobileDataStateTracker)
+ mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+ boolean isHipriProvisioning = mdstHipri.isProvisioningNetwork();
+ log("isMobileOk: isHipriProvisioning=" + isHipriProvisioning);
+
+ if (isDefaultProvisioning || isHipriProvisioning) {
+ result = CMP_RESULT_CODE_PROVISIONING_NETWORK;
+ log("isMobileOk: X default || hipri is provisioning result=" + result);
+ return result;
+ }
+
+ try {
+ // Continue trying to connect until time has run out
+ long endTime = SystemClock.elapsedRealtime() + params.mTimeOutMs;
+
+ if (!mCs.isMobileDataStateTrackerReady()) {
+ // Wait for MobileDataStateTracker to be ready.
+ if (DBG) log("isMobileOk: mdst is not ready");
+ while(SystemClock.elapsedRealtime() < endTime) {
+ if (mCs.isMobileDataStateTrackerReady()) {
+ // Enable fail fast as we'll do retries here and use a
+ // hipri connection so the default connection stays active.
+ if (DBG) log("isMobileOk: mdst ready, enable fail fast of mobile data");
+ mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
+ break;
+ }
+ sleep(POLLING_SLEEP_SEC);
+ }
+ }
+
+ log("isMobileOk: start hipri url=" + params.mUrl);
+
+ // First wait until we can start using hipri
+ Binder binder = new Binder();
+ while(SystemClock.elapsedRealtime() < endTime) {
+ int ret = mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ Phone.FEATURE_ENABLE_HIPRI, binder);
+ if ((ret == PhoneConstants.APN_ALREADY_ACTIVE)
+ || (ret == PhoneConstants.APN_REQUEST_STARTED)) {
+ log("isMobileOk: hipri started");
+ break;
+ }
+ if (VDBG) log("isMobileOk: hipri not started yet");
+ result = CMP_RESULT_CODE_NO_CONNECTION;
+ sleep(POLLING_SLEEP_SEC);
+ }
+
+ // Continue trying to connect until time has run out
+ while(SystemClock.elapsedRealtime() < endTime) {
+ try {
+ // Wait for hipri to connect.
+ // TODO: Don't poll and handle situation where hipri fails
+ // because default is retrying. See b/9569540
+ NetworkInfo.State state = mCs
+ .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
+ if (state != NetworkInfo.State.CONNECTED) {
+ if (true/*VDBG*/) {
+ log("isMobileOk: not connected ni=" +
+ mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+ }
+ sleep(POLLING_SLEEP_SEC);
+ result = CMP_RESULT_CODE_NO_CONNECTION;
+ continue;
+ }
+
+ // Hipri has started check if this is a provisioning url
+ MobileDataStateTracker mdst = (MobileDataStateTracker)
+ mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+ if (mdst.isProvisioningNetwork()) {
+ result = CMP_RESULT_CODE_PROVISIONING_NETWORK;
+ if (DBG) log("isMobileOk: X isProvisioningNetwork result=" + result);
+ return result;
+ } else {
+ if (DBG) log("isMobileOk: isProvisioningNetwork is false, continue");
+ }
+
+ // Get of the addresses associated with the url host. We need to use the
+ // address otherwise HttpURLConnection object will use the name to get
+ // the addresses and will try every address but that will bypass the
+ // route to host we setup and the connection could succeed as the default
+ // interface might be connected to the internet via wifi or other interface.
+ InetAddress[] addresses;
+ try {
+ addresses = InetAddress.getAllByName(orgUri.getHost());
+ } catch (UnknownHostException e) {
+ result = CMP_RESULT_CODE_NO_DNS;
+ log("isMobileOk: X UnknownHostException result=" + result);
+ return result;
+ }
+ log("isMobileOk: addresses=" + inetAddressesToString(addresses));
+
+ // Get the type of addresses supported by this link
+ LinkProperties lp = mCs.getLinkProperties(
+ ConnectivityManager.TYPE_MOBILE_HIPRI);
+ boolean linkHasIpv4 = lp.hasIPv4Address();
+ boolean linkHasIpv6 = lp.hasIPv6Address();
+ log("isMobileOk: linkHasIpv4=" + linkHasIpv4
+ + " linkHasIpv6=" + linkHasIpv6);
+
+ final ArrayList<InetAddress> validAddresses =
+ new ArrayList<InetAddress>(addresses.length);
+
+ for (InetAddress addr : addresses) {
+ if (((addr instanceof Inet4Address) && linkHasIpv4) ||
+ ((addr instanceof Inet6Address) && linkHasIpv6)) {
+ validAddresses.add(addr);
+ }
+ }
+
+ if (validAddresses.size() == 0) {
+ return CMP_RESULT_CODE_NO_CONNECTION;
+ }
+
+ int addrTried = 0;
+ while (true) {
+ // Loop through at most MAX_LOOPS valid addresses or until
+ // we run out of time
+ if (addrTried++ >= MAX_LOOPS) {
+ log("isMobileOk: too many loops tried - giving up");
+ break;
+ }
+ if (SystemClock.elapsedRealtime() >= endTime) {
+ log("isMobileOk: spend too much time - giving up");
+ break;
+ }
+
+ InetAddress hostAddr = validAddresses.get(rand.nextInt(
+ validAddresses.size()));
+
+ // Make a route to host so we check the specific interface.
+ if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI,
+ hostAddr.getAddress())) {
+ // Wait a short time to be sure the route is established ??
+ log("isMobileOk:"
+ + " wait to establish route to hostAddr=" + hostAddr);
+ sleep(NET_ROUTE_ESTABLISHMENT_SLEEP_SEC);
+ } else {
+ log("isMobileOk:"
+ + " could not establish route to hostAddr=" + hostAddr);
+ // Wait a short time before the next attempt
+ sleep(NET_ERROR_SLEEP_SEC);
+ continue;
+ }
+
+ // Rewrite the url to have numeric address to use the specific route
+ // using http for half the attempts and https for the other half.
+ // Doing https first and http second as on a redirected walled garden
+ // such as t-mobile uses we get a SocketTimeoutException: "SSL
+ // handshake timed out" which we declare as
+ // CMP_RESULT_CODE_NO_TCP_CONNECTION. We could change this, but by
+ // having http second we will be using logic used for some time.
+ URL newUrl;
+ String scheme = (addrTried <= (MAX_LOOPS/2)) ? "https" : "http";
+ newUrl = new URL(scheme, hostAddr.getHostAddress(),
+ orgUri.getPath());
+ log("isMobileOk: newUrl=" + newUrl);
+
+ HttpURLConnection urlConn = null;
+ try {
+ // Open the connection set the request headers and get the response
+ urlConn = (HttpURLConnection)newUrl.openConnection(
+ java.net.Proxy.NO_PROXY);
+ if (scheme.equals("https")) {
+ ((HttpsURLConnection)urlConn).setHostnameVerifier(
+ new CheckMpHostnameVerifier(orgUri));
+ }
+ urlConn.setInstanceFollowRedirects(false);
+ urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS);
+ urlConn.setReadTimeout(SOCKET_TIMEOUT_MS);
+ urlConn.setUseCaches(false);
+ urlConn.setAllowUserInteraction(false);
+ // Set the "Connection" to "Close" as by default "Keep-Alive"
+ // is used which is useless in this case.
+ urlConn.setRequestProperty("Connection", "close");
+ int responseCode = urlConn.getResponseCode();
+
+ // For debug display the headers
+ Map<String, List<String>> headers = urlConn.getHeaderFields();
+ log("isMobileOk: headers=" + headers);
+
+ // Close the connection
+ urlConn.disconnect();
+ urlConn = null;
+
+ if (mTestingFailures) {
+ // Pretend no connection, this tests using http and https
+ result = CMP_RESULT_CODE_NO_CONNECTION;
+ log("isMobileOk: TESTING_FAILURES, pretend no connction");
+ continue;
+ }
+
+ if (responseCode == 204) {
+ // Return
+ result = CMP_RESULT_CODE_CONNECTABLE;
+ log("isMobileOk: X got expected responseCode=" + responseCode
+ + " result=" + result);
+ return result;
+ } else {
+ // Retry to be sure this was redirected, we've gotten
+ // occasions where a server returned 200 even though
+ // the device didn't have a "warm" sim.
+ log("isMobileOk: not expected responseCode=" + responseCode);
+ // TODO - it would be nice in the single-address case to do
+ // another DNS resolve here, but flushing the cache is a bit
+ // heavy-handed.
+ result = CMP_RESULT_CODE_REDIRECTED;
+ }
+ } catch (Exception e) {
+ log("isMobileOk: HttpURLConnection Exception" + e);
+ result = CMP_RESULT_CODE_NO_TCP_CONNECTION;
+ if (urlConn != null) {
+ urlConn.disconnect();
+ urlConn = null;
+ }
+ sleep(NET_ERROR_SLEEP_SEC);
+ continue;
+ }
+ }
+ log("isMobileOk: X loops|timed out result=" + result);
+ return result;
+ } catch (Exception e) {
+ log("isMobileOk: Exception e=" + e);
+ continue;
+ }
+ }
+ log("isMobileOk: timed out");
+ } finally {
+ log("isMobileOk: F stop hipri");
+ mCs.setEnableFailFastMobileData(DctConstants.DISABLED);
+ mCs.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+ Phone.FEATURE_ENABLE_HIPRI);
+
+ // Wait for hipri to disconnect.
+ long endTime = SystemClock.elapsedRealtime() + 5000;
+
+ while(SystemClock.elapsedRealtime() < endTime) {
+ NetworkInfo.State state = mCs
+ .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
+ if (state != NetworkInfo.State.DISCONNECTED) {
+ if (VDBG) {
+ log("isMobileOk: connected ni=" +
+ mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+ }
+ sleep(POLLING_SLEEP_SEC);
+ continue;
+ }
+ }
+
+ log("isMobileOk: X result=" + result);
+ }
+ return result;
+ }
+
+ @Override
+ protected Integer doInBackground(Params... params) {
+ return isMobileOk(params[0]);
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ log("onPostExecute: result=" + result);
+ if ((mParams != null) && (mParams.mCb != null)) {
+ mParams.mCb.onComplete(result);
+ }
+ }
+
+ private String inetAddressesToString(InetAddress[] addresses) {
+ StringBuffer sb = new StringBuffer();
+ boolean firstTime = true;
+ for(InetAddress addr : addresses) {
+ if (firstTime) {
+ firstTime = false;
+ } else {
+ sb.append(",");
+ }
+ sb.append(addr);
+ }
+ return sb.toString();
+ }
+
+ private void printNetworkInfo() {
+ boolean hasIccCard = mTm.hasIccCard();
+ int simState = mTm.getSimState();
+ log("hasIccCard=" + hasIccCard
+ + " simState=" + simState);
+ NetworkInfo[] ni = mCs.getAllNetworkInfo();
+ if (ni != null) {
+ log("ni.length=" + ni.length);
+ for (NetworkInfo netInfo: ni) {
+ log("netInfo=" + netInfo.toString());
+ }
+ } else {
+ log("no network info ni=null");
+ }
+ }
+
+ /**
+ * Sleep for a few seconds then return.
+ * @param seconds
+ */
+ private static void sleep(int seconds) {
+ try {
+ Thread.sleep(seconds * 1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void log(String s) {
+ Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s);
+ }
+ }
+
+ // TODO: Move to ConnectivityManager and make public?
+ private static final String CONNECTED_TO_PROVISIONING_NETWORK_ACTION =
+ "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION";
+
+ private BroadcastReceiver mProvisioningReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(CONNECTED_TO_PROVISIONING_NETWORK_ACTION)) {
+ handleMobileProvisioningAction(intent.getStringExtra("EXTRA_URL"));
+ }
+ }
+ };
+
+ private void handleMobileProvisioningAction(String url) {
+ // Notication mark notification as not visible
+ setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null);
+
+ // If provisioning network handle as a special case,
+ // otherwise launch browser with the intent directly.
+ NetworkInfo ni = getProvisioningNetworkInfo();
+ if ((ni != null) && ni.isConnectedToProvisioningNetwork()) {
+ if (DBG) log("handleMobileProvisioningAction: on provisioning network");
+ MobileDataStateTracker mdst = (MobileDataStateTracker)
+ mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+ mdst.enableMobileProvisioning(url);
+ } else {
+ if (DBG) log("handleMobileProvisioningAction: on default network");
+ // Check for apps that can handle provisioning first
+ Intent provisioningIntent = new Intent(TelephonyIntents.ACTION_CARRIER_SETUP);
+ provisioningIntent.addCategory(TelephonyIntents.CATEGORY_MCCMNC_PREFIX
+ + mTelephonyManager.getSimOperator());
+ if (mContext.getPackageManager().resolveActivity(provisioningIntent, 0 /* flags */)
+ != null) {
+ provisioningIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(provisioningIntent);
+ } else {
+ // If no apps exist, use standard URL ACTION_VIEW method
+ Intent newIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN,
+ Intent.CATEGORY_APP_BROWSER);
+ newIntent.setData(Uri.parse(url));
+ newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ try {
+ mContext.startActivity(newIntent);
+ } catch (ActivityNotFoundException e) {
+ loge("handleMobileProvisioningAction: startActivity failed" + e);
+ }
+ }
+ }
+ }
+
+ private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
+ private volatile boolean mIsNotificationVisible = false;
+
+ private void setProvNotificationVisible(boolean visible, int networkType, String extraInfo,
+ String url) {
+ if (DBG) {
+ log("setProvNotificationVisible: E visible=" + visible + " networkType=" + networkType
+ + " extraInfo=" + extraInfo + " url=" + url);
+ }
+
+ Resources r = Resources.getSystem();
+ NotificationManager notificationManager = (NotificationManager) mContext
+ .getSystemService(Context.NOTIFICATION_SERVICE);
+
+ if (visible) {
+ CharSequence title;
+ CharSequence details;
+ int icon;
+ Intent intent;
+ Notification notification = new Notification();
+ switch (networkType) {
+ case ConnectivityManager.TYPE_WIFI:
+ title = r.getString(R.string.wifi_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ extraInfo);
+ icon = R.drawable.stat_notify_wifi_in_range;
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ break;
+ case ConnectivityManager.TYPE_MOBILE:
+ case ConnectivityManager.TYPE_MOBILE_HIPRI:
+ title = r.getString(R.string.network_available_sign_in, 0);
+ // TODO: Change this to pull from NetworkInfo once a printable
+ // name has been added to it
+ details = mTelephonyManager.getNetworkOperatorName();
+ icon = R.drawable.stat_notify_rssi_in_range;
+ intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
+ intent.putExtra("EXTRA_URL", url);
+ intent.setFlags(0);
+ notification.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
+ break;
+ default:
+ title = r.getString(R.string.network_available_sign_in, 0);
+ details = r.getString(R.string.network_available_sign_in_detailed,
+ extraInfo);
+ icon = R.drawable.stat_notify_rssi_in_range;
+ intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT |
+ Intent.FLAG_ACTIVITY_NEW_TASK);
+ notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+ break;
+ }
+
+ notification.when = 0;
+ notification.icon = icon;
+ notification.flags = Notification.FLAG_AUTO_CANCEL;
+ notification.tickerText = title;
+ notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
+
+ try {
+ notificationManager.notify(NOTIFICATION_ID, networkType, notification);
+ } catch (NullPointerException npe) {
+ loge("setNotificaitionVisible: visible notificationManager npe=" + npe);
+ npe.printStackTrace();
+ }
+ } else {
+ try {
+ notificationManager.cancel(NOTIFICATION_ID, networkType);
+ } catch (NullPointerException npe) {
+ loge("setNotificaitionVisible: cancel notificationManager npe=" + npe);
+ npe.printStackTrace();
+ }
+ }
+ mIsNotificationVisible = visible;
+ }
+
+ /** Location to an updatable file listing carrier provisioning urls.
+ * An example:
+ *
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <provisioningUrls>
+ * <provisioningUrl mcc="310" mnc="4">http://myserver.com/foo?mdn=%3$s&amp;iccid=%1$s&amp;imei=%2$s</provisioningUrl>
+ * <redirectedUrl mcc="310" mnc="4">http://www.google.com</redirectedUrl>
+ * </provisioningUrls>
+ */
+ private static final String PROVISIONING_URL_PATH =
+ "/data/misc/radio/provisioning_urls.xml";
+ private final File mProvisioningUrlFile = new File(PROVISIONING_URL_PATH);
+
+ /** XML tag for root element. */
+ private static final String TAG_PROVISIONING_URLS = "provisioningUrls";
+ /** XML tag for individual url */
+ private static final String TAG_PROVISIONING_URL = "provisioningUrl";
+ /** XML tag for redirected url */
+ private static final String TAG_REDIRECTED_URL = "redirectedUrl";
+ /** XML attribute for mcc */
+ private static final String ATTR_MCC = "mcc";
+ /** XML attribute for mnc */
+ private static final String ATTR_MNC = "mnc";
+
+ private static final int REDIRECTED_PROVISIONING = 1;
+ private static final int PROVISIONING = 2;
+
+ private String getProvisioningUrlBaseFromFile(int type) {
+ FileReader fileReader = null;
+ XmlPullParser parser = null;
+ Configuration config = mContext.getResources().getConfiguration();
+ String tagType;
+
+ switch (type) {
+ case PROVISIONING:
+ tagType = TAG_PROVISIONING_URL;
+ break;
+ case REDIRECTED_PROVISIONING:
+ tagType = TAG_REDIRECTED_URL;
+ break;
+ default:
+ throw new RuntimeException("getProvisioningUrlBaseFromFile: Unexpected parameter " +
+ type);
+ }
+
+ try {
+ fileReader = new FileReader(mProvisioningUrlFile);
+ parser = Xml.newPullParser();
+ parser.setInput(fileReader);
+ XmlUtils.beginDocument(parser, TAG_PROVISIONING_URLS);
+
+ while (true) {
+ XmlUtils.nextElement(parser);
+
+ String element = parser.getName();
+ if (element == null) break;
+
+ if (element.equals(tagType)) {
+ String mcc = parser.getAttributeValue(null, ATTR_MCC);
+ try {
+ if (mcc != null && Integer.parseInt(mcc) == config.mcc) {
+ String mnc = parser.getAttributeValue(null, ATTR_MNC);
+ if (mnc != null && Integer.parseInt(mnc) == config.mnc) {
+ parser.next();
+ if (parser.getEventType() == XmlPullParser.TEXT) {
+ return parser.getText();
+ }
+ }
+ }
+ } catch (NumberFormatException e) {
+ loge("NumberFormatException in getProvisioningUrlBaseFromFile: " + e);
+ }
+ }
+ }
+ return null;
+ } catch (FileNotFoundException e) {
+ loge("Carrier Provisioning Urls file not found");
+ } catch (XmlPullParserException e) {
+ loge("Xml parser exception reading Carrier Provisioning Urls file: " + e);
+ } catch (IOException e) {
+ loge("I/O exception reading Carrier Provisioning Urls file: " + e);
+ } finally {
+ if (fileReader != null) {
+ try {
+ fileReader.close();
+ } catch (IOException e) {}
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getMobileRedirectedProvisioningUrl() {
+ enforceConnectivityInternalPermission();
+ String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING);
+ if (TextUtils.isEmpty(url)) {
+ url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url);
+ }
+ return url;
+ }
+
+ @Override
+ public String getMobileProvisioningUrl() {
+ enforceConnectivityInternalPermission();
+ String url = getProvisioningUrlBaseFromFile(PROVISIONING);
+ if (TextUtils.isEmpty(url)) {
+ url = mContext.getResources().getString(R.string.mobile_provisioning_url);
+ log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url);
+ } else {
+ log("getMobileProvisioningUrl: mobile_provisioning_url from File =" + url);
+ }
+ // populate the iccid, imei and phone number in the provisioning url.
+ if (!TextUtils.isEmpty(url)) {
+ String phoneNumber = mTelephonyManager.getLine1Number();
+ if (TextUtils.isEmpty(phoneNumber)) {
+ phoneNumber = "0000000000";
+ }
+ url = String.format(url,
+ mTelephonyManager.getSimSerialNumber() /* ICCID */,
+ mTelephonyManager.getDeviceId() /* IMEI */,
+ phoneNumber /* Phone numer */);
+ }
+
+ return url;
+ }
+
+ @Override
+ public void setProvisioningNotificationVisible(boolean visible, int networkType,
+ String extraInfo, String url) {
+ enforceConnectivityInternalPermission();
+ setProvNotificationVisible(visible, networkType, extraInfo, url);
+ }
+
+ @Override
+ public void setAirplaneMode(boolean enable) {
+ enforceConnectivityInternalPermission();
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ final ContentResolver cr = mContext.getContentResolver();
+ Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0);
+ Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+ intent.putExtra("state", enable);
+ mContext.sendBroadcast(intent);
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
+ private void onUserStart(int userId) {
+ synchronized(mVpns) {
+ Vpn userVpn = mVpns.get(userId);
+ if (userVpn != null) {
+ loge("Starting user already has a VPN");
+ return;
+ }
+ userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId);
+ mVpns.put(userId, userVpn);
+ userVpn.startMonitoring(mContext, mTrackerHandler);
+ }
+ }
+
+ private void onUserStop(int userId) {
+ synchronized(mVpns) {
+ Vpn userVpn = mVpns.get(userId);
+ if (userVpn == null) {
+ loge("Stopping user has no VPN");
+ return;
+ }
+ mVpns.delete(userId);
+ }
+ }
+
+ private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
+ if (userId == UserHandle.USER_NULL) return;
+
+ if (Intent.ACTION_USER_STARTING.equals(action)) {
+ onUserStart(userId);
+ } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
+ onUserStop(userId);
+ }
+ }
+ };
+
+ @Override
+ public LinkQualityInfo getLinkQualityInfo(int networkType) {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(networkType)) {
+ return mNetTrackers[networkType].getLinkQualityInfo();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public LinkQualityInfo getActiveLinkQualityInfo() {
+ enforceAccessPermission();
+ if (isNetworkTypeValid(mActiveDefaultNetwork)) {
+ return mNetTrackers[mActiveDefaultNetwork].getLinkQualityInfo();
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public LinkQualityInfo[] getAllLinkQualityInfo() {
+ enforceAccessPermission();
+ final ArrayList<LinkQualityInfo> result = Lists.newArrayList();
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ LinkQualityInfo li = tracker.getLinkQualityInfo();
+ if (li != null) {
+ result.add(li);
+ }
+ }
+ }
+
+ return result.toArray(new LinkQualityInfo[result.size()]);
+ }
+
+ /* Infrastructure for network sampling */
+
+ private void handleNetworkSamplingTimeout() {
+
+ log("Sampling interval elapsed, updating statistics ..");
+
+ // initialize list of interfaces ..
+ Map<String, SamplingDataTracker.SamplingSnapshot> mapIfaceToSample =
+ new HashMap<String, SamplingDataTracker.SamplingSnapshot>();
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ String ifaceName = tracker.getNetworkInterfaceName();
+ if (ifaceName != null) {
+ mapIfaceToSample.put(ifaceName, null);
+ }
+ }
+ }
+
+ // Read samples for all interfaces
+ SamplingDataTracker.getSamplingSnapshots(mapIfaceToSample);
+
+ // process samples for all networks
+ for (NetworkStateTracker tracker : mNetTrackers) {
+ if (tracker != null) {
+ String ifaceName = tracker.getNetworkInterfaceName();
+ SamplingDataTracker.SamplingSnapshot ss = mapIfaceToSample.get(ifaceName);
+ if (ss != null) {
+ // end the previous sampling cycle
+ tracker.stopSampling(ss);
+ // start a new sampling cycle ..
+ tracker.startSampling(ss);
+ }
+ }
+ }
+
+ log("Done.");
+
+ int samplingIntervalInSeconds = Settings.Global.getInt(mContext.getContentResolver(),
+ Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS,
+ DEFAULT_SAMPLING_INTERVAL_IN_SECONDS);
+
+ if (DBG) log("Setting timer for " + String.valueOf(samplingIntervalInSeconds) + "seconds");
+
+ setAlarm(samplingIntervalInSeconds * 1000, mSampleIntervalElapsedIntent);
+ }
+
+ void setAlarm(int timeoutInMilliseconds, PendingIntent intent) {
+ long wakeupTime = SystemClock.elapsedRealtime() + timeoutInMilliseconds;
+ mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent);
+ }
+}