Merge "Telephony: SMS over IMS, DO NOT MERGE" into jb-mr2-dev
diff --git a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
index 81c0a6a..0aedecb 100644
--- a/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
+++ b/core/java/android/bluetooth/BluetoothTetheringDataTracker.java
@@ -152,6 +152,11 @@
         // not implemented
     }
 
+    @Override
+    public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+        // not implemented
+    }
+
     /**
      * Re-enable connectivity to a network after a {@link #teardown()}.
      */
diff --git a/core/java/android/net/BaseNetworkStateTracker.java b/core/java/android/net/BaseNetworkStateTracker.java
index 1165281..e87f84c 100644
--- a/core/java/android/net/BaseNetworkStateTracker.java
+++ b/core/java/android/net/BaseNetworkStateTracker.java
@@ -102,6 +102,11 @@
     }
 
     @Override
+    public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+        // not implemented
+    }
+
+    @Override
     public boolean setRadio(boolean turnOn) {
         // Base tracker doesn't handle radios
         return true;
diff --git a/core/java/android/net/CaptivePortalTracker.java b/core/java/android/net/CaptivePortalTracker.java
index 21995c0..19c5f39 100644
--- a/core/java/android/net/CaptivePortalTracker.java
+++ b/core/java/android/net/CaptivePortalTracker.java
@@ -34,6 +34,7 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
@@ -42,6 +43,7 @@
 import java.net.HttpURLConnection;
 import java.net.InetAddress;
 import java.net.Inet4Address;
+import java.net.SocketTimeoutException;
 import java.net.URL;
 import java.net.UnknownHostException;
 
@@ -52,17 +54,15 @@
  * @hide
  */
 public class CaptivePortalTracker extends StateMachine {
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final String TAG = "CaptivePortalTracker";
 
     private static final String DEFAULT_SERVER = "clients3.google.com";
-    private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
 
     private static final int SOCKET_TIMEOUT_MS = 10000;
 
     private String mServer;
     private String mUrl;
-    private boolean mNotificationShown = false;
     private boolean mIsCaptivePortalCheckEnabled = false;
     private IConnectivityManager mConnService;
     private TelephonyManager mTelephonyManager;
@@ -159,12 +159,12 @@
     private class DefaultState extends State {
         @Override
         public void enter() {
-            if (DBG) log(getName() + "\n");
+            setNotificationOff();
         }
 
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) log(getName() + message.toString() + "\n");
+            if (DBG) log(getName() + message.toString());
             switch (message.what) {
                 case CMD_DETECT_PORTAL:
                     NetworkInfo info = (NetworkInfo) message.obj;
@@ -186,23 +186,24 @@
     private class NoActiveNetworkState extends State {
         @Override
         public void enter() {
-            if (DBG) log(getName() + "\n");
             mNetworkInfo = null;
-            /* Clear any previous notification */
-            setNotificationVisible(false);
         }
 
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) log(getName() + message.toString() + "\n");
+            if (DBG) log(getName() + message.toString());
             InetAddress server;
             NetworkInfo info;
             switch (message.what) {
                 case CMD_CONNECTIVITY_CHANGE:
                     info = (NetworkInfo) message.obj;
-                    if (info.isConnected() && isActiveNetwork(info)) {
-                        mNetworkInfo = info;
-                        transitionTo(mDelayedCaptiveCheckState);
+                    if (info.getType() == ConnectivityManager.TYPE_WIFI) {
+                        if (info.isConnected() && isActiveNetwork(info)) {
+                            mNetworkInfo = info;
+                            transitionTo(mDelayedCaptiveCheckState);
+                        }
+                    } else {
+                        log(getName() + " not a wifi connectivity change, ignore");
                     }
                     break;
                 default:
@@ -215,7 +216,7 @@
     private class ActiveNetworkState extends State {
         @Override
         public void enter() {
-            if (DBG) log(getName() + "\n");
+            setNotificationOff();
         }
 
         @Override
@@ -248,7 +249,6 @@
     private class DelayedCaptiveCheckState extends State {
         @Override
         public void enter() {
-            if (DBG) log(getName() + "\n");
             Message message = obtainMessage(CMD_DELAYED_CAPTIVE_CHECK, ++mDelayedCheckToken, 0);
             if (mDeviceProvisioned) {
                 sendMessageDelayed(message, DELAYED_CHECK_INTERVAL_MS);
@@ -259,7 +259,7 @@
 
         @Override
         public boolean processMessage(Message message) {
-            if (DBG) log(getName() + message.toString() + "\n");
+            if (DBG) log(getName() + message.toString());
             switch (message.what) {
                 case CMD_DELAYED_CAPTIVE_CHECK:
                     if (message.arg1 == mDelayedCheckToken) {
@@ -270,11 +270,17 @@
                         } else {
                             if (DBG) log("Not captive network " + mNetworkInfo);
                         }
+                        notifyPortalCheckCompleted(mNetworkInfo, captive);
                         if (mDeviceProvisioned) {
                             if (captive) {
                                 // Setup Wizard will assist the user in connecting to a captive
                                 // portal, so make the notification visible unless during setup
-                                setNotificationVisible(true);
+                                try {
+                                    mConnService.setProvisioningNotificationVisible(true,
+                                        mNetworkInfo.getType(), mNetworkInfo.getExtraInfo(), mUrl);
+                                } catch(RemoteException e) {
+                                    e.printStackTrace();
+                                }
                             }
                         } else {
                             Intent intent = new Intent(
@@ -300,12 +306,26 @@
             return;
         }
         try {
+            if (DBG) log("notifyPortalCheckComplete: ni=" + info);
             mConnService.captivePortalCheckComplete(info);
         } catch(RemoteException e) {
             e.printStackTrace();
         }
     }
 
+    private void notifyPortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
+        if (info == null) {
+            loge("notifyPortalCheckComplete on null");
+            return;
+        }
+        try {
+            if (DBG) log("notifyPortalCheckCompleted: captive=" + isCaptivePortal + " ni=" + info);
+            mConnService.captivePortalCheckCompleted(info, isCaptivePortal);
+        } catch(RemoteException e) {
+            e.printStackTrace();
+        }
+    }
+
     private boolean isActiveNetwork(NetworkInfo info) {
         try {
             NetworkInfo active = mConnService.getActiveNetworkInfo();
@@ -318,6 +338,15 @@
         return false;
     }
 
+    private void setNotificationOff() {
+        try {
+            mConnService.setProvisioningNotificationVisible(false, ConnectivityManager.TYPE_NONE,
+                    null, null);
+        } catch (RemoteException e) {
+            log("setNotificationOff: " + e);
+        }
+    }
+
     /**
      * Do a URL fetch on a known server to see if we get the data we expect
      */
@@ -360,58 +389,4 @@
         }
         return null;
     }
-
-    private void setNotificationVisible(boolean visible) {
-        // if it should be hidden and it is already hidden, then noop
-        if (!visible && !mNotificationShown) {
-            return;
-        }
-
-        Resources r = Resources.getSystem();
-        NotificationManager notificationManager = (NotificationManager) mContext
-            .getSystemService(Context.NOTIFICATION_SERVICE);
-
-        if (visible) {
-            CharSequence title;
-            CharSequence details;
-            int icon;
-            switch (mNetworkInfo.getType()) {
-                case ConnectivityManager.TYPE_WIFI:
-                    title = r.getString(R.string.wifi_available_sign_in, 0);
-                    details = r.getString(R.string.network_available_sign_in_detailed,
-                            mNetworkInfo.getExtraInfo());
-                    icon = R.drawable.stat_notify_wifi_in_range;
-                    break;
-                case ConnectivityManager.TYPE_MOBILE:
-                    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:
-                    title = r.getString(R.string.network_available_sign_in, 0);
-                    details = r.getString(R.string.network_available_sign_in_detailed,
-                            mNetworkInfo.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(mUrl));
-            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);
-
-            notificationManager.notify(NOTIFICATION_ID, 1, notification);
-        } else {
-            notificationManager.cancel(NOTIFICATION_ID, 1);
-        }
-        mNotificationShown = visible;
-    }
 }
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 697bde9..02a6494 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -583,6 +583,29 @@
     }
 
     /**
+     * Returns details about the Provisioning or currently active default data network. When
+     * connected, this network is the default route for outgoing connections.
+     * You should always check {@link NetworkInfo#isConnected()} before initiating
+     * network traffic. This may return {@code null} when there is no default
+     * network.
+     *
+     * @return a {@link NetworkInfo} object for the current default network
+     *        or {@code null} if no network default network is currently active
+     *
+     * <p>This method requires the call to hold the permission
+     * {@link android.Manifest.permission#ACCESS_NETWORK_STATE}.
+     *
+     * {@hide}
+     */
+    public NetworkInfo getProvisioningOrActiveNetworkInfo() {
+        try {
+            return mService.getProvisioningOrActiveNetworkInfo();
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
      * Returns the IP information for the current default network.
      *
      * @return a {@link LinkProperties} object describing the IP info
@@ -1283,6 +1306,25 @@
     }
 
     /**
+     * Signal that the captive portal check on the indicated network
+     * is complete and whether its a captive portal or not.
+     *
+     * @param info the {@link NetworkInfo} object for the networkType
+     *        in question.
+     * @param isCaptivePortal true/false.
+     *
+     * <p>This method requires the call to hold the permission
+     * {@link android.Manifest.permission#CONNECTIVITY_INTERNAL}.
+     * {@hide}
+     */
+    public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) {
+        try {
+            mService.captivePortalCheckCompleted(info, isCaptivePortal);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
      * Supply the backend messenger for a network tracker
      *
      * @param type NetworkType to set
@@ -1297,70 +1339,26 @@
     }
 
     /**
-     * The ResultReceiver resultCode for checkMobileProvisioning (CMP_RESULT_CODE)
-     */
-
-    /**
-     * No connection was possible to the network.
-     * {@hide}
-     */
-    public static final int CMP_RESULT_CODE_NO_CONNECTION = 0;
-
-    /**
-     * A connection was made to the internet, all is well.
-     * {@hide}
-     */
-    public static final int CMP_RESULT_CODE_CONNECTABLE = 1;
-
-    /**
-     * 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.
-     * {@hide}
-     */
-    public static final int CMP_RESULT_CODE_REDIRECTED = 2;
-
-    /**
-     * A connection was made but no dns server was available to resolve a name to address.
-     * This is an indication of a warm sim on a mobile network.
+     * Check mobile provisioning.
      *
-     * {@hide}
-     */
-    public static final int CMP_RESULT_CODE_NO_DNS = 3;
-
-    /**
-     * A connection was made but could not open a TCP connection.
-     * This is an indication of a warm sim on a mobile network.
-     * {@hide}
-     */
-    public static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 4;
-
-    /**
-     * Check mobile provisioning. The resultCode passed to
-     * onReceiveResult will be one of the CMP_RESULT_CODE_xxxx values above.
-     * This may take a minute or more to complete.
-     *
-     * @param sendNotificaiton, when true a notification will be sent to user.
      * @param suggestedTimeOutMs, timeout in milliseconds
-     * @param resultReceiver needs to  be supplied to receive the result
      *
      * @return time out that will be used, maybe less that suggestedTimeOutMs
      * -1 if an error.
      *
      * {@hide}
      */
-    public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs,
-            ResultReceiver resultReceiver) {
+    public int checkMobileProvisioning(int suggestedTimeOutMs) {
         int timeOutMs = -1;
         try {
-            timeOutMs = mService.checkMobileProvisioning(sendNotification, suggestedTimeOutMs,
-                    resultReceiver);
+            timeOutMs = mService.checkMobileProvisioning(suggestedTimeOutMs);
         } catch (RemoteException e) {
         }
         return timeOutMs;
     }
 
     /**
-     * Get the carrier provisioning url.
+     * Get the mobile provisioning url.
      * {@hide}
      */
     public String getMobileProvisioningUrl() {
@@ -1370,4 +1368,32 @@
         }
         return null;
     }
+
+    /**
+     * Get the mobile redirected provisioning url.
+     * {@hide}
+     */
+    public String getMobileRedirectedProvisioningUrl() {
+        try {
+            return mService.getMobileRedirectedProvisioningUrl();
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    /**
+     * Set sign in error notification to visible or in visible
+     *
+     * @param visible
+     * @param networkType
+     *
+     * {@hide}
+     */
+    public void setProvisioningNotificationVisible(boolean visible, int networkType,
+            String extraInfo, String url) {
+        try {
+            mService.setProvisioningNotificationVisible(visible, networkType, extraInfo, url);
+        } catch (RemoteException e) {
+        }
+    }
 }
diff --git a/core/java/android/net/DummyDataStateTracker.java b/core/java/android/net/DummyDataStateTracker.java
index 15a81f3..ee738fd 100644
--- a/core/java/android/net/DummyDataStateTracker.java
+++ b/core/java/android/net/DummyDataStateTracker.java
@@ -120,10 +120,16 @@
         return true;
     }
 
+    @Override
     public void captivePortalCheckComplete() {
         // not implemented
     }
 
+    @Override
+    public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+        // not implemented
+    }
+
     /**
      * Record the detailed state of a network, and if it is a
      * change from the previous state, send a notification to
diff --git a/core/java/android/net/EthernetDataTracker.java b/core/java/android/net/EthernetDataTracker.java
index 27d5a58..ac2b0d9 100644
--- a/core/java/android/net/EthernetDataTracker.java
+++ b/core/java/android/net/EthernetDataTracker.java
@@ -279,6 +279,11 @@
         // not implemented
     }
 
+    @Override
+    public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+        // not implemented
+    }
+
     /**
      * Turn the wireless radio off for a network.
      * @param turnOn {@code true} to turn the radio on, {@code false}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 4600c1a..a17b4f5 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -46,6 +46,8 @@
     NetworkInfo getNetworkInfo(int networkType);
     NetworkInfo[] getAllNetworkInfo();
 
+    NetworkInfo getProvisioningOrActiveNetworkInfo();
+
     boolean isNetworkSupported(int networkType);
 
     LinkProperties getActiveLinkProperties();
@@ -129,11 +131,17 @@
 
     void captivePortalCheckComplete(in NetworkInfo info);
 
+    void captivePortalCheckCompleted(in NetworkInfo info, boolean isCaptivePortal);
+
     void supplyMessenger(int networkType, in Messenger messenger);
 
     int findConnectionTypeForIface(in String iface);
 
-    int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs, in ResultReceiver resultReceiver);
+    int checkMobileProvisioning(int suggestedTimeOutMs);
 
     String getMobileProvisioningUrl();
+
+    String getMobileRedirectedProvisioningUrl();
+
+    void setProvisioningNotificationVisible(boolean visible, int networkType, in String extraInfo, in String url);
 }
diff --git a/core/java/android/net/MobileDataStateTracker.java b/core/java/android/net/MobileDataStateTracker.java
index 5a1daed..77b7314 100644
--- a/core/java/android/net/MobileDataStateTracker.java
+++ b/core/java/android/net/MobileDataStateTracker.java
@@ -40,6 +40,7 @@
 
 import java.io.CharArrayWriter;
 import java.io.PrintWriter;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Track the state of mobile data connectivity. This is done by
@@ -51,7 +52,7 @@
 public class MobileDataStateTracker implements NetworkStateTracker {
 
     private static final String TAG = "MobileDataStateTracker";
-    private static final boolean DBG = false;
+    private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
     private PhoneConstants.DataState mMobileDataState;
@@ -75,6 +76,8 @@
     private Handler mHandler;
     private AsyncChannel mDataConnectionTrackerAc;
 
+    private AtomicBoolean mIsCaptivePortal = new AtomicBoolean(false);
+
     /**
      * Create a new MobileDataStateTracker
      * @param netType the ConnectivityManager network type
@@ -101,6 +104,7 @@
 
         IntentFilter filter = new IntentFilter();
         filter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+        filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN);
         filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
 
         mContext.registerReceiver(new MobileDataStateReceiver(), filter);
@@ -168,20 +172,49 @@
     public void releaseWakeLock() {
     }
 
+    private void updateLinkProperitesAndCapatilities(Intent intent) {
+        mLinkProperties = intent.getParcelableExtra(
+                PhoneConstants.DATA_LINK_PROPERTIES_KEY);
+        if (mLinkProperties == null) {
+            loge("CONNECTED event did not supply link properties.");
+            mLinkProperties = new LinkProperties();
+        }
+        mLinkCapabilities = intent.getParcelableExtra(
+                PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
+        if (mLinkCapabilities == null) {
+            loge("CONNECTED event did not supply link capabilities.");
+            mLinkCapabilities = new LinkCapabilities();
+        }
+    }
+
     private class MobileDataStateReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (intent.getAction().equals(TelephonyIntents.
+                    ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN)) {
+                String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
+                String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
+                if (!TextUtils.equals(mApnType, apnType)) {
+                    return;
+                }
+                if (DBG) {
+                    log("Broadcast received: " + intent.getAction() + " apnType=" + apnType
+                            + " apnName=" + apnName);
+                }
+
+                // Make us in the connecting state until we make a new TYPE_MOBILE_PROVISIONING
+                mMobileDataState = PhoneConstants.DataState.CONNECTING;
+                updateLinkProperitesAndCapatilities(intent);
+                setDetailedState(DetailedState.CONNECTED_TO_PROVISIONING_NETWORK, "", apnName);
+            } else if (intent.getAction().equals(TelephonyIntents.
                     ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
                 String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
-                if (VDBG) {
-                    log(String.format("Broadcast received: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED"
-                        + "mApnType=%s %s received apnType=%s", mApnType,
-                        TextUtils.equals(apnType, mApnType) ? "==" : "!=", apnType));
-                }
                 if (!TextUtils.equals(apnType, mApnType)) {
                     return;
                 }
+                if (DBG) {
+                    log("Broadcast received: " + intent.getAction() + " apnType=" + apnType);
+                }
 
                 int oldSubtype = mNetworkInfo.getSubtype();
                 int newSubType = TelephonyManager.getDefault().getNetworkType();
@@ -199,7 +232,7 @@
                 String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
                 mNetworkInfo.setRoaming(intent.getBooleanExtra(
                         PhoneConstants.DATA_NETWORK_ROAMING_KEY, false));
-                if (VDBG) {
+                if (DBG) {
                     log(mApnType + " setting isAvailable to " +
                             intent.getBooleanExtra(PhoneConstants.NETWORK_UNAVAILABLE_KEY,false));
                 }
@@ -233,18 +266,7 @@
                             setDetailedState(DetailedState.SUSPENDED, reason, apnName);
                             break;
                         case CONNECTED:
-                            mLinkProperties = intent.getParcelableExtra(
-                                    PhoneConstants.DATA_LINK_PROPERTIES_KEY);
-                            if (mLinkProperties == null) {
-                                loge("CONNECTED event did not supply link properties.");
-                                mLinkProperties = new LinkProperties();
-                            }
-                            mLinkCapabilities = intent.getParcelableExtra(
-                                    PhoneConstants.DATA_LINK_CAPABILITIES_KEY);
-                            if (mLinkCapabilities == null) {
-                                loge("CONNECTED event did not supply link capabilities.");
-                                mLinkCapabilities = new LinkCapabilities();
-                            }
+                            updateLinkProperitesAndCapatilities(intent);
                             setDetailedState(DetailedState.CONNECTED, reason, apnName);
                             break;
                     }
@@ -269,18 +291,13 @@
                     equals(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED)) {
                 String apnType = intent.getStringExtra(PhoneConstants.DATA_APN_TYPE_KEY);
                 if (!TextUtils.equals(apnType, mApnType)) {
-                    if (DBG) {
-                        log(String.format(
-                                "Broadcast received: ACTION_ANY_DATA_CONNECTION_FAILED ignore, " +
-                                "mApnType=%s != received apnType=%s", mApnType, apnType));
-                    }
                     return;
                 }
                 String reason = intent.getStringExtra(PhoneConstants.FAILURE_REASON_KEY);
                 String apnName = intent.getStringExtra(PhoneConstants.DATA_APN_KEY);
                 if (DBG) {
-                    log("Received " + intent.getAction() +
-                                " broadcast" + reason == null ? "" : "(" + reason + ")");
+                    log("Broadcast received: " + intent.getAction() +
+                                " reason=" + reason == null ? "null" : reason);
                 }
                 setDetailedState(DetailedState.FAILED, reason, apnName);
             } else {
@@ -372,11 +389,27 @@
         return (setEnableApn(mApnType, false) != PhoneConstants.APN_REQUEST_FAILED);
     }
 
+    /**
+     * @return true if this is ready to operate
+     */
+    public boolean isReady() {
+        return mDataConnectionTrackerAc != null;
+    }
+
     @Override
     public void captivePortalCheckComplete() {
         // not implemented
     }
 
+    @Override
+    public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+        if (mIsCaptivePortal.getAndSet(isCaptivePortal) != isCaptivePortal) {
+            // Captive portal change enable/disable failing fast
+            setEnableFailFastMobileData(
+                    isCaptivePortal ? DctConstants.ENABLED : DctConstants.DISABLED);
+        }
+    }
+
     /**
      * Record the detailed state of a network, and if it is a
      * change from the previous state, send a notification to
@@ -525,6 +558,40 @@
         }
     }
 
+    /**
+     *  Inform DCT mobile provisioning has started, it ends when provisioning completes.
+     */
+    public void enableMobileProvisioning(String url) {
+        if (DBG) log("enableMobileProvisioning(url=" + url + ")");
+        final AsyncChannel channel = mDataConnectionTrackerAc;
+        if (channel != null) {
+            Message msg = Message.obtain();
+            msg.what = DctConstants.CMD_ENABLE_MOBILE_PROVISIONING;
+            msg.setData(Bundle.forPair(DctConstants.PROVISIONING_URL_KEY, url));
+            channel.sendMessage(msg);
+        }
+    }
+
+    /**
+     * Return if this network is the provisioning network. Valid only if connected.
+     * @param met
+     */
+    public boolean isProvisioningNetwork() {
+        boolean retVal;
+        try {
+            Message msg = Message.obtain();
+            msg.what = DctConstants.CMD_IS_PROVISIONING_APN;
+            msg.setData(Bundle.forPair(DctConstants.APN_TYPE_KEY, mApnType));
+            Message result = mDataConnectionTrackerAc.sendMessageSynchronously(msg);
+            retVal = result.arg1 == DctConstants.ENABLED;
+        } catch (NullPointerException e) {
+            loge("isProvisioningNetwork: X " + e);
+            retVal = false;
+        }
+        if (DBG) log("isProvisioningNetwork: retVal=" + retVal);
+        return retVal;
+    }
+
     @Override
     public void addStackedLink(LinkProperties link) {
         mLinkProperties.addStackedLink(link);
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 689dae5..dabc73a 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -84,6 +84,12 @@
         VERIFYING_POOR_LINK,
         /** Checking if network is a captive portal */
         CAPTIVE_PORTAL_CHECK,
+        /**
+         * Network is connected to provisioning network
+         * TODO: Probably not needed when we add TYPE_PROVISIONING_NETWORK
+         * @hide
+         */
+        CONNECTED_TO_PROVISIONING_NETWORK
     }
 
     /**
@@ -108,6 +114,7 @@
         stateMap.put(DetailedState.DISCONNECTED, State.DISCONNECTED);
         stateMap.put(DetailedState.FAILED, State.DISCONNECTED);
         stateMap.put(DetailedState.BLOCKED, State.DISCONNECTED);
+        stateMap.put(DetailedState.CONNECTED_TO_PROVISIONING_NETWORK, State.CONNECTED);
     }
 
     private int mNetworkType;
diff --git a/core/java/android/net/NetworkStateTracker.java b/core/java/android/net/NetworkStateTracker.java
index cf77a1c..9ed7533 100644
--- a/core/java/android/net/NetworkStateTracker.java
+++ b/core/java/android/net/NetworkStateTracker.java
@@ -144,6 +144,11 @@
     public void captivePortalCheckComplete();
 
     /**
+     * Captive portal check has completed
+     */
+    public void captivePortalCheckCompleted(boolean isCaptive);
+
+    /**
      * Turn the wireless radio off for a network.
      * @param turnOn {@code true} to turn the radio on, {@code false}
      */
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 2a2f7cf..0fe5cc4 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -81,9 +81,6 @@
         }
     };
 
-    private static final HostnameVerifier HOSTNAME_VERIFIER =
-        HttpsURLConnection.getDefaultHostnameVerifier();
-
     private SSLSocketFactory mInsecureFactory = null;
     private SSLSocketFactory mSecureFactory = null;
     private TrustManager[] mTrustManagers = null;
@@ -195,7 +192,7 @@
             if (session == null) {
                 throw new SSLException("Cannot verify SSL socket without session");
             }
-            if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
+            if (!HttpsURLConnection.getDefaultHostnameVerifier().verify(hostname, session)) {
                 throw new SSLPeerUnverifiedException("Cannot verify hostname: " + hostname);
             }
         }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 4bff536..c23012a 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -151,6 +151,7 @@
     <protected-broadcast android:name="android.intent.action.DREAMING_STARTED" />
     <protected-broadcast android:name="android.intent.action.DREAMING_STOPPED" />
     <protected-broadcast android:name="android.intent.action.ANY_DATA_STATE" />
+    <protected-broadcast android:name="android.intent.action.DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN" />
 
     <protected-broadcast android:name="com.android.server.WifiManager.action.START_SCAN" />
     <protected-broadcast android:name="com.android.server.WifiManager.action.DELAYED_DRIVER_STOP" />
@@ -181,6 +182,9 @@
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_START" />
     <protected-broadcast android:name="android.intent.action.ACTION_IDLE_MAINTENANCE_END" />
 
+    <protected-broadcast
+        android:name="com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION" />
+
     <!-- ====================================== -->
     <!-- Permissions for things that cost money -->
     <!-- ====================================== -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4572f5b..707590b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -772,6 +772,10 @@
     <!-- IP address of the dns server to use if nobody else suggests one -->
     <string name="config_default_dns_server" translatable="false">8.8.8.8</string>
 
+    <!-- The default mobile provisioning apn. Empty by default, maybe overridden by
+         an mcc/mnc specific config.xml -->
+    <string name="mobile_provisioning_apn" translatable="false"></string>
+
     <!-- The default mobile provisioning url. Empty by default, maybe overridden by
          an mcc/mnc specific config.xml -->
     <string name="mobile_provisioning_url" translatable="false"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b885d56..a67c2af 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -703,6 +703,7 @@
   <java-symbol type="string" name="preposition_for_time" />
   <java-symbol type="string" name="progress_erasing" />
   <java-symbol type="string" name="progress_unmounting" />
+  <java-symbol type="string" name="mobile_provisioning_apn" />
   <java-symbol type="string" name="mobile_provisioning_url" />
   <java-symbol type="string" name="mobile_redirected_provisioning_url" />
   <java-symbol type="string" name="reboot_safemode_confirm" />
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
index f3a91c5..ee867ff 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaNames.java
@@ -157,4 +157,11 @@
     public static final int STREAM_H264_480_360_1411k_DURATION = 46000;
     public static final int VIDEO_H263_AAC_DURATION = 501000;
     public static final int VIDEO_H263_AMR_DURATION = 502000;
+
+    // Video files for WiFi IOT video streaming test.
+    public static final String[] NETWORK_VIDEO_FILES = {
+            "H264_BP_720x480_25fps_256kbps_AMRNB_8khz_12.2kbps_m_0_26.mp4",
+            "MPEG4_SP_720x480_30fps_280kbps_AACLC_48kHz_161kbps_s_0_26.mp4",
+            "MPEG4_SP_720x480_30fps_280kbps_AACLC_48kHz_96kbps_s_0_21.mp4"
+    };
 }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
index 67da6ac..e289812 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/stress/MediaPlayerStressTest.java
@@ -17,18 +17,13 @@
 package com.android.mediaframeworktest.stress;
 
 import com.android.mediaframeworktest.MediaFrameworkTest;
+import com.android.mediaframeworktest.MediaPlayerStressTestRunner;
 
-import android.app.Activity;
-import android.app.Instrumentation;
-import android.content.Intent;
-import android.hardware.Camera;
-import android.media.MediaPlayer;
-import android.media.MediaRecorder;
+import android.os.Bundle;
 import android.os.Environment;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
-import android.view.SurfaceHolder;
 
 import com.android.mediaframeworktest.MediaNames;
 import com.android.mediaframeworktest.functional.CodecTest;
@@ -38,14 +33,12 @@
 import java.io.FileWriter;
 import java.io.Writer;
 
-import android.test.AndroidTestCase;
-import android.test.InstrumentationTestCase;
-
 /**
  * Junit / Instrumentation test case for the media player
  */
 public class MediaPlayerStressTest extends ActivityInstrumentationTestCase2<MediaFrameworkTest> {
     private String TAG = "MediaPlayerStressTest";
+    private String mMediaSrc;
 
     public MediaPlayerStressTest() {
         super("com.android.mediaframeworktest", MediaFrameworkTest.class);
@@ -56,6 +49,12 @@
         //the workaround for the race condition of requesting the updated surface.
         Thread.sleep(2000);
         getActivity();
+        MediaPlayerStressTestRunner mRunner = (MediaPlayerStressTestRunner)getInstrumentation();
+        Bundle arguments = mRunner.getArguments();
+        mMediaSrc = arguments.getString("media-source");
+        if (mMediaSrc == null) {
+            mMediaSrc = MediaNames.MEDIA_SAMPLE_POOL;
+        }
         super.setUp();
     }
 
@@ -119,17 +118,20 @@
         boolean testResult = true;
         // load directory files
         boolean onCompleteSuccess = false;
-        File dir = new File(MediaNames.MEDIA_SAMPLE_POOL);
-        String[] children = dir.list();
+        String[] children = MediaNames.NETWORK_VIDEO_FILES;
+        if (MediaNames.MEDIA_SAMPLE_POOL.equals(mMediaSrc)) {
+            File dir = new File(mMediaSrc);
+            children = dir.list();
+        }
         if (children == null) {
             Log.v("MediaPlayerApiTest:testMediaSamples", "dir is empty");
             return;
         } else {
             for (int i = 0; i < children.length; i++) {
-                //Get filename of directory
+                //Get filename
                 String filename = children[i];
                 onCompleteSuccess =
-                    CodecTest.playMediaSamples(dir + "/" + filename);
+                    CodecTest.playMediaSamples(mMediaSrc + filename);
                 if (!onCompleteSuccess){
                     //Don't fail the test right away, print out the failure file.
                     fileWithError += filename + '\n';
diff --git a/services/java/com/android/server/AppOpsService.java b/services/java/com/android/server/AppOpsService.java
index 20ad636..a55fddc 100644
--- a/services/java/com/android/server/AppOpsService.java
+++ b/services/java/com/android/server/AppOpsService.java
@@ -380,12 +380,14 @@
         HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks = null;
         synchronized (this) {
             boolean changed = false;
-            for (int i=0; i<mUidOps.size(); i++) {
+            for (int i=mUidOps.size()-1; i>=0; i--) {
                 HashMap<String, Ops> packages = mUidOps.valueAt(i);
-                for (Map.Entry<String, Ops> ent : packages.entrySet()) {
+                Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator();
+                while (it.hasNext()) {
+                    Map.Entry<String, Ops> ent = it.next();
                     String packageName = ent.getKey();
                     Ops pkgOps = ent.getValue();
-                    for (int j=0; j<pkgOps.size(); j++) {
+                    for (int j=pkgOps.size()-1; j>=0; j--) {
                         Op curOp = pkgOps.valueAt(j);
                         if (curOp.mode != AppOpsManager.MODE_ALLOWED) {
                             curOp.mode = AppOpsManager.MODE_ALLOWED;
@@ -394,9 +396,17 @@
                                     mOpModeWatchers.get(curOp.op));
                             callbacks = addCallbacks(callbacks, packageName, curOp.op,
                                     mPackageModeWatchers.get(packageName));
-                            pruneOp(curOp, mUidOps.keyAt(i), packageName);
+                            if (curOp.time == 0 && curOp.rejectTime == 0) {
+                                pkgOps.removeAt(j);
+                            }
                         }
                     }
+                    if (pkgOps.size() == 0) {
+                        it.remove();
+                    }
+                }
+                if (packages.size() == 0) {
+                    mUidOps.removeAt(i);
                 }
             }
             if (changed) {
diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java
index cb4e89c..ee23b13 100644
--- a/services/java/com/android/server/ConnectivityService.java
+++ b/services/java/com/android/server/ConnectivityService.java
@@ -35,6 +35,7 @@
 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;
@@ -143,6 +144,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Random;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
@@ -383,9 +385,6 @@
 
     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
@@ -609,8 +608,9 @@
         mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY);
         mSettingsObserver.observe(mContext);
 
-        mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this);
-        loadGlobalProxy();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION);
+        mContext.registerReceiver(mProvisioningReceiver, filter);
     }
 
     /**
@@ -879,6 +879,46 @@
         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.getDetailedState()
+                    == NetworkInfo.DetailedState.CONNECTED_TO_PROVISIONING_NETWORK) {
+                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)) {
@@ -1241,8 +1281,10 @@
                                 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 {
@@ -1254,9 +1296,11 @@
                             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) {
@@ -1290,11 +1334,12 @@
             }
         }
         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 - not a live request, ignoring");
+            if (VDBG) log("stopUsingNetworkFeature: X not a live request, ignoring");
             return 1;
         }
     }
@@ -1849,6 +1894,9 @@
          */
         if (mNetConfigs[prevNetType].isDefault()) {
             if (mActiveDefaultNetwork == prevNetType) {
+                if (DBG) {
+                    log("tryFailover: set mActiveDefaultNetwork=-1, prevNetType=" + prevNetType);
+                }
                 mActiveDefaultNetwork = -1;
             }
 
@@ -2041,6 +2089,9 @@
     }
 
     void systemReady() {
+        mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this);
+        loadGlobalProxy();
+
         synchronized(this) {
             mSystemReady = true;
             if (mInitialBroadcast != null) {
@@ -2071,10 +2122,11 @@
     };
 
     private boolean isNewNetTypePreferredOverCurrentNetType(int type) {
-        if ((type != mNetworkPreference &&
-                    mNetConfigs[mActiveDefaultNetwork].priority >
-                    mNetConfigs[type].priority) ||
-                mNetworkPreference == mActiveDefaultNetwork) return false;
+        if (((type != mNetworkPreference)
+                      && (mNetConfigs[mActiveDefaultNetwork].priority > mNetConfigs[type].priority))
+                   || (mNetworkPreference == mActiveDefaultNetwork)) {
+            return false;
+        }
         return true;
     }
 
@@ -2088,6 +2140,11 @@
         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()) {
@@ -2169,15 +2226,26 @@
             }
         }
 
+        if (DBG) log("handleCaptivePortalTrackerCheck: call captivePortalCheckComplete ni=" + info);
         thisNet.captivePortalCheckComplete();
     }
 
     /** @hide */
+    @Override
     public void captivePortalCheckComplete(NetworkInfo info) {
         enforceConnectivityInternalPermission();
+        if (DBG) log("captivePortalCheckComplete: ni=" + info);
         mNetTrackers[info.getType()].captivePortalCheckComplete();
     }
 
+    /** @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.
      *
@@ -2238,6 +2306,10 @@
      */
     private void handleConnectivityChange(int netType, boolean doReset) {
         int resetMask = doReset ? NetworkUtils.RESET_ALL_ADDRESSES : 0;
+        if (VDBG) {
+            log("handleConnectivityChange: netType=" + netType + " doReset=" + doReset
+                    + " resetMask=" + resetMask);
+        }
 
         /*
          * If a non-default network is enabled, add the host routes that
@@ -2305,7 +2377,9 @@
         boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault());
 
         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) {
@@ -2338,6 +2412,7 @@
         // 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
@@ -2391,6 +2466,7 @@
 
         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) {
@@ -2435,7 +2511,6 @@
                 // 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) {
@@ -2725,9 +2800,8 @@
         public void handleMessage(Message msg) {
             NetworkInfo info;
             switch (msg.what) {
-                case NetworkStateTracker.EVENT_STATE_CHANGED:
+                case NetworkStateTracker.EVENT_STATE_CHANGED: {
                     info = (NetworkInfo) msg.obj;
-                    int type = info.getType();
                     NetworkInfo.State state = info.getState();
 
                     if (VDBG || (state == NetworkInfo.State.CONNECTED) ||
@@ -2737,15 +2811,17 @@
                             state + "/" + info.getDetailedState());
                     }
 
-                    // After booting we'll check once for mobile provisioning
-                    // if we've provisioned by and connected.
-                    if (!mFirstProvisioningCheckStarted
+                    // 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)) {
-                        log("check provisioning after booting");
-                        mFirstProvisioningCheckStarted = true;
-                        checkMobileProvisioning(true, CheckMp.MAX_TIMEOUT_MS, null);
+                        checkMobileProvisioning(CheckMp.MAX_TIMEOUT_MS);
                     }
 
                     EventLogTags.writeConnectivityStateChanged(
@@ -2757,6 +2833,30 @@
                     } else if (info.getDetailedState() ==
                             DetailedState.CAPTIVE_PORTAL_CHECK) {
                         handleCaptivePortalTrackerCheck(info);
+                    } else if (info.getDetailedState() ==
+                            DetailedState.CONNECTED_TO_PROVISIONING_NETWORK) {
+                        /**
+                         * 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) {
@@ -2775,18 +2875,21 @@
                         mLockdownTracker.onNetworkInfoChanged(info);
                     }
                     break;
-                case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED:
+                }
+                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:
+                }
+                case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: {
                     info = (NetworkInfo) msg.obj;
-                    type = info.getType();
+                    int type = info.getType();
                     updateNetworkSettings(mNetTrackers[type]);
                     break;
+                }
             }
         }
     }
@@ -3561,72 +3664,153 @@
                          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.
+     */
+    public static final int CMP_RESULT_CODE_NO_CONNECTION = 0;
+
+    /**
+     * A connection was made to the internet, all is well.
+     */
+    public static final int CMP_RESULT_CODE_CONNECTABLE = 1;
+
+    /**
+     * 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.
+     */
+    public static final int CMP_RESULT_CODE_REDIRECTED = 2;
+
+    /**
+     * A connection was made but no dns server was available to resolve a name to address.
+     * This is an indication of a warm sim on a mobile network.
+     */
+    public static final int CMP_RESULT_CODE_NO_DNS = 3;
+
+    /**
+     * A connection was made but could not open a TCP connection.
+     * This is an indication of a warm sim on a mobile network.
+     */
+    public static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 4;
+
+    /**
+     * The mobile network is a provisioning network.
+     * This is an indication of a warm sim on a mobile network.
+     */
+    public static final int CMP_RESULT_CODE_PROVISIONING_NETWORK = 5;
+
+    AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false);
+
     @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;
-        }
+    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 notification off
+            setProvNotificationVisible(false, ConnectivityManager.TYPE_NONE, null, null);
+
+            // See if we've alreadying determined if we've got a provsioning connection
+            // if so we don't need to do anything active
+            MobileDataStateTracker mdstDefault = (MobileDataStateTracker)
+                    mNetTrackers[ConnectivityManager.TYPE_MOBILE];
+            boolean isDefaultProvisioning = mdstDefault.isProvisioningNetwork();
+
+            MobileDataStateTracker mdstHipri = (MobileDataStateTracker)
+                    mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI];
+            boolean isHipriProvisioning = mdstHipri.isProvisioningNetwork();
+
+            if (isDefaultProvisioning || isHipriProvisioning) {
+                if (mIsNotificationVisible) {
+                    if (DBG) {
+                        log("checkMobileProvisioning: provisioning-ignore notification is visible");
+                    }
+                } else {
+                    NetworkInfo ni = null;
+                    if (isDefaultProvisioning) {
+                        ni = mdstDefault.getNetworkInfo();
+                    }
+                    if (isHipriProvisioning) {
+                        ni = mdstHipri.getNetworkInfo();
+                    }
+                    String url = getMobileProvisioningUrl();
+                    if ((ni != null) && (!TextUtils.isEmpty(url))) {
+                        setProvNotificationVisible(true, ni.getType(), ni.getExtraInfo(), url);
+                    } else {
+                        if (DBG) log("checkMobileProvisioning: provisioning but no url, ignore");
+                    }
+                }
+                mIsCheckingMobileProvisioning.set(false);
+                return timeOutMs;
+            }
+
             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);
-                    }
+                    if (DBG) log("CheckMp.onComplete: result=" + result);
                     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");
+                        case CMP_RESULT_CODE_CONNECTABLE:
+                        case CMP_RESULT_CODE_NO_CONNECTION: {
+                            if (DBG) log("CheckMp.onComplete: ignore, connected or no connection");
                             break;
                         }
-                        case ConnectivityManager.CMP_RESULT_CODE_REDIRECTED: {
-                            log("CheckMp.onComplete: warm sim");
+                        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) {
-                                log("CheckMp.onComplete: warm sim (redirected), url=" + url);
-                                setNotificationVisible(true, ni, url);
+                                if (DBG) log("CheckMp.onComplete: warm (redirected), url=" + url);
+                                setProvNotificationVisible(true, ni.getType(), ni.getExtraInfo(),
+                                        url);
                             } else {
-                                log("CheckMp.onComplete: warm sim (redirected), no url");
+                                if (DBG) log("CheckMp.onComplete: warm (redirected), no url");
                             }
                             break;
                         }
-                        case ConnectivityManager.CMP_RESULT_CODE_NO_DNS:
-                        case ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION: {
+                        case CMP_RESULT_CODE_NO_DNS:
+                        case 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);
+                                if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), url=" + url);
+                                setProvNotificationVisible(true, ni.getType(), ni.getExtraInfo(),
+                                        url);
                             } else {
-                                log("CheckMp.onComplete: warm sim (no dns/tcp), no url");
+                                if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url");
                             }
                             break;
                         }
@@ -3635,16 +3819,16 @@
                             break;
                         }
                     }
+                    mIsCheckingMobileProvisioning.set(false);
                 }
             };
             CheckMp.Params params =
                     new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb);
-            log("checkMobileProvisioning: params=" + params);
-            setNotificationVisible(false, null, null);
+            if (DBG) log("checkMobileProvisioning: params=" + params);
             checkMp.execute(params);
         } finally {
             Binder.restoreCallingIdentity(token);
-            log("checkMobileProvisioning: X");
+            if (DBG) log("checkMobileProvisioning: X");
         }
         return timeOutMs;
     }
@@ -3716,27 +3900,54 @@
          * a known address that fetches the data we expect.
          */
         private synchronized Integer isMobileOk(Params params) {
-            Integer result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+            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) {
+                log("isMobileOk: not mobile capable");
+                result = CMP_RESULT_CODE_NO_CONNECTION;
+                return result;
+            }
+
             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;
+
+                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(1);
+                    }
+                }
+
+                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(1);
+                }
+
+                // Continue trying to connect until time has run out
                 while(SystemClock.elapsedRealtime() < endTime) {
                     try {
                         // Wait for hipri to connect.
@@ -3745,13 +3956,26 @@
                         NetworkInfo.State state = mCs
                                 .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState();
                         if (state != NetworkInfo.State.CONNECTED) {
-                            log("isMobileOk: not connected ni=" +
+                            if (true/*VDBG*/) {
+                                log("isMobileOk: not connected ni=" +
                                     mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI));
+                            }
                             sleep(1);
-                            result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION;
+                            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()) {
+                            if (DBG) log("isMobileOk: isProvisioningNetwork is true, no TCP conn");
+                            result = CMP_RESULT_CODE_NO_TCP_CONNECTION;
+                            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 is will try every address but that will bypass the
@@ -3762,7 +3986,7 @@
                             addresses = InetAddress.getAllByName(orgUri.getHost());
                         } catch (UnknownHostException e) {
                             log("isMobileOk: UnknownHostException");
-                            result = ConnectivityManager.CMP_RESULT_CODE_NO_DNS;
+                            result = CMP_RESULT_CODE_NO_DNS;
                             return result;
                         }
                         log("isMobileOk: addresses=" + inetAddressesToString(addresses));
@@ -3827,9 +4051,9 @@
                                 urlConn.setRequestProperty("Connection", "close");
                                 int responseCode = urlConn.getResponseCode();
                                 if (responseCode == 204) {
-                                    result = ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE;
+                                    result = CMP_RESULT_CODE_CONNECTABLE;
                                 } else {
-                                    result = ConnectivityManager.CMP_RESULT_CODE_REDIRECTED;
+                                    result = CMP_RESULT_CODE_REDIRECTED;
                                 }
                                 log("isMobileOk: connected responseCode=" + responseCode);
                                 urlConn.disconnect();
@@ -3843,7 +4067,7 @@
                                 }
                             }
                         }
-                        result = ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION;
+                        result = CMP_RESULT_CODE_NO_TCP_CONNECTION;
                         log("isMobileOk: loops|timed out");
                         return result;
                     } catch (Exception e) {
@@ -3857,6 +4081,23 @@
                 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(1);
+                        continue;
+                    }
+                }
+
                 log("isMobileOk: X result=" + result);
             }
             return result;
@@ -3936,10 +4177,55 @@
         }
     }
 
-    private static final String NOTIFICATION_ID = "CaptivePortal.Notification";
+    // 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 void setNotificationVisible(boolean visible, NetworkInfo networkInfo, String url) {
-        log("setNotificationVisible: E visible=" + visible + " ni=" + networkInfo + " url=" + url);
+    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_NONE, null, null);
+
+        // If provisioning network handle as a special case,
+        // otherwise launch browser with the intent directly.
+        NetworkInfo ni = getProvisioningNetworkInfo();
+        if ((ni != null) && ni.getDetailedState() ==
+                    NetworkInfo.DetailedState.CONNECTED_TO_PROVISIONING_NETWORK) {
+            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");
+            Intent newIntent =
+                    new Intent(Intent.ACTION_VIEW, 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
@@ -3949,50 +4235,64 @@
             CharSequence title;
             CharSequence details;
             int icon;
-            switch (networkInfo.getType()) {
+            Intent intent;
+            Notification notification = new Notification();
+            switch (networkType) {
                 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());
+                            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:
-                    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;
+                    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:
-                    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());
+                            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 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);
+            try {
+                notificationManager.notify(NOTIFICATION_ID, 1, notification);
+            } catch (NullPointerException npe) {
+                loge("setNotificaitionVisible: visible notificationManager npe=" + npe);
+                npe.printStackTrace();
+            }
         } else {
-            log("setNotificaitionVisible: cancel");
-            notificationManager.cancel(NOTIFICATION_ID, 1);
+            try {
+                notificationManager.cancel(NOTIFICATION_ID, 1);
+            } catch (NullPointerException npe) {
+                loge("setNotificaitionVisible: cancel notificationManager npe=" + npe);
+                npe.printStackTrace();
+            }
         }
-        log("setNotificationVisible: X visible=" + visible + " ni=" + networkInfo + " url=" + url);
+        mIsNotificationVisible = visible;
     }
 
     /** Location to an updatable file listing carrier provisioning urls.
@@ -4086,7 +4386,9 @@
         return null;
     }
 
-    private String getMobileRedirectedProvisioningUrl() {
+    @Override
+    public String getMobileRedirectedProvisioningUrl() {
+        enforceConnectivityInternalPermission();
         String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING);
         if (TextUtils.isEmpty(url)) {
             url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url);
@@ -4094,14 +4396,15 @@
         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("getProvisioningUrl: mobile_provisioining_url from resource =" + url);
+            log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url);
         } else {
-            log("getProvisioningUrl: mobile_provisioning_url from File =" + url);
+            log("getMobileProvisioningUrl: mobile_provisioning_url from File =" + url);
         }
         // populate the iccid, imei and phone number in the provisioning url.
         if (!TextUtils.isEmpty(url)) {
@@ -4117,4 +4420,11 @@
 
         return url;
     }
+
+    @Override
+    public void setProvisioningNotificationVisible(boolean visible, int networkType,
+            String extraInfo, String url) {
+        enforceConnectivityInternalPermission();
+        setProvNotificationVisible(visible, networkType, extraInfo, url);
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 4d8342c..eb93e9c 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -94,6 +94,8 @@
     public static final int EVENT_DISCONNECT_DC_RETRYING = BASE + 34;
     public static final int EVENT_DATA_SETUP_COMPLETE_ERROR = BASE + 35;
     public static final int CMD_SET_ENABLE_FAIL_FAST_MOBILE_DATA = BASE + 36;
+    public static final int CMD_ENABLE_MOBILE_PROVISIONING = BASE + 37;
+    public static final int CMD_IS_PROVISIONING_APN = BASE + 38;
 
     /***** Constants *****/
 
@@ -112,4 +114,5 @@
     public static final int ENABLED = 1;
 
     public static final String APN_TYPE_KEY = "apnType";
+    public static final String PROVISIONING_URL_KEY = "provisioningUrl";
 }
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 3cfd0bf..40a3c8f 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -145,6 +145,29 @@
     public static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED
             = "android.intent.action.ANY_DATA_STATE";
 
+    /**
+     * Broadcast Action: Occurs when a data connection connects to a provisioning apn
+     * and is broadcast by the low level data connection code.
+     * The intent will have the following extra values:</p>
+     * <ul>
+     *   <li><em>apn</em> - A string that is the APN associated with this
+     *      connection.</li>
+     *   <li><em>apnType</em> - A string array of APN types associated with
+     *      this connection.  The APN type <code>"*"</code> is a special
+     *      type that means this APN services all types.</li>
+     *   <li><em>linkProperties</em> - The <code>LinkProperties</code> for this APN</li>
+     *   <li><em>linkCapabilities</em> - The <code>linkCapabilities</code> for this APN</li>
+     *   <li><em>iface</em> - A string that is the name of the interface</li>
+     * </ul>
+     *
+     * <p class="note">
+     * Requires the READ_PHONE_STATE permission.
+     *
+     * <p class="note">This is a protected intent that can only be sent
+     * by the system.
+     */
+    public static final String ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN
+            = "android.intent.action.DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN";
 
     /**
      * Broadcast Action: An attempt to establish a data connection has failed.
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 0077354..6589ff5 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -3166,6 +3166,7 @@
     class VerifyingLinkState extends State {
         @Override
         public void enter() {
+            log(getName() + " enter");
             setNetworkDetailedState(DetailedState.VERIFYING_POOR_LINK);
             mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.VERIFYING_POOR_LINK);
             sendNetworkStateChangeBroadcast(mLastBssid);
@@ -3175,11 +3176,14 @@
             switch (message.what) {
                 case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
                     //stay here
+                    log(getName() + " POOR_LINK_DETECTED: no transition");
                     break;
                 case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
+                    log(getName() + " GOOD_LINK_DETECTED: transition to captive portal check");
                     transitionTo(mCaptivePortalCheckState);
                     break;
                 default:
+                    log(getName() + " what=" + message.what + " NOT_HANDLED");
                     return NOT_HANDLED;
             }
             return HANDLED;
@@ -3189,6 +3193,7 @@
     class CaptivePortalCheckState extends State {
         @Override
         public void enter() {
+            log(getName() + " enter");
             setNetworkDetailedState(DetailedState.CAPTIVE_PORTAL_CHECK);
             mWifiConfigStore.updateStatus(mLastNetworkId, DetailedState.CAPTIVE_PORTAL_CHECK);
             sendNetworkStateChangeBroadcast(mLastBssid);
@@ -3197,6 +3202,7 @@
         public boolean processMessage(Message message) {
             switch (message.what) {
                 case CMD_CAPTIVE_CHECK_COMPLETE:
+                    log(getName() + " CMD_CAPTIVE_CHECK_COMPLETE");
                     try {
                         mNwService.enableIpv6(mInterfaceName);
                     } catch (RemoteException re) {
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index cf75381..461dedb 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -120,6 +120,11 @@
         mWifiManager.captivePortalCheckComplete();
     }
 
+    @Override
+    public void captivePortalCheckCompleted(boolean isCaptivePortal) {
+        // not implemented
+    }
+
     /**
      * Turn the wireless radio off for a network.
      * @param turnOn {@code true} to turn the radio on, {@code false}