diff options
author | Paul Jensen <pauljensen@google.com> | 2015-02-27 22:55:47 -0500 |
---|---|---|
committer | Paul Jensen <pauljensen@google.com> | 2015-04-16 16:53:10 +0000 |
commit | 25a217c0fbda9bbaf58ec08b91115e99f73b727f (patch) | |
tree | ac4d41cd935606dac1fa1a7bb47ee776958143c4 | |
parent | 04b18ec15359b79b832ec50010f050d7298e7fb5 (diff) | |
download | frameworks_base-25a217c0fbda9bbaf58ec08b91115e99f73b727f.zip frameworks_base-25a217c0fbda9bbaf58ec08b91115e99f73b727f.tar.gz frameworks_base-25a217c0fbda9bbaf58ec08b91115e99f73b727f.tar.bz2 |
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
-rw-r--r-- | api/current.txt | 4 | ||||
-rw-r--r-- | api/system-current.txt | 4 | ||||
-rw-r--r-- | core/java/android/net/ConnectivityManager.java | 114 | ||||
-rw-r--r-- | core/java/android/net/IConnectivityManager.aidl | 2 | ||||
-rw-r--r-- | core/res/AndroidManifest.xml | 1 | ||||
-rw-r--r-- | packages/CaptivePortalLogin/AndroidManifest.xml | 4 | ||||
-rw-r--r-- | packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java | 67 | ||||
-rw-r--r-- | services/core/java/com/android/server/ConnectivityService.java | 10 | ||||
-rw-r--r-- | services/core/java/com/android/server/connectivity/NetworkMonitor.java | 128 |
9 files changed, 222 insertions, 112 deletions
diff --git a/api/current.txt b/api/current.txt index 68f673c..fa9a146 100644 --- a/api/current.txt +++ b/api/current.txt @@ -16996,6 +16996,7 @@ package android.net { 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 boolean isNetworkTypeValid(int); @@ -17003,6 +17004,7 @@ package android.net { 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 void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); @@ -17013,8 +17015,10 @@ package android.net { method public deprecated int stopUsingNetworkFeature(int, java.lang.String); method public void unregisterNetworkCallback(android.net.ConnectivityManager.NetworkCallback); field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + 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_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 a6b5d7a..877484d 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -18257,6 +18257,7 @@ package android.net { 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 boolean isNetworkTypeValid(int); @@ -18264,6 +18265,7 @@ package android.net { 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 void requestNetwork(android.net.NetworkRequest, android.net.ConnectivityManager.NetworkCallback); method public void requestNetwork(android.net.NetworkRequest, android.app.PendingIntent); @@ -18274,8 +18276,10 @@ package android.net { method public deprecated int stopUsingNetworkFeature(int, java.lang.String); method public void unregisterNetworkCallback(android.net.ConnectivityManager.NetworkCallback); field public static final deprecated java.lang.String ACTION_BACKGROUND_DATA_SETTING_CHANGED = "android.net.conn.BACKGROUND_DATA_SETTING_CHANGED"; + 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_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/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 @@ public class ConnectivityManager { "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 class ConnectivityManager { 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 @@ public class ConnectivityManager { } } + /** {@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 @@ interface IConnectivityManager 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/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index b1213b3..ed21e80 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -208,6 +208,7 @@ <protected-broadcast android:name="android.intent.action.MEDIA_UNMOUNTABLE" /> <protected-broadcast android:name="android.intent.action.MEDIA_EJECT" /> + <protected-broadcast android:name="android.net.conn.CAPTIVE_PORTAL" /> <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE" /> <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_IMMEDIATE" /> <protected-broadcast android:name="android.net.conn.DATA_ACTIVITY_CHANGE" /> diff --git a/packages/CaptivePortalLogin/AndroidManifest.xml b/packages/CaptivePortalLogin/AndroidManifest.xml index 2ec15be..aea8585 100644 --- a/packages/CaptivePortalLogin/AndroidManifest.xml +++ b/packages/CaptivePortalLogin/AndroidManifest.xml @@ -21,6 +21,7 @@ <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> + <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" /> <application android:label="@string/app_name" > <activity @@ -28,9 +29,8 @@ android:label="@string/action_bar_label" android:theme="@style/AppTheme" > <intent-filter> - <action android:name="android.intent.action.ACTION_SEND"/> + <action android:name="android.net.conn.CAPTIVE_PORTAL"/> <category android:name="android.intent.category.DEFAULT"/> - <data android:mimeType="text/plain"/> </intent-filter> </activity> </application> diff --git a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java index 77765a4..81ff2ab 100644 --- a/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java +++ b/packages/CaptivePortalLogin/src/com/android/captiveportallogin/CaptivePortalLoginActivity.java @@ -58,24 +58,13 @@ public class CaptivePortalLoginActivity extends Activity { private static final String DEFAULT_SERVER = "connectivitycheck.android.com"; private static final int SOCKET_TIMEOUT_MS = 10000; - // Keep this in sync with NetworkMonitor. - // Intent broadcast to ConnectivityService indicating sign-in is complete. - // Extras: - // EXTRA_TEXT = netId - // LOGGED_IN_RESULT = one of the CAPTIVE_PORTAL_APP_RETURN_* values below. - // RESPONSE_TOKEN = data fragment from launching Intent - private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN = - "android.net.netmon.captive_portal_logged_in"; - private static final String LOGGED_IN_RESULT = "result"; - private static final int CAPTIVE_PORTAL_APP_RETURN_APPEASED = 0; - private static final int CAPTIVE_PORTAL_APP_RETURN_UNWANTED = 1; - private static final int CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS = 2; - private static final String RESPONSE_TOKEN = "response_token"; + private enum Result { DISMISSED, UNWANTED, WANTED_AS_IS }; private URL mURL; - private int mNetId; + private Network mNetwork; private String mResponseToken; private NetworkCallback mNetworkCallback; + private ConnectivityManager mCm; @Override protected void onCreate(Bundle savedInstanceState) { @@ -83,23 +72,19 @@ public class CaptivePortalLoginActivity extends Activity { String server = Settings.Global.getString(getContentResolver(), "captive_portal_server"); if (server == null) server = DEFAULT_SERVER; + mCm = ConnectivityManager.from(this); try { mURL = new URL("http", server, "/generate_204"); - final Uri dataUri = getIntent().getData(); - if (!dataUri.getScheme().equals("netid")) { - throw new MalformedURLException(); - } - mNetId = Integer.parseInt(dataUri.getSchemeSpecificPart()); - mResponseToken = dataUri.getFragment(); - } catch (MalformedURLException|NumberFormatException e) { + } catch (MalformedURLException e) { // System misconfigured, bail out in a way that at least provides network access. - done(CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS); + Log.e(TAG, "Invalid captive portal URL, server=" + server); + done(Result.WANTED_AS_IS); } + mNetwork = getIntent().getParcelableExtra(ConnectivityManager.EXTRA_NETWORK); + mResponseToken = getIntent().getStringExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_TOKEN); - final ConnectivityManager cm = ConnectivityManager.from(this); - final Network network = new Network(mNetId); // Also initializes proxy system properties. - cm.bindProcessToNetwork(network); + mCm.bindProcessToNetwork(mNetwork); // Proxy system properties must be initialized before setContentView is called because // setContentView initializes the WebView logic which in turn reads the system properties. @@ -108,7 +93,7 @@ public class CaptivePortalLoginActivity extends Activity { getActionBar().setDisplayShowHomeEnabled(false); // Exit app if Network disappears. - final NetworkCapabilities networkCapabilities = cm.getNetworkCapabilities(network); + final NetworkCapabilities networkCapabilities = mCm.getNetworkCapabilities(mNetwork); if (networkCapabilities == null) { finish(); return; @@ -116,14 +101,14 @@ public class CaptivePortalLoginActivity extends Activity { mNetworkCallback = new NetworkCallback() { @Override public void onLost(Network lostNetwork) { - if (network.equals(lostNetwork)) done(CAPTIVE_PORTAL_APP_RETURN_UNWANTED); + if (mNetwork.equals(lostNetwork)) done(Result.UNWANTED); } }; final NetworkRequest.Builder builder = new NetworkRequest.Builder(); for (int transportType : networkCapabilities.getTransportTypes()) { builder.addTransportType(transportType); } - cm.registerNetworkCallback(builder.build(), mNetworkCallback); + mCm.registerNetworkCallback(builder.build(), mNetworkCallback); final WebView myWebView = (WebView) findViewById(R.id.webview); myWebView.clearCache(true); @@ -160,15 +145,21 @@ public class CaptivePortalLoginActivity extends Activity { } } - private void done(int result) { + private void done(Result result) { if (mNetworkCallback != null) { - ConnectivityManager.from(this).unregisterNetworkCallback(mNetworkCallback); + mCm.unregisterNetworkCallback(mNetworkCallback); + } + switch (result) { + case DISMISSED: + mCm.reportCaptivePortalDismissed(mNetwork, mResponseToken); + break; + case UNWANTED: + mCm.ignoreNetworkWithCaptivePortal(mNetwork, mResponseToken); + break; + case WANTED_AS_IS: + mCm.useNetworkWithCaptivePortal(mNetwork, mResponseToken); + break; } - Intent intent = new Intent(ACTION_CAPTIVE_PORTAL_LOGGED_IN); - intent.putExtra(Intent.EXTRA_TEXT, String.valueOf(mNetId)); - intent.putExtra(LOGGED_IN_RESULT, String.valueOf(result)); - intent.putExtra(RESPONSE_TOKEN, mResponseToken); - sendBroadcast(intent); finish(); } @@ -192,11 +183,11 @@ public class CaptivePortalLoginActivity extends Activity { public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); if (id == R.id.action_use_network) { - done(CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS); + done(Result.WANTED_AS_IS); return true; } if (id == R.id.action_do_not_use_network) { - done(CAPTIVE_PORTAL_APP_RETURN_UNWANTED); + done(Result.UNWANTED); return true; } return super.onOptionsItemSelected(item); @@ -225,7 +216,7 @@ public class CaptivePortalLoginActivity extends Activity { if (urlConnection != null) urlConnection.disconnect(); } if (httpResponseCode == 204) { - done(CAPTIVE_PORTAL_APP_RETURN_APPEASED); + done(Result.DISMISSED); } } }).start(); 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 class ConnectivityService extends IConnectivityManager.Stub } } + 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 diff --git a/services/core/java/com/android/server/connectivity/NetworkMonitor.java b/services/core/java/com/android/server/connectivity/NetworkMonitor.java index 76220db..3dc5426 100644 --- a/services/core/java/com/android/server/connectivity/NetworkMonitor.java +++ b/services/core/java/com/android/server/connectivity/NetworkMonitor.java @@ -87,17 +87,6 @@ public class NetworkMonitor extends StateMachine { private static final String PERMISSION_ACCESS_NETWORK_CONDITIONS = "android.permission.ACCESS_NETWORK_CONDITIONS"; - // Keep these in sync with CaptivePortalLoginActivity.java. - // Intent broadcast from CaptivePortalLogin indicating sign-in is complete. - // Extras: - // EXTRA_TEXT = netId - // LOGGED_IN_RESULT = one of the CAPTIVE_PORTAL_APP_RETURN_* values below. - // RESPONSE_TOKEN = data fragment from launching Intent - private static final String ACTION_CAPTIVE_PORTAL_LOGGED_IN = - "android.net.netmon.captive_portal_logged_in"; - private static final String LOGGED_IN_RESULT = "result"; - private static final String RESPONSE_TOKEN = "response_token"; - // After a network has been tested this result can be sent with EVENT_NETWORK_TESTED. // The network should be used as a default internet connection. It was found to be: // 1. a functioning network providing internet access, or @@ -170,11 +159,12 @@ public class NetworkMonitor extends StateMachine { /** * Message to self indicating captive portal app finished. - * arg1 = one of: CAPTIVE_PORTAL_APP_RETURN_APPEASED, + * arg1 = one of: CAPTIVE_PORTAL_APP_RETURN_DISMISSED, * CAPTIVE_PORTAL_APP_RETURN_UNWANTED, * CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS + * obj = mCaptivePortalLoggedInResponseToken as String */ - private static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9; + public static final int CMD_CAPTIVE_PORTAL_APP_FINISHED = BASE + 9; /** * Request ConnectivityService display provisioning notification. @@ -185,26 +175,11 @@ public class NetworkMonitor extends StateMachine { public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10; /** - * Message to self indicating sign-in app bypassed captive portal. - */ - private static final int EVENT_APP_BYPASSED_CAPTIVE_PORTAL = BASE + 11; - - /** - * Message to self indicating no sign-in app responded. + * Message to self indicating sign-in app should be launched. + * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the + * user touches the sign in notification. */ - private static final int EVENT_NO_APP_RESPONSE = BASE + 12; - - /** - * Message to self indicating sign-in app indicates sign-in is not possible. - */ - private static final int EVENT_APP_INDICATES_SIGN_IN_IMPOSSIBLE = BASE + 13; - - /** - * Return codes from captive portal sign-in app. - */ - public static final int CAPTIVE_PORTAL_APP_RETURN_APPEASED = 0; - public static final int CAPTIVE_PORTAL_APP_RETURN_UNWANTED = 1; - public static final int CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS = 2; + private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; private static final String LINGER_DELAY_PROPERTY = "persist.netmon.linger"; // Default to 30s linger time-out. @@ -259,7 +234,7 @@ public class NetworkMonitor extends StateMachine { private final State mCaptivePortalState = new CaptivePortalState(); private final State mLingeringState = new LingeringState(); - private CaptivePortalLoggedInBroadcastReceiver mCaptivePortalLoggedInBroadcastReceiver = null; + private CustomIntentReceiver mLaunchCaptivePortalAppBroadcastReceiver = null; private String mCaptivePortalLoggedInResponseToken = null; public NetworkMonitor(Context context, Handler handler, NetworkAgentInfo networkAgentInfo, @@ -323,9 +298,9 @@ public class NetworkMonitor extends StateMachine { return HANDLED; case CMD_NETWORK_DISCONNECTED: if (DBG) log("Disconnected - quitting"); - if (mCaptivePortalLoggedInBroadcastReceiver != null) { - mContext.unregisterReceiver(mCaptivePortalLoggedInBroadcastReceiver); - mCaptivePortalLoggedInBroadcastReceiver = null; + if (mLaunchCaptivePortalAppBroadcastReceiver != null) { + mContext.unregisterReceiver(mLaunchCaptivePortalAppBroadcastReceiver); + mLaunchCaptivePortalAppBroadcastReceiver = null; } quit(); return HANDLED; @@ -336,14 +311,21 @@ public class NetworkMonitor extends StateMachine { transitionTo(mEvaluatingState); return HANDLED; case CMD_CAPTIVE_PORTAL_APP_FINISHED: - // Previous token was broadcast, come up with a new one. + if (!mCaptivePortalLoggedInResponseToken.equals((String)message.obj)) + return HANDLED; + // Previous token was sent out, come up with a new one. mCaptivePortalLoggedInResponseToken = String.valueOf(new Random().nextLong()); switch (message.arg1) { - case CAPTIVE_PORTAL_APP_RETURN_APPEASED: - case CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS: + case ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_DISMISSED: + sendMessage(CMD_FORCE_REEVALUATION, 0 /* no UID */, + 0 /* INITIAL_ATTEMPTS */); + break; + case ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_WANTED_AS_IS: + // 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 CAPTIVE_PORTAL_APP_RETURN_UNWANTED: + case ConnectivityManager.CAPTIVE_PORTAL_APP_RETURN_UNWANTED: mUserDoesNotWant = true; // TODO: Should teardown network. transitionTo(mOfflineState); @@ -421,6 +403,25 @@ public class NetworkMonitor extends StateMachine { // is required. This State takes care to clear the notification upon exit from the State. private class MaybeNotifyState extends State { @Override + public boolean processMessage(Message message) { + if (DBG) log(getName() + message.toString()); + switch (message.what) { + case CMD_LAUNCH_CAPTIVE_PORTAL_APP: + 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.setFlags( + Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); + mContext.startActivityAsUser(intent, UserHandle.CURRENT); + return HANDLED; + default: + return NOT_HANDLED; + } + } + + @Override public void exit() { Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 0, mNetworkAgentInfo.network.netId, null); @@ -516,7 +517,9 @@ public class NetworkMonitor extends StateMachine { mContext.registerReceiver(this, new IntentFilter(mAction)); } public PendingIntent getPendingIntent() { - return PendingIntent.getBroadcast(mContext, 0, new Intent(mAction), 0); + final Intent intent = new Intent(mAction); + intent.setPackage(mContext.getPackageName()); + return PendingIntent.getBroadcast(mContext, 0, intent, 0); } @Override public void onReceive(Context context, Intent intent) { @@ -524,48 +527,29 @@ public class NetworkMonitor extends StateMachine { } } - private class CaptivePortalLoggedInBroadcastReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - if (Integer.parseInt(intent.getStringExtra(Intent.EXTRA_TEXT)) == - mNetworkAgentInfo.network.netId && - mCaptivePortalLoggedInResponseToken.equals( - intent.getStringExtra(RESPONSE_TOKEN))) { - sendMessage(obtainMessage(CMD_CAPTIVE_PORTAL_APP_FINISHED, - Integer.parseInt(intent.getStringExtra(LOGGED_IN_RESULT)), 0)); - } - } - } - // Being in the CaptivePortalState State indicates a captive portal was detected and the user // has been shown a notification to sign-in. private class CaptivePortalState extends State { + private static final String ACTION_LAUNCH_CAPTIVE_PORTAL_APP = + "android.net.netmon.launchCaptivePortalApp"; + @Override public void enter() { mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED, NETWORK_TEST_RESULT_INVALID, 0, mNetworkAgentInfo)); - - // Assemble Intent to launch captive portal sign-in app. - final Intent intent = new Intent(Intent.ACTION_SEND); - // Intent cannot use extras because PendingIntent.getActivity will merge matching - // Intents erasing extras. Use data instead of extras to encode NetID. - intent.setData(Uri.fromParts("netid", Integer.toString(mNetworkAgentInfo.network.netId), - mCaptivePortalLoggedInResponseToken)); - intent.setComponent(new ComponentName("com.android.captiveportallogin", - "com.android.captiveportallogin.CaptivePortalLoginActivity")); - intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK); - - if (mCaptivePortalLoggedInBroadcastReceiver == null) { + // Create a CustomIntentReceiver that sends us a + // CMD_LAUNCH_CAPTIVE_PORTAL_APP message when the user + // touches the notification. + if (mLaunchCaptivePortalAppBroadcastReceiver == null) { // Wait for result. - mCaptivePortalLoggedInBroadcastReceiver = - new CaptivePortalLoggedInBroadcastReceiver(); - final IntentFilter filter = new IntentFilter(ACTION_CAPTIVE_PORTAL_LOGGED_IN); - mContext.registerReceiver(mCaptivePortalLoggedInBroadcastReceiver, filter); + mLaunchCaptivePortalAppBroadcastReceiver = new CustomIntentReceiver( + ACTION_LAUNCH_CAPTIVE_PORTAL_APP, new Random().nextInt(), + CMD_LAUNCH_CAPTIVE_PORTAL_APP); } - // Initiate notification to sign-in. + // Display the sign in notification. Message message = obtainMessage(EVENT_PROVISIONING_NOTIFICATION, 1, mNetworkAgentInfo.network.netId, - PendingIntent.getActivity(mContext, 0, intent, 0)); + mLaunchCaptivePortalAppBroadcastReceiver.getPendingIntent()); mConnectivityServiceHandler.sendMessage(message); } |