OSDN Git Service

Make CS.isTetheringSupported dynamic
[android-x86/frameworks-base.git] / services / java / com / android / server / ConnectivityService.java
index e5cfdf6..cb4e89c 100644 (file)
@@ -16,7 +16,6 @@
 
 package com.android.server;
 
-import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
 import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
@@ -32,7 +31,9 @@ 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.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
 import android.bluetooth.BluetoothTetheringDataTracker;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -41,6 +42,7 @@ 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;
@@ -54,11 +56,13 @@ import android.net.INetworkPolicyManager;
 import android.net.INetworkStatsService;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.Uri;
 import android.net.LinkProperties.CompareResult;
 import android.net.MobileDataStateTracker;
 import android.net.NetworkConfig;
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
+import android.net.NetworkInfo.State;
 import android.net.NetworkQuotaInfo;
 import android.net.NetworkState;
 import android.net.NetworkStateTracker;
@@ -68,6 +72,7 @@ import android.net.ProxyProperties;
 import android.net.RouteInfo;
 import android.net.wifi.WifiStateTracker;
 import android.net.wimax.WimaxManagerConstants;
+import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -76,10 +81,12 @@ 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.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
@@ -87,18 +94,23 @@ 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.EventLog;
 import android.util.Slog;
 import android.util.SparseIntArray;
+import android.util.Xml;
 
+import com.android.internal.R;
 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.util.IndentingPrintWriter;
+import com.android.internal.util.XmlUtils;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.Tethering;
 import com.android.server.connectivity.Vpn;
 import com.android.server.net.BaseNetworkObserver;
@@ -108,13 +120,21 @@ 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;
@@ -122,6 +142,8 @@ import java.util.Collection;
 import java.util.GregorianCalendar;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * @hide
@@ -142,12 +164,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
     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";
+
     // 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 boolean mTetheringConfigValid = false;
 
     private KeyStore mKeyStore;
 
@@ -157,6 +184,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
     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. */
@@ -183,7 +212,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
      * A per Net list of the PID's that requested access to the net
      * used both as a refcount and for per-PID DNS selection
      */
-    private List mNetRequestersPids[];
+    private List<Integer> mNetRequestersPids[];
 
     // priority order of the nettrackers
     // (excluding dynamically set mNetworkPreference)
@@ -291,6 +320,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
 
     private static final int EVENT_VPN_STATE_CHANGED = 14;
 
+    /**
+     * Used internally to disable fail fast of mobile data
+     */
+    private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 15;
+
     /** Handler used for internal events. */
     private InternalHandler mHandler;
     /** Handler used for incoming {@link NetworkStateTracker} events. */
@@ -320,12 +354,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
 
     // track the current default http proxy - tell the world if we get a new one (real change)
     private ProxyProperties mDefaultProxy = null;
-    private Object mDefaultProxyLock = new Object();
+    private Object mProxyLock = new Object();
     private boolean mDefaultProxyDisabled = false;
 
     // track the global proxy.
     private ProxyProperties mGlobalProxy = null;
-    private final Object mGlobalProxyLock = new Object();
 
     private SettingsObserver mSettingsObserver;
 
@@ -346,6 +379,13 @@ public class ConnectivityService extends IConnectivityManager.Stub {
     // the set of network types that can only be enabled by system/sig apps
     List mProtectedNetworks;
 
+    private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0);
+
+    TelephonyManager mTelephonyManager;
+
+    // We only want one checkMobileProvisioning after booting.
+    volatile boolean mFirstProvisioningCheckStarted = false;
+
     public ConnectivityService(Context context, INetworkManagementService netd,
             INetworkStatsService statsService, INetworkPolicyManager policyManager) {
         // Currently, omitting a NetworkFactory will create one internally
@@ -394,6 +434,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         mNetd = checkNotNull(netManager, "missing INetworkManagementService");
         mPolicyManager = checkNotNull(policyManager, "missing INetworkPolicyManager");
         mKeyStore = KeyStore.getInstance();
+        mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
 
         try {
             mPolicyManager.registerListener(mPolicyListener);
@@ -412,8 +453,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                 ConnectivityManager.MAX_NETWORK_TYPE+1];
         mCurrentLinkProperties = new LinkProperties[ConnectivityManager.MAX_NETWORK_TYPE+1];
 
-        mNetworkPreference = getPersistedNetworkPreference();
-
         mRadioAttributes = new RadioAttributes[ConnectivityManager.MAX_RADIO_TYPE+1];
         mNetConfigs = new NetworkConfig[ConnectivityManager.MAX_NETWORK_TYPE+1];
 
@@ -434,6 +473,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             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) {
@@ -444,6 +486,11 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                             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);
@@ -495,15 +542,29 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             }
         }
 
-        mNetRequestersPids = new ArrayList[ConnectivityManager.MAX_NETWORK_TYPE+1];
+        // 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();
+            mNetRequestersPids[i] = new ArrayList<Integer>();
         }
 
         mFeatureUsers = new ArrayList<FeatureUser>();
 
-        mNumDnsEntries = 0;
-
         mTestMode = SystemProperties.get("cm.test.mode").equals("true")
                 && SystemProperties.get("ro.build.type").equals("eng");
 
@@ -527,17 +588,16 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         }
 
         mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper());
-        mTetheringConfigValid = ((mTethering.getTetherableUsbRegexs().length != 0 ||
-                                  mTethering.getTetherableWifiRegexs().length != 0 ||
-                                  mTethering.getTetherableBluetoothRegexs().length != 0) &&
-                                 mTethering.getUpstreamIfaceTypes().length != 0);
 
-        mVpn = new Vpn(mContext, mVpnCallback, mNetd);
+        mVpn = new Vpn(mContext, mVpnCallback, mNetd, this);
         mVpn.startMonitoring(mContext, mTrackerHandler);
 
+        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);
         }
@@ -726,11 +786,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
 
         final int networkPrefSetting = Settings.Global
                 .getInt(cr, Settings.Global.NETWORK_PREFERENCE, -1);
-        if (networkPrefSetting != -1) {
-            return networkPrefSetting;
-        }
 
-        return ConnectivityManager.DEFAULT_NETWORK_PREFERENCE;
+        return networkPrefSetting;
     }
 
     /**
@@ -1306,7 +1363,14 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             if (usedNetworkType != networkType) {
                 Integer currentPid = new Integer(pid);
                 mNetRequestersPids[usedNetworkType].remove(currentPid);
-                reassessPidDns(pid, true);
+
+                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 +
@@ -1381,8 +1445,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                 netState != DetailedState.CAPTIVE_PORTAL_CHECK) ||
                 tracker.isTeardownRequested()) {
             if (VDBG) {
-                log("requestRouteToHostAddress on down network " +
-                           "(" + networkType + ") - dropped");
+                log("requestRouteToHostAddress on down network "
+                        + "(" + networkType + ") - dropped"
+                        + " tracker=" + tracker
+                        + " netState=" + netState
+                        + " isTeardownRequested="
+                            + ((tracker != null) ? tracker.isTeardownRequested() : "tracker:null"));
             }
             return false;
         }
@@ -1390,21 +1458,24 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         try {
             InetAddress addr = InetAddress.getByAddress(hostAddress);
             LinkProperties lp = tracker.getLinkProperties();
-            return addRouteToAddress(lp, addr);
+            boolean ok = addRouteToAddress(lp, addr);
+            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) {
-        return modifyRoute(p.getInterfaceName(), p, r, 0, ADD, toDefaultTable);
+        return modifyRoute(p, r, 0, ADD, toDefaultTable);
     }
 
     private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) {
-        return modifyRoute(p.getInterfaceName(), p, r, 0, REMOVE, toDefaultTable);
+        return modifyRoute(p, r, 0, REMOVE, toDefaultTable);
     }
 
     private boolean addRouteToAddress(LinkProperties lp, InetAddress addr) {
@@ -1417,26 +1488,27 @@ public class ConnectivityService extends IConnectivityManager.Stub {
 
     private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd,
             boolean toDefaultTable) {
-        RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), addr);
+        RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr);
         if (bestRoute == null) {
-            bestRoute = RouteInfo.makeHostRoute(addr);
+            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);
+                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());
+                bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface);
             }
         }
-        return modifyRoute(lp.getInterfaceName(), lp, bestRoute, 0, doAdd, toDefaultTable);
+        return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable);
     }
 
-    private boolean modifyRoute(String ifaceName, LinkProperties lp, RouteInfo r, int cycleCount,
-            boolean doAdd, boolean toDefaultTable) {
-        if ((ifaceName == null) || (lp == null) || (r == null)) {
-            if (DBG) log("modifyRoute got unexpected null: " + ifaceName + ", " + lp + ", " + r);
+    private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd,
+            boolean toDefaultTable) {
+        if ((lp == null) || (r == null)) {
+            if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r);
             return false;
         }
 
@@ -1445,18 +1517,25 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             return false;
         }
 
-        if (r.isHostRoute() == false) {
-            RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getRoutes(), r.getGateway());
+        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());
+                    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());
+                    bestRoute = RouteInfo.makeHostRoute(r.getGateway(),
+                                                        bestRoute.getGateway(),
+                                                        ifaceName);
                 }
-                modifyRoute(ifaceName, lp, bestRoute, cycleCount+1, doAdd, toDefaultTable);
+                modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable);
             }
         }
         if (doAdd) {
@@ -1688,9 +1767,8 @@ public class ConnectivityService extends IConnectivityManager.Stub {
          * in accordance with network preference policies.
          */
         if (!mNetConfigs[prevNetType].isDefault()) {
-            List pids = mNetRequestersPids[prevNetType];
-            for (int i = 0; i<pids.size(); i++) {
-                Integer pid = (Integer)pids.get(i);
+            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.
@@ -1811,6 +1889,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
     }
 
     public void sendConnectedBroadcast(NetworkInfo info) {
+        enforceConnectivityInternalPermission();
         sendGeneralBroadcast(info, CONNECTIVITY_ACTION_IMMEDIATE);
         sendGeneralBroadcast(info, CONNECTIVITY_ACTION);
     }
@@ -2095,6 +2174,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
 
     /** @hide */
     public void captivePortalCheckComplete(NetworkInfo info) {
+        enforceConnectivityInternalPermission();
         mNetTrackers[info.getType()].captivePortalCheckComplete();
     }
 
@@ -2225,33 +2305,55 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault());
 
         if (resetMask != 0 || resetDns) {
-            LinkProperties linkProperties = mNetTrackers[netType].getLinkProperties();
-            if (linkProperties != null) {
-                String iface = linkProperties.getInterfaceName();
-                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) {
-                            mVpn.interfaceStatusChanged(iface, false);
+            if (curLp != null) {
+                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) {
+                                mVpn.interfaceStatusChanged(iface, false);
+                            }
                         }
-                    }
-                    if (resetDns) {
-                        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);
+                        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. */
@@ -2281,7 +2383,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             routeDiff = curLp.compareRoutes(newLp);
             dnsDiff = curLp.compareDnses(newLp);
         } else if (newLp != null) {
-            routeDiff.added = newLp.getRoutes();
+            routeDiff.added = newLp.getAllRoutes();
             dnsDiff.added = newLp.getDnses();
         }
 
@@ -2297,28 +2399,6 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             }
         }
 
-        for (RouteInfo r :  routeDiff.added) {
-            if (isLinkDefault || ! r.isDefaultRoute()) {
-                addRoute(newLp, r, TO_DEFAULT_TABLE);
-            } else {
-                // add to a secondary route table
-                addRoute(newLp, r, TO_SECONDARY_TABLE);
-
-                // 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();
-                if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) {
-                    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);
-                    }
-                }
-            }
-        }
-
         if (!isLinkDefault) {
             // handle DNS routes
             if (routesChanged) {
@@ -2343,6 +2423,29 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                 }
             }
         }
+
+        for (RouteInfo r :  routeDiff.added) {
+            if (isLinkDefault || ! r.isDefaultRoute()) {
+                addRoute(newLp, r, TO_DEFAULT_TABLE);
+            } else {
+                // add to a secondary route table
+                addRoute(newLp, r, TO_SECONDARY_TABLE);
+
+                // 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();
+                if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) {
+                    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;
     }
 
@@ -2352,7 +2455,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
      * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system
      * wide use
      */
-   public void updateNetworkSettings(NetworkStateTracker nt) {
+   private void updateNetworkSettings(NetworkStateTracker nt) {
         String key = nt.getTcpBufferSizesPropName();
         String bufferSizes = key == null ? null : SystemProperties.get(key);
 
@@ -2406,9 +2509,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
      * on the highest priority active net which this process requested.
      * If there aren't any, clear it out
      */
-    private void reassessPidDns(int myPid, boolean doBump)
+    private void reassessPidDns(int pid, boolean doBump)
     {
-        if (VDBG) log("reassessPidDns for pid " + myPid);
+        if (VDBG) log("reassessPidDns for pid " + pid);
+        Integer myPid = new Integer(pid);
         for(int i : mPriorityList) {
             if (mNetConfigs[i].isDefault()) {
                 continue;
@@ -2418,61 +2522,25 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                     !nt.isTeardownRequested()) {
                 LinkProperties p = nt.getLinkProperties();
                 if (p == null) continue;
-                List pids = mNetRequestersPids[i];
-                for (int j=0; j<pids.size(); j++) {
-                    Integer pid = (Integer)pids.get(j);
-                    if (pid.intValue() == myPid) {
-                        Collection<InetAddress> dnses = p.getDnses();
-                        writePidDns(dnses, myPid);
-                        if (doBump) {
-                            bumpDns();
-                        }
-                        return;
+                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
-        for (int i = 1; ; i++) {
-            String prop = "net.dns" + i + "." + myPid;
-            if (SystemProperties.get(prop).length() == 0) {
-                if (doBump) {
-                    bumpDns();
-                }
-                return;
-            }
-            SystemProperties.set(prop, "");
-        }
-    }
-
-    // return true if results in a change
-    private boolean writePidDns(Collection <InetAddress> dnses, int pid) {
-        int j = 1;
-        boolean changed = false;
-        for (InetAddress dns : dnses) {
-            String dnsString = dns.getHostAddress();
-            if (changed || !dnsString.equals(SystemProperties.get("net.dns" + j + "." + pid))) {
-                changed = true;
-                SystemProperties.set("net.dns" + j + "." + pid, dns.getHostAddress());
-            }
-            j++;
+        try {
+            mNetd.clearDnsInterfaceForPid(pid);
+        } catch (Exception e) {
+            Slog.e(TAG, "exception clear interface from pid: " + e);
         }
-        return changed;
     }
 
-    private void bumpDns() {
-        /*
-         * Bump the property that tells the name resolver library to reread
-         * the DNS server list from the properties.
-         */
-        String propVal = SystemProperties.get("net.dnschange");
-        int n = 0;
-        if (propVal.length() != 0) {
-            try {
-                n = Integer.parseInt(propVal);
-            } catch (NumberFormatException e) {}
-        }
-        SystemProperties.set("net.dnschange", "" + (n+1));
+    private void flushVmDnsCache() {
         /*
          * Tell the VMs to toss their DNS caches
          */
@@ -2491,56 +2559,34 @@ public class ConnectivityService extends IConnectivityManager.Stub {
     }
 
     // Caller must grab mDnsLock.
-    private boolean updateDns(String network, String iface,
+    private void updateDnsLocked(String network, String iface,
             Collection<InetAddress> dnses, String domains) {
-        boolean changed = false;
         int last = 0;
         if (dnses.size() == 0 && mDefaultDns != null) {
-            ++last;
-            String value = mDefaultDns.getHostAddress();
-            if (!value.equals(SystemProperties.get("net.dns1"))) {
-                if (DBG) {
-                    loge("no dns provided for " + network + " - using " + value);
-                }
-                changed = true;
-                SystemProperties.set("net.dns1", value);
+            dnses = new ArrayList();
+            dnses.add(mDefaultDns);
+            if (DBG) {
+                loge("no dns provided for " + network + " - using " + mDefaultDns.getHostAddress());
             }
-        } else {
+        }
+
+        try {
+            mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains);
+            mNetd.setDefaultInterfaceForDns(iface);
             for (InetAddress dns : dnses) {
                 ++last;
                 String key = "net.dns" + last;
                 String value = dns.getHostAddress();
-                if (!changed && value.equals(SystemProperties.get(key))) {
-                    continue;
-                }
-                if (VDBG) {
-                    log("adding dns " + value + " for " + network);
-                }
-                changed = true;
                 SystemProperties.set(key, value);
             }
-        }
-        for (int i = last + 1; i <= mNumDnsEntries; ++i) {
-            String key = "net.dns" + i;
-            if (VDBG) log("erasing " + key);
-            changed = true;
-            SystemProperties.set(key, "");
-        }
-        mNumDnsEntries = last;
-
-        if (changed) {
-            try {
-                mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses));
-                mNetd.setDefaultInterfaceForDns(iface);
-            } catch (Exception e) {
-                if (DBG) loge("exception setting default dns interface: " + e);
+            for (int i = last + 1; i <= mNumDnsEntries; ++i) {
+                String key = "net.dns" + i;
+                SystemProperties.set(key, "");
             }
+            mNumDnsEntries = last;
+        } catch (Exception e) {
+            if (DBG) loge("exception setting default dns interface: " + e);
         }
-        if (!domains.equals(SystemProperties.get("net.dns.search"))) {
-            SystemProperties.set("net.dns.search", domains);
-            changed = true;
-        }
-        return changed;
     }
 
     private void handleDnsConfigurationChange(int netType) {
@@ -2550,29 +2596,31 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             LinkProperties p = nt.getLinkProperties();
             if (p == null) return;
             Collection<InetAddress> dnses = p.getDnses();
-            boolean changed = false;
             if (mNetConfigs[netType].isDefault()) {
                 String network = nt.getNetworkInfo().getTypeName();
                 synchronized (mDnsLock) {
                     if (!mDnsOverridden) {
-                        changed = updateDns(network, p.getInterfaceName(), dnses, "");
+                        updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains());
                     }
                 }
             } else {
                 try {
                     mNetd.setDnsServersForInterface(p.getInterfaceName(),
-                            NetworkUtils.makeStrings(dnses));
+                            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 pids = mNetRequestersPids[netType];
-                for (int y=0; y< pids.size(); y++) {
-                    Integer pid = (Integer)pids.get(y);
-                    changed = writePidDns(dnses, pid.intValue());
+                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);
+                    }
                 }
             }
-            if (changed) bumpDns();
+            flushVmDnsCache();
         }
     }
 
@@ -2631,7 +2679,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         pw.increaseIndent();
         for (int net : mPriorityList) {
             String pidString = net + ": ";
-            for (Object pid : mNetRequestersPids[net]) {
+            for (Integer pid : mNetRequestersPids[net]) {
                 pidString = pidString + pid.toString() + ", ";
             }
             pw.println(pidString);
@@ -2689,6 +2737,17 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                             state + "/" + info.getDetailedState());
                     }
 
+                    // After booting we'll check once for mobile provisioning
+                    // if we've provisioned by and connected.
+                    if (!mFirstProvisioningCheckStarted
+                            && (0 != Settings.Global.getInt(mContext.getContentResolver(),
+                                        Settings.Global.DEVICE_PROVISIONED, 0))
+                            && (state == NetworkInfo.State.CONNECTED)) {
+                        log("check provisioning after booting");
+                        mFirstProvisioningCheckStarted = true;
+                        checkMobileProvisioning(true, CheckMp.MAX_TIMEOUT_MS, null);
+                    }
+
                     EventLogTags.writeConnectivityStateChanged(
                             info.getType(), info.getSubtype(), info.getDetailedState().ordinal());
 
@@ -2820,6 +2879,19 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                     }
                     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);
+                    }
+                }
             }
         }
     }
@@ -2886,7 +2958,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
     }
 
     public int setUsbTethering(boolean enable) {
-        enforceTetherAccessPermission();
+        enforceTetherChangePermission();
         if (isTetheringSupported()) {
             return mTethering.setUsbTethering(enable);
         } else {
@@ -2925,7 +2997,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         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 && mTetheringConfigValid;
+        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.
@@ -3039,14 +3114,19 @@ public class ConnectivityService extends IConnectivityManager.Stub {
     }
 
     public ProxyProperties getProxy() {
-        synchronized (mDefaultProxyLock) {
-            return mDefaultProxyDisabled ? null : mDefaultProxy;
+        // 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) {
+            if (mGlobalProxy != null) return mGlobalProxy;
+            return (mDefaultProxyDisabled ? null : mDefaultProxy);
         }
     }
 
     public void setGlobalProxy(ProxyProperties proxyProperties) {
-        enforceChangePermission();
-        synchronized (mGlobalProxyLock) {
+        enforceConnectivityInternalPermission();
+        synchronized (mProxyLock) {
             if (proxyProperties == mGlobalProxy) return;
             if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return;
             if (mGlobalProxy != null && mGlobalProxy.equals(proxyProperties)) return;
@@ -3063,16 +3143,21 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                 mGlobalProxy = null;
             }
             ContentResolver res = mContext.getContentResolver();
-            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);
+            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);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
 
         if (mGlobalProxy == null) {
             proxyProperties = mDefaultProxy;
         }
-        //sendProxyBroadcast(proxyProperties);
+        sendProxyBroadcast(proxyProperties);
     }
 
     private void loadGlobalProxy() {
@@ -3083,14 +3168,18 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                 Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST);
         if (!TextUtils.isEmpty(host)) {
             ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList);
-            synchronized (mGlobalProxyLock) {
+            synchronized (mProxyLock) {
                 mGlobalProxy = proxyProperties;
             }
         }
     }
 
     public ProxyProperties getGlobalProxy() {
-        synchronized (mGlobalProxyLock) {
+        // 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;
         }
     }
@@ -3099,11 +3188,12 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         if (proxy != null && TextUtils.isEmpty(proxy.getHost())) {
             proxy = null;
         }
-        synchronized (mDefaultProxyLock) {
+        synchronized (mProxyLock) {
             if (mDefaultProxy != null && mDefaultProxy.equals(proxy)) return;
-            if (mDefaultProxy == proxy) return;
+            if (mDefaultProxy == proxy) return; // catches repeated nulls
             mDefaultProxy = proxy;
 
+            if (mGlobalProxy != null) return;
             if (!mDefaultProxyDisabled) {
                 sendProxyBroadcast(proxy);
             }
@@ -3225,7 +3315,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         throwIfLockdownEnabled();
         try {
             int type = mActiveDefaultNetwork;
-            if (ConnectivityManager.isNetworkTypeValid(type)) {
+            if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) {
                 mVpn.protect(socket, mNetTrackers[type].getLinkProperties().getInterfaceName());
                 return true;
             }
@@ -3341,19 +3431,15 @@ public class ConnectivityService extends IConnectivityManager.Stub {
             String domains = buffer.toString().trim();
 
             // Apply DNS changes.
-            boolean changed = false;
             synchronized (mDnsLock) {
-                changed = updateDns("VPN", "VPN", addresses, domains);
+                updateDnsLocked("VPN", "VPN", addresses, domains);
                 mDnsOverridden = true;
             }
-            if (changed) {
-                bumpDns();
-            }
 
-            // Temporarily disable the default proxy.
-            synchronized (mDefaultProxyLock) {
+            // Temporarily disable the default proxy (not global).
+            synchronized (mProxyLock) {
                 mDefaultProxyDisabled = true;
-                if (mDefaultProxy != null) {
+                if (mGlobalProxy == null && mDefaultProxy != null) {
                     sendProxyBroadcast(null);
                 }
             }
@@ -3368,9 +3454,9 @@ public class ConnectivityService extends IConnectivityManager.Stub {
                     mHandler.sendEmptyMessage(EVENT_RESTORE_DNS);
                 }
             }
-            synchronized (mDefaultProxyLock) {
+            synchronized (mProxyLock) {
                 mDefaultProxyDisabled = false;
-                if (mDefaultProxy != null) {
+                if (mGlobalProxy == null && mDefaultProxy != null) {
                     sendProxyBroadcast(mDefaultProxy);
                 }
             }
@@ -3379,7 +3465,10 @@ public class ConnectivityService extends IConnectivityManager.Stub {
 
     @Override
     public boolean updateLockdownVpn() {
-        enforceSystemUid();
+        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();
@@ -3415,6 +3504,7 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         try {
             if (tracker != null) {
                 mNetd.setFirewallEnabled(true);
+                mNetd.setFirewallInterfaceRule("lo", true);
                 mLockdownTracker = tracker;
                 mLockdownTracker.init();
             } else {
@@ -3431,10 +3521,600 @@ public class ConnectivityService extends IConnectivityManager.Stub {
         }
     }
 
-    private static void enforceSystemUid() {
-        final int uid = Binder.getCallingUid();
-        if (uid != Process.SYSTEM_UID) {
-            throw new SecurityException("Only available to AID_SYSTEM");
+    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));
+    }
+
+    @Override
+    public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs,
+            final ResultReceiver resultReceiver) {
+        log("checkMobileProvisioning: E sendNotification=" + sendNotification
+                + " suggestedTimeOutMs=" + suggestedTimeOutMs
+                + " resultReceiver=" + resultReceiver);
+        enforceChangePermission();
+
+        mFirstProvisioningCheckStarted = true;
+
+        int 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)) {
+            log("checkMobileProvisioning: X no mobile network");
+            if (resultReceiver != null) {
+                resultReceiver.send(ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION, null);
+            }
+            return timeOutMs;
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            CheckMp checkMp = new CheckMp(mContext, this);
+            CheckMp.CallBack cb = new CheckMp.CallBack() {
+                @Override
+                void onComplete(Integer result) {
+                    log("CheckMp.onComplete: result=" + result);
+                    if (resultReceiver != null) {
+                        log("CheckMp.onComplete: send result");
+                        resultReceiver.send(result, null);
+                    }
+                    NetworkInfo ni =
+                            mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo();
+                    switch(result) {
+                        case ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE:
+                        case ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION: {
+                            log("CheckMp.onComplete: ignore, connected or no connection");
+                            break;
+                        }
+                        case ConnectivityManager.CMP_RESULT_CODE_REDIRECTED: {
+                            log("CheckMp.onComplete: warm sim");
+                            String url = getMobileProvisioningUrl();
+                            if (TextUtils.isEmpty(url)) {
+                                url = getMobileRedirectedProvisioningUrl();
+                            }
+                            if (TextUtils.isEmpty(url) == false) {
+                                log("CheckMp.onComplete: warm sim (redirected), url=" + url);
+                                setNotificationVisible(true, ni, url);
+                            } else {
+                                log("CheckMp.onComplete: warm sim (redirected), no url");
+                            }
+                            break;
+                        }
+                        case ConnectivityManager.CMP_RESULT_CODE_NO_DNS:
+                        case ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION: {
+                            String url = getMobileProvisioningUrl();
+                            if (TextUtils.isEmpty(url) == false) {
+                                log("CheckMp.onComplete: warm sim (no dns/tcp), url=" + url);
+                                setNotificationVisible(true, ni, url);
+                            } else {
+                                log("CheckMp.onComplete: warm sim (no dns/tcp), no url");
+                            }
+                            break;
+                        }
+                        default: {
+                            loge("CheckMp.onComplete: ignore unexpected result=" + result);
+                            break;
+                        }
+                    }
+                }
+            };
+            CheckMp.Params params =
+                    new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb);
+            log("checkMobileProvisioning: params=" + params);
+            setNotificationVisible(false, null, null);
+            checkMp.execute(params);
+        } finally {
+            Binder.restoreCallingIdentity(token);
+            log("checkMobileProvisioning: X");
+        }
+        return timeOutMs;
+    }
+
+    static class CheckMp extends
+            AsyncTask<CheckMp.Params, Void, Integer> {
+        private static final String CHECKMP_TAG = "CheckMp";
+        public static final int MAX_TIMEOUT_MS =  60000;
+        private static final int SOCKET_TIMEOUT_MS = 5000;
+        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 + "}";
+            }
+        }
+
+        /**
+         * 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) {
+            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 = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+            Uri orgUri = Uri.parse(params.mUrl);
+            Random rand = new Random();
+            mParams = params;
+
+            try {
+                if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) {
+                    log("isMobileOk: not mobile capable");
+                    result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+                    return result;
+                }
+
+                // Enable fail fast as we'll do retries here and use a
+                // hipri connection so the default connection stays active.
+                log("isMobileOk: start hipri url=" + params.mUrl);
+                mCs.setEnableFailFastMobileData(DctConstants.ENABLED);
+                mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE,
+                        Phone.FEATURE_ENABLE_HIPRI, new Binder());
+
+                // Continue trying to connect until time has run out
+                long endTime = SystemClock.elapsedRealtime() + params.mTimeOutMs;
+                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) {
+                            log("isMobileOk: not connected ni=" +
+                                    mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+                            sleep(1);
+                            result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+                            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 is 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) {
+                            log("isMobileOk: UnknownHostException");
+                            result = ConnectivityManager.CMP_RESULT_CODE_NO_DNS;
+                            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 = hasIPv4Address(lp);
+                        boolean linkHasIpv6 = hasIPv6Address(lp);
+                        log("isMobileOk: linkHasIpv4=" + linkHasIpv4
+                                + " linkHasIpv6=" + linkHasIpv6);
+
+                        // Loop through at most 3 valid addresses or all of the address or until
+                        // we run out of time
+                        int loops = Math.min(3, addresses.length);
+                        for(int validAddr=0, addrTried=0;
+                                    (validAddr < loops) && (addrTried < addresses.length)
+                                      && (SystemClock.elapsedRealtime() < endTime);
+                                addrTried ++) {
+
+                            // Choose the address at random but make sure its type is supported
+                            InetAddress hostAddr = addresses[rand.nextInt(addresses.length)];
+                            if (((hostAddr instanceof Inet4Address) && linkHasIpv4)
+                                    || ((hostAddr instanceof Inet6Address) && linkHasIpv6)) {
+                                // Valid address, so use it
+                                validAddr += 1;
+                            } else {
+                                // Invalid address so try next address
+                                continue;
+                            }
+
+                            // 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(3);
+                            } else {
+                                log("isMobileOk:"
+                                        + " could not establish route to hostAddr=" + hostAddr);
+                                continue;
+                            }
+
+                            // Rewrite the url to have numeric address to use the specific route.
+                            // I also set the "Connection" to "Close" as by default "Keep-Alive"
+                            // is used which is useless in this case.
+                            URL newUrl = new URL(orgUri.getScheme() + "://"
+                                    + hostAddr.getHostAddress() + orgUri.getPath());
+                            log("isMobileOk: newUrl=" + newUrl);
+
+                            HttpURLConnection urlConn = null;
+                            try {
+                                // Open the connection set the request header and get the response
+                                urlConn = (HttpURLConnection) newUrl.openConnection(
+                                        java.net.Proxy.NO_PROXY);
+                                urlConn.setInstanceFollowRedirects(false);
+                                urlConn.setConnectTimeout(SOCKET_TIMEOUT_MS);
+                                urlConn.setReadTimeout(SOCKET_TIMEOUT_MS);
+                                urlConn.setUseCaches(false);
+                                urlConn.setAllowUserInteraction(false);
+                                urlConn.setRequestProperty("Connection", "close");
+                                int responseCode = urlConn.getResponseCode();
+                                if (responseCode == 204) {
+                                    result = ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE;
+                                } else {
+                                    result = ConnectivityManager.CMP_RESULT_CODE_REDIRECTED;
+                                }
+                                log("isMobileOk: connected responseCode=" + responseCode);
+                                urlConn.disconnect();
+                                urlConn = null;
+                                return result;
+                            } catch (Exception e) {
+                                log("isMobileOk: HttpURLConnection Exception e=" + e);
+                                if (urlConn != null) {
+                                    urlConn.disconnect();
+                                    urlConn = null;
+                                }
+                            }
+                        }
+                        result = ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION;
+                        log("isMobileOk: loops|timed out");
+                        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);
+                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();
+            }
+        }
+
+        public boolean hasIPv4Address(LinkProperties lp) {
+            return lp.hasIPv4Address();
+        }
+
+        // Not implemented in LinkProperties, do it here.
+        public boolean hasIPv6Address(LinkProperties lp) {
+            for (LinkAddress address : lp.getLinkAddresses()) {
+              if (address.getAddress() instanceof Inet6Address) {
+                return true;
+              }
+            }
+            return false;
+        }
+
+        private void log(String s) {
+            Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s);
+        }
+    }
+
+    private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
+
+    private void setNotificationVisible(boolean visible, NetworkInfo networkInfo, String url) {
+        log("setNotificationVisible: E visible=" + visible + " ni=" + networkInfo + " url=" + url);
+
+        Resources r = Resources.getSystem();
+        NotificationManager notificationManager = (NotificationManager) mContext
+            .getSystemService(Context.NOTIFICATION_SERVICE);
+
+        if (visible) {
+            CharSequence title;
+            CharSequence details;
+            int icon;
+            switch (networkInfo.getType()) {
+                case ConnectivityManager.TYPE_WIFI:
+                    log("setNotificationVisible: TYPE_WIFI");
+                    title = r.getString(R.string.wifi_available_sign_in, 0);
+                    details = r.getString(R.string.network_available_sign_in_detailed,
+                            networkInfo.getExtraInfo());
+                    icon = R.drawable.stat_notify_wifi_in_range;
+                    break;
+                case ConnectivityManager.TYPE_MOBILE:
+                case ConnectivityManager.TYPE_MOBILE_HIPRI:
+                    log("setNotificationVisible: 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;
+                    break;
+                default:
+                    log("setNotificationVisible: other type=" + networkInfo.getType());
+                    title = r.getString(R.string.network_available_sign_in, 0);
+                    details = r.getString(R.string.network_available_sign_in_detailed,
+                            networkInfo.getExtraInfo());
+                    icon = R.drawable.stat_notify_rssi_in_range;
+                    break;
+            }
+
+            Notification notification = new Notification();
+            notification.when = 0;
+            notification.icon = icon;
+            notification.flags = Notification.FLAG_AUTO_CANCEL;
+            Intent 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);
+            notification.tickerText = title;
+            notification.setLatestEventInfo(mContext, title, details, notification.contentIntent);
+
+            log("setNotificaitionVisible: notify notificaiton=" + notification);
+            notificationManager.notify(NOTIFICATION_ID, 1, notification);
+        } else {
+            log("setNotificaitionVisible: cancel");
+            notificationManager.cancel(NOTIFICATION_ID, 1);
+        }
+        log("setNotificationVisible: X visible=" + visible + " ni=" + networkInfo + " url=" + url);
+    }
+
+    /** 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;
+    }
+
+    private String getMobileRedirectedProvisioningUrl() {
+        String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING);
+        if (TextUtils.isEmpty(url)) {
+            url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url);
+        }
+        return url;
+    }
+
+    public String getMobileProvisioningUrl() {
+        enforceConnectivityInternalPermission();
+        String url = getProvisioningUrlBaseFromFile(PROVISIONING);
+        if (TextUtils.isEmpty(url)) {
+            url = mContext.getResources().getString(R.string.mobile_provisioning_url);
+            log("getProvisioningUrl: mobile_provisioining_url from resource =" + url);
+        } else {
+            log("getProvisioningUrl: 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;
+    }
 }