Add android.net.CaptivePortal class for captive-portal-handling-app callbacks

This new class replaces the awkward string token and ConnectivityManager APIs
used by apps handling captive portals.

Bug:21343774
Change-Id: I1a2c69edb17322715bf8422bb4216b0ea60bfd59
diff --git a/Android.mk b/Android.mk
index e96a932..1de3625 100644
--- a/Android.mk
+++ b/Android.mk
@@ -181,6 +181,7 @@
 	core/java/android/hardware/location/IGeofenceHardwareMonitorCallback.aidl \
 	core/java/android/hardware/soundtrigger/IRecognitionStatusCallback.aidl \
 	core/java/android/hardware/usb/IUsbManager.aidl \
+	core/java/android/net/ICaptivePortal.aidl \
 	core/java/android/net/IConnectivityManager.aidl \
 	core/java/android/net/IEthernetManager.aidl \
 	core/java/android/net/IEthernetServiceListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index 8a82ed0..d197f3d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -18115,6 +18115,14 @@
 
 package android.net {
 
+  public class CaptivePortal implements android.os.Parcelable {
+    method public int describeContents();
+    method public void ignoreNetwork();
+    method public void reportCaptivePortalDismissed();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
+  }
+
   public class ConnectivityManager {
     method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public boolean bindProcessToNetwork(android.net.Network);
@@ -18131,7 +18139,6 @@
     method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
     method public deprecated int getNetworkPreference();
     method public static deprecated android.net.Network getProcessDefaultNetwork();
-    method public void ignoreNetworkWithCaptivePortal(android.net.Network, java.lang.String);
     method public boolean isActiveNetworkMetered();
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
@@ -18140,7 +18147,6 @@
     method public void releaseNetworkRequest(android.app.PendingIntent);
     method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public deprecated void reportBadNetwork(android.net.Network);
-    method public void reportCaptivePortalDismissed(android.net.Network, java.lang.String);
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
@@ -18152,7 +18158,7 @@
     field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
     field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
     field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
-    field public static final java.lang.String EXTRA_CAPTIVE_PORTAL_TOKEN = "captivePortalToken";
+    field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
     field public static final java.lang.String EXTRA_EXTRA_INFO = "extraInfo";
     field public static final java.lang.String EXTRA_IS_FAILOVER = "isFailover";
     field public static final java.lang.String EXTRA_NETWORK = "android.net.extra.NETWORK";
diff --git a/api/system-current.txt b/api/system-current.txt
index a382fb0..b91069a 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -19630,6 +19630,14 @@
 
 package android.net {
 
+  public class CaptivePortal implements android.os.Parcelable {
+    method public int describeContents();
+    method public void ignoreNetwork();
+    method public void reportCaptivePortalDismissed();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
+  }
+
   public class ConnectivityManager {
     method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public boolean bindProcessToNetwork(android.net.Network);
@@ -19646,7 +19654,6 @@
     method public android.net.NetworkInfo getNetworkInfo(android.net.Network);
     method public deprecated int getNetworkPreference();
     method public static deprecated android.net.Network getProcessDefaultNetwork();
-    method public void ignoreNetworkWithCaptivePortal(android.net.Network, java.lang.String);
     method public boolean isActiveNetworkMetered();
     method public boolean isDefaultNetworkActive();
     method public static deprecated boolean isNetworkTypeValid(int);
@@ -19655,7 +19662,6 @@
     method public void releaseNetworkRequest(android.app.PendingIntent);
     method public void removeDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
     method public deprecated void reportBadNetwork(android.net.Network);
-    method public void reportCaptivePortalDismissed(android.net.Network, java.lang.String);
     method public void reportNetworkConnectivity(android.net.Network, boolean);
     method public boolean requestBandwidthUpdate(android.net.Network);
     method public void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback);
@@ -19667,7 +19673,7 @@
     field public static final java.lang.String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
     field public static final java.lang.String CONNECTIVITY_ACTION = "android.net.conn.CONNECTIVITY_CHANGE";
     field public static final deprecated int DEFAULT_NETWORK_PREFERENCE = 1; // 0x1
-    field public static final java.lang.String EXTRA_CAPTIVE_PORTAL_TOKEN = "captivePortalToken";
+    field public static final java.lang.String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
     field public static final java.lang.String EXTRA_EXTRA_INFO = "extraInfo";
     field public static final java.lang.String EXTRA_IS_FAILOVER = "isFailover";
     field public static final java.lang.String EXTRA_NETWORK = "android.net.extra.NETWORK";
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
new file mode 100644
index 0000000..ee05f28
--- /dev/null
+++ b/core/java/android/net/CaptivePortal.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed urnder the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net;
+
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+
+/**
+ * A class allowing apps handling the {@link ConnectivityManager#ACTION_CAPTIVE_PORTAL_SIGN_IN}
+ * activity to indicate to the system different outcomes of captive portal sign in.  This class is
+ * passed as an extra named {@link ConnectivityManager#EXTRA_CAPTIVE_PORTAL} with the
+ * {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} activity.
+ */
+public class CaptivePortal implements Parcelable {
+    /** @hide */
+    public static final int APP_RETURN_DISMISSED    = 0;
+    /** @hide */
+    public static final int APP_RETURN_UNWANTED     = 1;
+    /** @hide */
+    public static final int APP_RETURN_WANTED_AS_IS = 2;
+
+    private final IBinder mBinder;
+
+    /** @hide */
+    public CaptivePortal(IBinder binder) {
+        mBinder = binder;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeStrongBinder(mBinder);
+    }
+
+    public static final Parcelable.Creator<CaptivePortal> CREATOR
+            = new Parcelable.Creator<CaptivePortal>() {
+        @Override
+        public CaptivePortal createFromParcel(Parcel in) {
+            return new CaptivePortal(in.readStrongBinder());
+        }
+
+        @Override
+        public CaptivePortal[] newArray(int size) {
+            return new CaptivePortal[size];
+        }
+    };
+
+    /**
+     * Indicate to the system that the captive portal has been
+     * dismissed.  In response the framework will re-evaluate the network's
+     * connectivity and might take further action thereafter.
+     */
+    public void reportCaptivePortalDismissed() {
+        try {
+            ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_DISMISSED);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Indicate to the system that the user does not want to pursue signing in to the
+     * captive portal and the system should continue to prefer other networks
+     * without captive portals for use as the default active data network.  The
+     * system will not retest the network for a captive portal so as to avoid
+     * disturbing the user with further sign in to network notifications.
+     */
+    public void ignoreNetwork() {
+        try {
+            ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_UNWANTED);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Indicate to the system the user wants to use this network as is, even though
+     * the captive portal is still in place.  The system will treat the network
+     * as if it did not have a captive portal when selecting the network to use
+     * as the default active data network. This may result in this network
+     * becoming the default active data network, which could disrupt network
+     * connectivity for apps because the captive portal is still in place.
+     * @hide
+     */
+    public void useNetwork() {
+        try {
+            ICaptivePortal.Stub.asInterface(mBinder).appResponse(APP_RETURN_WANTED_AS_IS);
+        } catch (RemoteException e) {
+        }
+    }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index 80476ea..a1bc417 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -102,26 +102,26 @@
      * portal, which is blocking Internet connectivity. The user was presented
      * with a notification that network sign in is required,
      * and the user invoked the notification's action indicating they
-     * desire to sign in to the network. Apps handling this action should
+     * desire to sign in to the network. Apps handling this activity should
      * facilitate signing in to the network. This action includes a
      * {@link Network} typed extra called {@link #EXTRA_NETWORK} that represents
      * the network presenting the captive portal; all communication with the
      * captive portal must be done using this {@code Network} object.
      * <p/>
-     * When the app handling this action believes the user has signed in to
-     * the network and the captive portal has been dismissed, the app should call
-     * {@link #reportCaptivePortalDismissed} so the system can reevaluate the network.
-     * If reevaluation finds the network no longer subject to a captive portal,
-     * the network may become the default active data network.
-     * <p/>
-     * When the app handling this action believes the user explicitly wants
+     * This activity includes a {@link CaptivePortal} extra named
+     * {@link #EXTRA_CAPTIVE_PORTAL} that can be used to indicate different
+     * outcomes of the captive portal sign in to the system:
+     * <ul>
+     * <li> When the app handling this action believes the user has signed in to
+     * the network and the captive portal has been dismissed, the app should
+     * call {@link CaptivePortal#reportCaptivePortalDismissed} so the system can
+     * reevaluate the network. If reevaluation finds the network no longer
+     * subject to a captive portal, the network may become the default active
+     * data network. </li>
+     * <li> When the app handling this action believes the user explicitly wants
      * to ignore the captive portal and the network, the app should call
-     * {@link #ignoreNetworkWithCaptivePortal}.
-     * <p/>
-     * Note that this action includes a {@code String} extra named
-     * {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} that must
-     * be passed in to {@link #reportCaptivePortalDismissed} and
-     * {@link #ignoreNetworkWithCaptivePortal}.
+     * {@link CaptivePortal#ignoreNetwork}. </li>
+     * </ul>
      */
     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
@@ -187,16 +187,15 @@
      * {@hide}
      */
     public static final String EXTRA_INET_CONDITION = "inetCondition";
-
     /**
-     * The lookup key for a string that is sent out with
-     * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}. This string must be
-     * passed in to {@link #reportCaptivePortalDismissed} and
-     * {@link #ignoreNetworkWithCaptivePortal}. Retrieve it with
-     * {@link android.content.Intent#getStringExtra(String)}.
+     * The lookup key for a {@link CaptivePortal} object included with the
+     * {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} intent.  The {@code CaptivePortal}
+     * object can be used to either indicate to the system that the captive
+     * portal has been dismissed or that the user does not want to pursue
+     * signing in to captive portal.  Retrieve it with
+     * {@link android.content.Intent#getParcelableExtra(String)}.
      */
-    public static final String EXTRA_CAPTIVE_PORTAL_TOKEN = "captivePortalToken";
-
+    public static final String EXTRA_CAPTIVE_PORTAL = "android.net.extra.CAPTIVE_PORTAL";
     /**
      * Broadcast action to indicate the change of data activity status
      * (idle or active) on a network in a recent period.
@@ -1779,82 +1778,6 @@
         }
     }
 
-    /** {@hide} */
-    public static final int CAPTIVE_PORTAL_APP_RETURN_DISMISSED    = 0;
-    /** {@hide} */
-    public static final int CAPTIVE_PORTAL_APP_RETURN_UNWANTED     = 1;
-    /** {@hide} */
-    public static final int CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS = 2;
-
-    /**
-     * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}
-     * action to indicate to the system that the captive portal has been
-     * dismissed.  In response the framework will re-evaluate the network's
-     * connectivity and might take further action thereafter.
-     *
-     * @param network The {@link Network} object passed via
-     *                {@link #EXTRA_NETWORK} with the
-     *                {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     * @param actionToken The {@code String} passed via
-     *                    {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the
-     *                    {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     */
-    public void reportCaptivePortalDismissed(Network network, String actionToken) {
-        try {
-            mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_DISMISSED,
-                    actionToken);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
-     * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}
-     * action to indicate that the user does not want to pursue signing in to
-     * captive portal and the system should continue to prefer other networks
-     * without captive portals for use as the default active data network.  The
-     * system will not retest the network for a captive portal so as to avoid
-     * disturbing the user with further sign in to network notifications.
-     *
-     * @param network The {@link Network} object passed via
-     *                {@link #EXTRA_NETWORK} with the
-     *                {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     * @param actionToken The {@code String} passed via
-     *                    {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the
-     *                    {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     */
-    public void ignoreNetworkWithCaptivePortal(Network network, String actionToken) {
-        try {
-            mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_UNWANTED,
-                    actionToken);
-        } catch (RemoteException e) {
-        }
-    }
-
-    /**
-     * Called by an app handling the {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN}
-     * action to indicate the user wants to use this network as is, even though
-     * the captive portal is still in place.  The system will treat the network
-     * as if it did not have a captive portal when selecting the network to use
-     * as the default active data network. This may result in this network
-     * becoming the default active data network, which could disrupt network
-     * connectivity for apps because the captive portal is still in place.
-     *
-     * @param network The {@link Network} object passed via
-     *                {@link #EXTRA_NETWORK} with the
-     *                {@link #ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     * @param actionToken The {@code String} passed via
-     *                    {@link #EXTRA_CAPTIVE_PORTAL_TOKEN} with the
-     *                    {@code ACTION_CAPTIVE_PORTAL_SIGN_IN} action.
-     * @hide
-     */
-    public void useNetworkWithCaptivePortal(Network network, String actionToken) {
-        try {
-            mService.captivePortalAppResponse(network, CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS,
-                    actionToken);
-        } catch (RemoteException e) {
-        }
-    }
-
     /**
      * Set a network-independent global http proxy.  This is not normally what you want
      * for typical HTTP proxies - they are general network dependent.  However if you're
diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl
new file mode 100644
index 0000000..a013e79
--- /dev/null
+++ b/core/java/android/net/ICaptivePortal.aidl
@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2015, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+/**
+ * Interface to inform NetworkMonitor of decisions of app handling captive portal.
+ * @hide
+ */
+interface ICaptivePortal
+{
+    oneway void appResponse(int response);
+}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 29557bb..78f8b95 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -96,8 +96,6 @@
 
     void reportNetworkConnectivity(in Network network, boolean hasConnectivity);
 
-    void captivePortalAppResponse(in Network network, int response, String actionToken);
-
     ProxyInfo getGlobalProxy();
 
     void setGlobalProxy(in ProxyInfo p);
diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
index ddbcd78..a489f94 100644
--- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
+++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
@@ -62,7 +63,7 @@
 
     private URL mURL;
     private Network mNetwork;
-    private String mResponseToken;
+    private CaptivePortal mCaptivePortal;
     private NetworkCallback mNetworkCallback;
     private ConnectivityManager mCm;
     private boolean mLaunchBrowser = false;
@@ -83,7 +84,7 @@
             done(Result.WANTED_AS_IS);
         }
         mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK);
-        mResponseToken = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_TOKEN);
+        mCaptivePortal = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL);
 
         // Also initializes proxy system properties.
         mCm.bindProcessToNetwork(mNetwork);
@@ -155,13 +156,13 @@
         }
         switch (result) {
             case DISMISSED:
-                mCm.reportCaptivePortalDismissed(mNetwork, mResponseToken);
+                mCaptivePortal.reportCaptivePortalDismissed();
                 break;
             case UNWANTED:
-                mCm.ignoreNetworkWithCaptivePortal(mNetwork, mResponseToken);
+                mCaptivePortal.ignoreNetwork();
                 break;
             case WANTED_AS_IS:
-                mCm.useNetworkWithCaptivePortal(mNetwork, mResponseToken);
+                mCaptivePortal.useNetwork();
                 break;
         }
         finish();
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 7f124dc..54d864b 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2733,16 +2733,6 @@
         }
     }
 
-    public void captivePortalAppResponse(Network network, int response, String actionToken) {
-        if (response == ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS) {
-            enforceConnectivityInternalPermission();
-        }
-        final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
-        if (nai == null) return;
-        nai.networkMonitor.sendMessage(NetworkMonitor.CMD_CAPTIVE_PORTAL_APP_FINISHED, response, 0,
-                actionToken);
-    }
-
     private ProxyInfo getDefaultProxy() {
         // 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
diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
index e4729286..a6dc6a3 100644
--- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java
+++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java
@@ -16,6 +16,10 @@
 
 package com.android.server.connectivity;
 
+import static android.net.CaptivePortal.APP_RETURN_DISMISSED;
+import static android.net.CaptivePortal.APP_RETURN_UNWANTED;
+import static android.net.CaptivePortal.APP_RETURN_WANTED_AS_IS;
+
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -23,7 +27,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.net.CaptivePortal;
 import android.net.ConnectivityManager;
+import android.net.ICaptivePortal;
 import android.net.NetworkRequest;
 import android.net.ProxyInfo;
 import android.net.TrafficStats;
@@ -160,12 +166,12 @@
 
     /**
      * Message to self indicating captive portal app finished.
-     * arg1 = one of: CAPTIVE_PORTAL_APP_RETURN_DISMISSED,
-     *                CAPTIVE_PORTAL_APP_RETURN_UNWANTED,
-     *                CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS
+     * arg1 = one of: APP_RETURN_DISMISSED,
+     *                APP_RETURN_UNWANTED,
+     *                APP_RETURN_WANTED_AS_IS
      * obj = mCaptivePortalLoggedInResponseToken as String
      */
-    public static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
+    private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9;
 
     /**
      * Request ConnectivityService display provisioning notification.
@@ -234,7 +240,6 @@
     private final State mLingeringState = new LingeringState();
 
     private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null;
-    private String mCaptivePortalLoggedInResponseToken = null;
 
     private final LocalLog validationLogs = new LocalLog(20); // 20 lines
 
@@ -268,8 +273,6 @@
         mIsCaptivePortalCheckEnabled = Settings.Global.getInt(mContext.getContentResolver(),
                 Settings.Global.CAPTIVE_PORTAL_DETECTION_ENABLED, 1) == 1;
 
-        mCaptivePortalLoggedInResponseToken = String.valueOf(new Random().nextLong());
-
         start();
     }
 
@@ -314,22 +317,18 @@
                     transitionTo(mEvaluatingState);
                     return HANDLED;
                 case CMD_CAPTIVE_PORTAL_APP_FINISHED:
-                    if (!mCaptivePortalLoggedInResponseToken.equals((String)message.obj))
-                        return HANDLED;
                     log("CaptivePortal App responded with " + message.arg1);
-                    // Previous token was sent out, come up with a new one.
-                    mCaptivePortalLoggedInResponseToken = String.valueOf(new Random().nextLong());
                     switch (message.arg1) {
-                        case ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_DISMISSED:
+                        case APP_RETURN_DISMISSED:
                             sendMessage(CMD_FORCE_REEVALUATION, 0 /* no UID */, 0);
                             break;
-                        case ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS:
+                        case APP_RETURN_WANTED_AS_IS:
                             mDontDisplaySigninNotification = true;
                             // TODO: Distinguish this from a network that actually validates.
                             // Displaying the "!" on the system UI icon may still be a good idea.
                             transitionTo(mValidatedState);
                             break;
-                        case ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_UNWANTED:
+                        case APP_RETURN_UNWANTED:
                             mDontDisplaySigninNotification = true;
                             mUserDoesNotWant = true;
                             mConnectivityServiceHandler.sendMessage(obtainMessage(
@@ -380,8 +379,18 @@
                     final Intent intent = new Intent(
                             ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN);
                     intent.putExtra(ConnectivityManager.EXTRA_NETWORK, mNetworkAgentInfo.network);
-                    intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_TOKEN,
-                            mCaptivePortalLoggedInResponseToken);
+                    intent.putExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL,
+                            new CaptivePortal(new ICaptivePortal.Stub() {
+                                @Override
+                                public void appResponse(int response) {
+                                    if (response == APP_RETURN_WANTED_AS_IS) {
+                                        mContext.enforceCallingPermission(
+                                                android.Manifest.permission.CONNECTIVITY_INTERNAL,
+                                                "CaptivePortal");
+                                    }
+                                    sendMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, response);
+                                }
+                            }));
                     intent.setFlags(
                             Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
                     mContext.startActivityAsUser(intent, UserHandle.CURRENT);