Loading src/android/net/util/NetworkStackUtils.java +29 −1 Original line number Diff line number Diff line Loading @@ -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"); } Loading Loading @@ -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; Loading src/com/android/server/connectivity/NetworkMonitor.java +32 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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) { Loading Loading @@ -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(); } Loading tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +56 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -478,6 +480,7 @@ public class NetworkMonitorTest { mCreatedNetworkMonitors = new HashSet<>(); mRegisteredReceivers = new HashSet<>(); setDismissPortalInValidatedNetwork(false); } @After Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -1442,6 +1448,50 @@ public class NetworkMonitorTest { .notifyNetworkTested(eq(VALIDATION_RESULT_VALID), any()); } @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(); Loading Loading @@ -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()); Loading Loading
src/android/net/util/NetworkStackUtils.java +29 −1 Original line number Diff line number Diff line Loading @@ -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"); } Loading Loading @@ -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; Loading
src/com/android/server/connectivity/NetworkMonitor.java +32 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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) { Loading Loading @@ -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(); } Loading
tests/unit/src/com/android/server/connectivity/NetworkMonitorTest.java +56 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -478,6 +480,7 @@ public class NetworkMonitorTest { mCreatedNetworkMonitors = new HashSet<>(); mRegisteredReceivers = new HashSet<>(); setDismissPortalInValidatedNetwork(false); } @After Loading Loading @@ -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); Loading @@ -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); Loading Loading @@ -1442,6 +1448,50 @@ public class NetworkMonitorTest { .notifyNetworkTested(eq(VALIDATION_RESULT_VALID), any()); } @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(); Loading Loading @@ -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()); Loading