Loading core/java/android/net/ConnectivityManager.java +17 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; Loading Loading @@ -3467,6 +3468,22 @@ public class ConnectivityManager { } } /** * Requests that the system open the captive portal app on the specified network. * * @param network The network to log into. * * @hide */ @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void startCaptivePortalApp(Network network) { try { mService.startCaptivePortalApp(network); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * It is acceptable to briefly use multipath data to provide seamless connectivity for * time-sensitive user-facing operations when the system default network is temporarily Loading core/java/android/net/IConnectivityManager.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -161,6 +161,7 @@ interface IConnectivityManager void setAcceptUnvalidated(in Network network, boolean accept, boolean always); void setAvoidUnvalidated(in Network network); void startCaptivePortalApp(in Network network); int getMultipathPreference(in Network Network); Loading services/core/java/com/android/server/ConnectivityService.java +11 −0 Original line number Diff line number Diff line Loading @@ -2764,6 +2764,17 @@ public class ConnectivityService extends IConnectivityManager.Stub PROMPT_UNVALIDATED_DELAY_MS); } @Override public void startCaptivePortalApp(Network network) { enforceConnectivityInternalPermission(); mHandler.post(() -> { NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null) return; if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return; nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP); }); } public boolean avoidBadWifi() { return mMultinetworkPolicyTracker.getAvoidBadWifi(); } Loading services/core/java/com/android/server/connectivity/NetworkMonitor.java +5 −3 Original line number Diff line number Diff line Loading @@ -197,11 +197,13 @@ public class NetworkMonitor extends StateMachine { public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10; /** * Message to self indicating sign-in app should be launched. * Message indicating sign-in app should be launched. * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the * user touches the sign in notification. * user touches the sign in notification, or sent by * ConnectivityService when the user touches the "sign into * network" button in the wifi access point detail page. */ private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; /** * Retest network to see if captive portal is still in place. Loading tests/net/java/com/android/server/ConnectivityServiceTest.java +71 −1 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.net.CaptivePortal; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.PacketKeepalive; Loading Loading @@ -78,6 +79,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.test.AndroidTestCase; import android.test.mock.MockContentResolver; Loading Loading @@ -121,7 +123,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private static final int TIMEOUT_MS = 500; private static final int TEST_LINGER_DELAY_MS = 120; private BroadcastInterceptingContext mServiceContext; private MockContext mServiceContext; private WrappedConnectivityService mService; private WrappedConnectivityManager mCm; private MockNetworkAgent mWiFiNetworkAgent; Loading Loading @@ -152,6 +154,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private final MockContentResolver mContentResolver; @Spy private Resources mResources; private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>(); MockContext(Context base) { super(base); Loading @@ -168,6 +171,27 @@ public class ConnectivityServiceTest extends AndroidTestCase { mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); } @Override public void startActivityAsUser(Intent intent, UserHandle handle) { mStartedActivities.offer(intent); } public Intent expectStartActivityIntent(int timeoutMs) { Intent intent = null; try { intent = mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS); } catch (InterruptedException e) {} assertNotNull("Did not receive sign-in intent after " + timeoutMs + "ms", intent); return intent; } public void expectNoStartActivityIntent(int timeoutMs) { try { assertNull("Received unexpected Intent to start activity", mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS)); } catch (InterruptedException e) {} } @Override public Object getSystemService(String name) { if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; Loading Loading @@ -1922,6 +1946,52 @@ public class ConnectivityServiceTest extends AndroidTestCase { validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); } @SmallTest public void testCaptivePortalApp() { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); final TestNetworkCallback validatedCallback = new TestNetworkCallback(); final NetworkRequest validatedRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_VALIDATED).build(); mCm.registerNetworkCallback(validatedRequest, validatedCallback); // Bring up wifi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Check that calling startCaptivePortalApp does nothing. final int fastTimeoutMs = 100; mCm.startCaptivePortalApp(wifiNetwork); mServiceContext.expectNoStartActivityIntent(fastTimeoutMs); // Turn into a captive portal. mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302; mCm.reportNetworkConnectivity(wifiNetwork, false); captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); // Check that startCaptivePortalApp sends the expected intent. mCm.startCaptivePortalApp(wifiNetwork); Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS); assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction()); assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK)); // Have the app report that the captive portal is dismissed, and check that we revalidate. mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); c.reportCaptivePortalDismissed(); validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); mCm.unregisterNetworkCallback(validatedCallback); mCm.unregisterNetworkCallback(captivePortalCallback); } @SmallTest public void testAvoidOrIgnoreCaptivePortals() { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); Loading Loading
core/java/android/net/ConnectivityManager.java +17 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package android.net; import android.annotation.IntDef; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SystemApi; Loading Loading @@ -3467,6 +3468,22 @@ public class ConnectivityManager { } } /** * Requests that the system open the captive portal app on the specified network. * * @param network The network to log into. * * @hide */ @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void startCaptivePortalApp(Network network) { try { mService.startCaptivePortalApp(network); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } /** * It is acceptable to briefly use multipath data to provide seamless connectivity for * time-sensitive user-facing operations when the system default network is temporarily Loading
core/java/android/net/IConnectivityManager.aidl +1 −0 Original line number Diff line number Diff line Loading @@ -161,6 +161,7 @@ interface IConnectivityManager void setAcceptUnvalidated(in Network network, boolean accept, boolean always); void setAvoidUnvalidated(in Network network); void startCaptivePortalApp(in Network network); int getMultipathPreference(in Network Network); Loading
services/core/java/com/android/server/ConnectivityService.java +11 −0 Original line number Diff line number Diff line Loading @@ -2764,6 +2764,17 @@ public class ConnectivityService extends IConnectivityManager.Stub PROMPT_UNVALIDATED_DELAY_MS); } @Override public void startCaptivePortalApp(Network network) { enforceConnectivityInternalPermission(); mHandler.post(() -> { NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network); if (nai == null) return; if (!nai.networkCapabilities.hasCapability(NET_CAPABILITY_CAPTIVE_PORTAL)) return; nai.networkMonitor.sendMessage(NetworkMonitor.CMD_LAUNCH_CAPTIVE_PORTAL_APP); }); } public boolean avoidBadWifi() { return mMultinetworkPolicyTracker.getAvoidBadWifi(); } Loading
services/core/java/com/android/server/connectivity/NetworkMonitor.java +5 −3 Original line number Diff line number Diff line Loading @@ -197,11 +197,13 @@ public class NetworkMonitor extends StateMachine { public static final int EVENT_PROVISIONING_NOTIFICATION = BASE + 10; /** * Message to self indicating sign-in app should be launched. * Message indicating sign-in app should be launched. * Sent by mLaunchCaptivePortalAppBroadcastReceiver when the * user touches the sign in notification. * user touches the sign in notification, or sent by * ConnectivityService when the user touches the "sign into * network" button in the wifi access point detail page. */ private static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; public static final int CMD_LAUNCH_CAPTIVE_PORTAL_APP = BASE + 11; /** * Retest network to see if captive portal is still in place. Loading
tests/net/java/com/android/server/ConnectivityServiceTest.java +71 −1 Original line number Diff line number Diff line Loading @@ -39,6 +39,7 @@ import android.content.ContextWrapper; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.net.CaptivePortal; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivityManager.PacketKeepalive; Loading Loading @@ -78,6 +79,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; import android.provider.Settings; import android.test.AndroidTestCase; import android.test.mock.MockContentResolver; Loading Loading @@ -121,7 +123,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private static final int TIMEOUT_MS = 500; private static final int TEST_LINGER_DELAY_MS = 120; private BroadcastInterceptingContext mServiceContext; private MockContext mServiceContext; private WrappedConnectivityService mService; private WrappedConnectivityManager mCm; private MockNetworkAgent mWiFiNetworkAgent; Loading Loading @@ -152,6 +154,7 @@ public class ConnectivityServiceTest extends AndroidTestCase { private final MockContentResolver mContentResolver; @Spy private Resources mResources; private final LinkedBlockingQueue<Intent> mStartedActivities = new LinkedBlockingQueue<>(); MockContext(Context base) { super(base); Loading @@ -168,6 +171,27 @@ public class ConnectivityServiceTest extends AndroidTestCase { mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); } @Override public void startActivityAsUser(Intent intent, UserHandle handle) { mStartedActivities.offer(intent); } public Intent expectStartActivityIntent(int timeoutMs) { Intent intent = null; try { intent = mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS); } catch (InterruptedException e) {} assertNotNull("Did not receive sign-in intent after " + timeoutMs + "ms", intent); return intent; } public void expectNoStartActivityIntent(int timeoutMs) { try { assertNull("Received unexpected Intent to start activity", mStartedActivities.poll(timeoutMs, TimeUnit.MILLISECONDS)); } catch (InterruptedException e) {} } @Override public Object getSystemService(String name) { if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm; Loading Loading @@ -1922,6 +1946,52 @@ public class ConnectivityServiceTest extends AndroidTestCase { validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); } @SmallTest public void testCaptivePortalApp() { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); final NetworkRequest captivePortalRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL).build(); mCm.registerNetworkCallback(captivePortalRequest, captivePortalCallback); final TestNetworkCallback validatedCallback = new TestNetworkCallback(); final NetworkRequest validatedRequest = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_VALIDATED).build(); mCm.registerNetworkCallback(validatedRequest, validatedCallback); // Bring up wifi. mWiFiNetworkAgent = new MockNetworkAgent(TRANSPORT_WIFI); mWiFiNetworkAgent.connect(true); validatedCallback.expectAvailableAndValidatedCallbacks(mWiFiNetworkAgent); Network wifiNetwork = mWiFiNetworkAgent.getNetwork(); // Check that calling startCaptivePortalApp does nothing. final int fastTimeoutMs = 100; mCm.startCaptivePortalApp(wifiNetwork); mServiceContext.expectNoStartActivityIntent(fastTimeoutMs); // Turn into a captive portal. mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 302; mCm.reportNetworkConnectivity(wifiNetwork, false); captivePortalCallback.expectAvailableCallbacks(mWiFiNetworkAgent); validatedCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); // Check that startCaptivePortalApp sends the expected intent. mCm.startCaptivePortalApp(wifiNetwork); Intent intent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS); assertEquals(ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN, intent.getAction()); assertEquals(wifiNetwork, intent.getExtra(ConnectivityManager.EXTRA_NETWORK)); // Have the app report that the captive portal is dismissed, and check that we revalidate. mWiFiNetworkAgent.getWrappedNetworkMonitor().gen204ProbeResult = 204; CaptivePortal c = (CaptivePortal) intent.getExtra(ConnectivityManager.EXTRA_CAPTIVE_PORTAL); c.reportCaptivePortalDismissed(); validatedCallback.expectAvailableCallbacks(mWiFiNetworkAgent); captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent); mCm.unregisterNetworkCallback(validatedCallback); mCm.unregisterNetworkCallback(captivePortalCallback); } @SmallTest public void testAvoidOrIgnoreCaptivePortals() { final TestNetworkCallback captivePortalCallback = new TestNetworkCallback(); Loading