diff options
author | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-03-09 09:23:38 +0000 |
---|---|---|
committer | Chiachang Wang <chiachangwang@google.com> | 2020-03-10 02:13:14 +0000 |
commit | faac06e3e1412364512e46f38e63c6d2ca7548c1 (patch) | |
tree | 4fad8113a7070a3306e668816b072b01619ff607 | |
parent | 046a14ee618731ad5ef8c6b6c79bf0d84ab00fa6 (diff) |
Use redirect URL to start webview
NetworkMonitor will detect captive portal and may get a redirect
URL from WiFi AP. Redirect URL should able to send to captive
portal app to open the webview instead of detecting again by
captive portal app.
Bug: 134892996
Test: Manually test with captive portal AP
Test: atest NetworkStackTests NetworkStackNextTests
Change-Id: Idf363c79b7243a899121be8a68b32d0541dff14f
Merged-In: Idf363c79b7243a899121be8a68b32d0541dff14f
3 files changed, 117 insertions, 3 deletions
diff --git a/src/android/net/util/NetworkStackUtils.java b/src/android/net/util/NetworkStackUtils.java index d147b45..9d913fc 100644 --- a/src/android/net/util/NetworkStackUtils.java +++ b/src/android/net/util/NetworkStackUtils.java @@ -141,6 +141,14 @@ public class NetworkStackUtils { */ public static final String DHCP_IP_CONFLICT_DETECT_VERSION = "dhcp_ip_conflict_detect_version"; + /** + * Minimum module version at which to enable dismissal CaptivePortalLogin app in validated + * network feature. CaptivePortalLogin app will also use validation facilities in + * {@link NetworkMonitor} to perform portal validation if feature is enabled. + */ + public static final String DISMISS_PORTAL_IN_VALIDATED_NETWORK = + "dismiss_portal_in_validated_network"; + static { System.loadLibrary("networkstackutilsjni"); } @@ -270,12 +278,32 @@ public class NetworkStackUtils { */ public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, @NonNull String name) { + final int propertyVersion = getDeviceConfigPropertyInt(namespace, name, + 0 /* default value */); + return isFeatureEnabled(context, namespace, name, false); + } + + /** + * Check whether or not one specific experimental feature for a particular namespace from + * {@link DeviceConfig} is enabled by comparing NetworkStack module version {@link NetworkStack} + * with current version of property. If this property version is valid, the corresponding + * experimental feature would be enabled, otherwise disabled. + * @param context The global context information about an app environment. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultEnabled The value to return if the property does not exist or its value is + * null. + * @return true if this feature is enabled, or false if disabled. + */ + public static boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, + @NonNull String name, boolean defaultEnabled) { try { final int propertyVersion = getDeviceConfigPropertyInt(namespace, name, 0 /* default value */); final long packageVersion = context.getPackageManager().getPackageInfo( context.getPackageName(), 0).getLongVersionCode(); - return (propertyVersion != 0 && packageVersion >= (long) propertyVersion); + return (propertyVersion == 0 && defaultEnabled) + || (propertyVersion != 0 && packageVersion >= (long) propertyVersion); } catch (NameNotFoundException e) { Log.e(TAG, "Could not find the package name", e); return false; diff --git a/src/com/android/server/connectivity/NetworkMonitor.java b/src/com/android/server/connectivity/NetworkMonitor.java index e914a55..438080c 100644 --- a/src/com/android/server/connectivity/NetworkMonitor.java +++ b/src/com/android/server/connectivity/NetworkMonitor.java @@ -65,6 +65,7 @@ import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_MODE_PROMPT; import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS; import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USER_AGENT; import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS; +import static android.net.util.NetworkStackUtils.DISMISS_PORTAL_IN_VALIDATED_NETWORK; import static android.net.util.NetworkStackUtils.isEmpty; import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY; @@ -960,7 +961,11 @@ public class NetworkMonitor extends StateMachine { final Network network = new Network(mCleartextDnsNetwork); appExtras.putParcelable(ConnectivityManager.EXTRA_NETWORK, network); final CaptivePortalProbeResult probeRes = mLastPortalProbeResult; - appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, probeRes.detectUrl); + // Use redirect URL from AP if exists. + final String portalUrl = + (useRedirectUrlForPortal() && probeRes.redirectUrl != null) + ? probeRes.redirectUrl : probeRes.detectUrl; + appExtras.putString(EXTRA_CAPTIVE_PORTAL_URL, portalUrl); if (probeRes.probeSpec != null) { final String encodedSpec = probeRes.probeSpec.getEncodedSpec(); appExtras.putString(EXTRA_CAPTIVE_PORTAL_PROBE_SPEC, encodedSpec); @@ -977,6 +982,15 @@ public class NetworkMonitor extends StateMachine { } } + private boolean useRedirectUrlForPortal() { + // It must match the conditions in CaptivePortalLogin in which the redirect URL is not + // used to validate that the portal is gone. + final boolean aboveQ = + ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q); + return aboveQ && mDependencies.isFeatureEnabled(mContext, NAMESPACE_CONNECTIVITY, + DISMISS_PORTAL_IN_VALIDATED_NETWORK, aboveQ /* defaultEnabled */); + } + @Override public void exit() { if (mLaunchCaptivePortalAppBroadcastReceiver != null) { @@ -2385,6 +2399,23 @@ public class NetworkMonitor extends StateMachine { NetworkMonitorUtils.PERMISSION_ACCESS_NETWORK_CONDITIONS); } + /** + * Check whether or not one specific experimental feature for a particular namespace from + * {@link DeviceConfig} is enabled by comparing NetworkStack module version + * {@link NetworkStack} with current version of property. If this property version is valid, + * the corresponding experimental feature would be enabled, otherwise disabled. + * @param context The global context information about an app environment. + * @param namespace The namespace containing the property to look up. + * @param name The name of the property to look up. + * @param defaultEnabled The value to return if the property does not exist or its value is + * null. + * @return true if this feature is enabled, or false if disabled. + */ + public boolean isFeatureEnabled(@NonNull Context context, @NonNull String namespace, + @NonNull String name, boolean defaultEnabled) { + return NetworkStackUtils.isFeatureEnabled(context, namespace, name, defaultEnabled); + } + public static final Dependencies DEFAULT = new Dependencies(); } diff --git a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java index b2492aa..72d50b6 100644 --- a/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +++ b/tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java @@ -37,6 +37,7 @@ import static android.net.util.DataStallUtils.DATA_STALL_EVALUATION_TYPE_TCP; import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_FALLBACK_PROBE_SPECS; import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_OTHER_FALLBACK_URLS; import static android.net.util.NetworkStackUtils.CAPTIVE_PORTAL_USE_HTTPS; +import static android.net.util.NetworkStackUtils.DISMISS_PORTAL_IN_VALIDATED_NETWORK; import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_DNS_EVENTS; import static com.android.networkstack.apishim.ConstantsShim.DETECTION_METHOD_TCP_METRICS; @@ -59,6 +60,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; @@ -478,6 +480,7 @@ public class NetworkMonitorTest { mCreatedNetworkMonitors = new HashSet<>(); mRegisteredReceivers = new HashSet<>(); + setDismissPortalInValidatedNetwork(false); } @After @@ -1087,7 +1090,7 @@ public class NetworkMonitorTest { public void testLaunchCaptivePortalApp() throws Exception { setSslException(mHttpsConnection); setPortal302(mHttpConnection); - + when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(TEST_LOGIN_URL); final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES); notifyNetworkConnected(nm, METERED_CAPABILITIES); @@ -1111,6 +1114,9 @@ public class NetworkMonitorTest { // framework and only intended for the captive portal app, but the framework needs // the network to identify the right NetworkMonitor. assertEquals(TEST_NETID, networkCaptor.getValue().netId); + // Portal URL should be detection URL. + final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL); + assertEquals(TEST_HTTP_URL, redirectUrl); // Have the app report that the captive portal is dismissed, and check that we revalidate. setStatus(mHttpsConnection, 204); @@ -1443,6 +1449,50 @@ public class NetworkMonitorTest { } @Test + public void testDismissPortalInValidatedNetworkEnabledOsSupported() throws Exception { + assumeTrue(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)); + testDismissPortalInValidatedNetworkEnabled(TEST_LOGIN_URL); + } + + @Test + public void testDismissPortalInValidatedNetworkEnabledOsNotSupported() throws Exception { + assumeFalse(ShimUtils.isReleaseOrDevelopmentApiAbove(Build.VERSION_CODES.Q)); + testDismissPortalInValidatedNetworkEnabled(TEST_HTTP_URL); + } + + private void testDismissPortalInValidatedNetworkEnabled(String portalUrl) throws Exception { + setDismissPortalInValidatedNetwork(true); + setSslException(mHttpsConnection); + setPortal302(mHttpConnection); + when(mHttpConnection.getHeaderField(eq("location"))).thenReturn(TEST_LOGIN_URL); + final NetworkMonitor nm = makeMonitor(METERED_CAPABILITIES); + notifyNetworkConnected(nm, METERED_CAPABILITIES); + + verify(mCallbacks, timeout(HANDLER_TIMEOUT_MS).times(1)) + .showProvisioningNotification(any(), any()); + + assertEquals(1, mRegisteredReceivers.size()); + // Check that startCaptivePortalApp sends the expected intent. + nm.launchCaptivePortalApp(); + + final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class); + final ArgumentCaptor<Network> networkCaptor = ArgumentCaptor.forClass(Network.class); + verify(mCm, timeout(HANDLER_TIMEOUT_MS).times(1)) + .startCaptivePortalApp(networkCaptor.capture(), bundleCaptor.capture()); + verify(mNotifier).notifyCaptivePortalValidationPending(networkCaptor.getValue()); + final Bundle bundle = bundleCaptor.getValue(); + final Network bundleNetwork = bundle.getParcelable(ConnectivityManager.EXTRA_NETWORK); + assertEquals(TEST_NETID, bundleNetwork.netId); + // Network is passed both in bundle and as parameter, as the bundle is opaque to the + // framework and only intended for the captive portal app, but the framework needs + // the network to identify the right NetworkMonitor. + assertEquals(TEST_NETID, networkCaptor.getValue().netId); + // Portal URL should be redirect URL. + final String redirectUrl = bundle.getString(ConnectivityManager.EXTRA_CAPTIVE_PORTAL_URL); + assertEquals(portalUrl, redirectUrl); + } + + @Test public void testEvaluationState_clearProbeResults() throws Exception { final NetworkMonitor nm = runValidatedNetworkTest(); nm.getEvaluationState().clearProbeResults(); @@ -1602,6 +1652,11 @@ public class NetworkMonitorTest { eq(Settings.Global.CAPTIVE_PORTAL_MODE), anyInt())).thenReturn(mode); } + private void setDismissPortalInValidatedNetwork(boolean enabled) { + when(mDependencies.isFeatureEnabled(any(), any(), + eq(DISMISS_PORTAL_IN_VALIDATED_NETWORK), anyBoolean())).thenReturn(enabled); + } + private void runPortalNetworkTest(int result) { runNetworkTest(result); assertEquals(1, mRegisteredReceivers.size()); |