Add captive portal API.

This API allows apps other than the system's CaptivePortalLogin
to handle signing in to captive portals.

bug:19416463
Change-Id: I27fce5856b635233e6ff66396d50ccabedd76cf5
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index cab893b..7b758bb 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -110,6 +110,35 @@
             "android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE";
 
     /**
+     * The device has connected to a network that has presented a captive
+     * 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
+     * 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
+     * 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}.
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_CAPTIVE_PORTAL_SIGN_IN = "android.net.conn.CAPTIVE_PORTAL";
+
+    /**
      * The lookup key for a {@link NetworkInfo} object. Retrieve with
      * {@link android.content.Intent#getParcelableExtra(String)}.
      *
@@ -172,6 +201,15 @@
     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)}.
+     */
+    public static final String EXTRA_CAPTIVE_PORTAL_TOKEN = "captivePortalToken";
+
+    /**
      * Broadcast action to indicate the change of data activity status
      * (idle or active) on a network in a recent period.
      * The network becomes active when data transmission is started, or
@@ -1751,6 +1789,82 @@
         }
     }
 
+    /** {@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/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index e94ee06..6e06aa5 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -97,6 +97,8 @@
 
     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/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d83ef6f..ffda5a7 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -2493,6 +2493,16 @@
         }
     }
 
+    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);
+    }
+
     public 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