Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0839a191 authored by Remi NGUYEN VAN's avatar Remi NGUYEN VAN
Browse files

Only show "Connected" note after opening portal

The "Connected" notification would be shown every time a network
validates after being identified as a captive portal. This causes issues
on networks that have auto-login mechanisms, as a high priority
notification would be shown even though the user was not interacting
with the phone.

The "Connected" notification is intended to confirm to the user that
they successfuly logged in (manually), so only show it after the user
opens the portal on the network.

Bug: 134124044
Test: Flashed, connected to portal: notification shown
      Opened portal from command line + revalidate: no notification
      Tests passing with change, failing without

Change-Id: I89e7a7f49019bd7e4686712c56e00bd786eb3ef3
parent 867956b5
Loading
Loading
Loading
Loading
+11 −6
Original line number Diff line number Diff line
@@ -2614,9 +2614,11 @@ public class ConnectivityService extends IConnectivityManager.Stub
                    final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0);
                    final boolean wasValidated = nai.lastValidated;
                    final boolean wasDefault = isDefaultNetwork(nai);
                    if (nai.everCaptivePortalDetected && !nai.captivePortalLoginNotified
                            && valid) {
                        nai.captivePortalLoginNotified = true;
                    // Only show a connected notification if the network is pending validation
                    // after the captive portal app was open, and it has now validated.
                    if (nai.captivePortalValidationPending && valid) {
                        // User is now logged in, network validated.
                        nai.captivePortalValidationPending = false;
                        showNetworkNotification(nai, NotificationType.LOGGED_IN);
                    }

@@ -2687,9 +2689,6 @@ public class ConnectivityService extends IConnectivityManager.Stub
                        final int oldScore = nai.getCurrentScore();
                        nai.lastCaptivePortalDetected = visible;
                        nai.everCaptivePortalDetected |= visible;
                        if (visible) {
                            nai.captivePortalLoginNotified = false;
                        }
                        if (nai.lastCaptivePortalDetected &&
                            Settings.Global.CAPTIVE_PORTAL_MODE_AVOID == getCaptivePortalMode()) {
                            if (DBG) log("Avoiding captive portal network: " + nai.name());
@@ -3498,6 +3497,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
                new CaptivePortal(new CaptivePortalImpl(network).asBinder()));
        appIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);

        // This runs on a random binder thread, but getNetworkAgentInfoForNetwork is thread-safe,
        // and captivePortalValidationPending is volatile.
        final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
        if (nai != null) {
            nai.captivePortalValidationPending = true;
        }
        Binder.withCleanCallingIdentity(() ->
                mContext.startActivityAsUser(appIntent, UserHandle.CURRENT));
    }
+4 −4
Original line number Diff line number Diff line
@@ -155,9 +155,9 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
    // Whether a captive portal was found during the last network validation attempt.
    public boolean lastCaptivePortalDetected;

    // Indicates the user was notified of a successful captive portal login since a portal was
    // last detected.
    public boolean captivePortalLoginNotified;
    // Indicates the captive portal app was opened to show a login UI to the user, but the network
    // has not validated yet.
    public volatile boolean captivePortalValidationPending;

    // Set to true when partial connectivity was detected.
    public boolean partialConnectivity;
@@ -629,7 +629,7 @@ public class NetworkAgentInfo implements Comparable<NetworkAgentInfo> {
                + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
                + "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
                + "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
                + "captivePortalLoginNotified{" + captivePortalLoginNotified + "} "
                + "captivePortalValidationPending{" + captivePortalValidationPending + "} "
                + "partialConnectivity{" + partialConnectivity + "} "
                + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
                + "clat{" + clatd + "} "
+37 −5
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.server;

import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.MATCH_ANY_USER;
import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
import static android.net.ConnectivityManager.NETID_UNSET;
import static android.net.ConnectivityManager.PRIVATE_DNS_MODE_OFF;
@@ -84,13 +85,13 @@ import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -145,6 +146,7 @@ import android.net.NetworkInfo.DetailedState;
import android.net.NetworkMisc;
import android.net.NetworkRequest;
import android.net.NetworkSpecifier;
import android.net.NetworkStack;
import android.net.NetworkStackClient;
import android.net.NetworkState;
import android.net.NetworkUtils;
@@ -158,6 +160,7 @@ import android.net.shared.NetworkMonitorUtils;
import android.net.shared.PrivateDnsConfig;
import android.net.util.MultinetworkPolicyTracker;
import android.os.Binder;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -195,6 +198,7 @@ import com.android.server.connectivity.DefaultNetworkMetrics;
import com.android.server.connectivity.IpConnectivityMetrics;
import com.android.server.connectivity.MockableSystemProperties;
import com.android.server.connectivity.Nat464Xlat;
import com.android.server.connectivity.NetworkNotificationManager.NotificationType;
import com.android.server.connectivity.ProxyTracker;
import com.android.server.connectivity.Tethering;
import com.android.server.connectivity.Vpn;
@@ -289,6 +293,7 @@ public class ConnectivityServiceTest {
    @Mock NetworkStackClient mNetworkStack;
    @Mock PackageManager mPackageManager;
    @Mock UserManager mUserManager;
    @Mock NotificationManager mNotificationManager;

    private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor =
            ArgumentCaptor.forClass(ResolverParamsParcel.class);
@@ -358,7 +363,7 @@ public class ConnectivityServiceTest {
        @Override
        public Object getSystemService(String name) {
            if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
            if (Context.NOTIFICATION_SERVICE.equals(name)) return mock(NotificationManager.class);
            if (Context.NOTIFICATION_SERVICE.equals(name)) return mNotificationManager;
            if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack;
            if (Context.USER_SERVICE.equals(name)) return mUserManager;
            return super.getSystemService(name);
@@ -378,6 +383,16 @@ public class ConnectivityServiceTest {
        public PackageManager getPackageManager() {
            return mPackageManager;
        }

        @Override
        public void enforceCallingOrSelfPermission(String permission, String message) {
            // The mainline permission can only be held if signed with the network stack certificate
            // Skip testing for this permission.
            if (NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK.equals(permission)) return;
            // All other permissions should be held by the test or unnecessary: check as normal to
            // make sure the code does not rely on unexpected permissions.
            super.enforceCallingOrSelfPermission(permission, message);
        }
    }

    public void waitForIdle(int timeoutMsAsInt) {
@@ -2863,6 +2878,9 @@ public class ConnectivityServiceTest {

        // Expect NET_CAPABILITY_VALIDATED onAvailable callback.
        validatedCallback.expectAvailableDoubleValidatedCallbacks(mWiFiNetworkAgent);
        // Expect no notification to be shown when captive portal disappears by itself
        verify(mNotificationManager, never()).notifyAsUser(
                anyString(), eq(NotificationType.LOGGED_IN.eventId), any(), any());

        // Break network connectivity.
        // Expect NET_CAPABILITY_VALIDATED onLost callback.
@@ -2892,6 +2910,8 @@ public class ConnectivityServiceTest {
        // Check that calling startCaptivePortalApp does nothing.
        final int fastTimeoutMs = 100;
        mCm.startCaptivePortalApp(wifiNetwork);
        waitForIdle();
        verify(mWiFiNetworkAgent.mNetworkMonitor, never()).launchCaptivePortalApp();
        mServiceContext.expectNoStartActivityIntent(fastTimeoutMs);

        // Turn into a captive portal.
@@ -2902,14 +2922,26 @@ public class ConnectivityServiceTest {

        // Check that startCaptivePortalApp sends the expected command to NetworkMonitor.
        mCm.startCaptivePortalApp(wifiNetwork);
        verify(mWiFiNetworkAgent.mNetworkMonitor, timeout(TIMEOUT_MS).times(1))
                .launchCaptivePortalApp();
        waitForIdle();
        verify(mWiFiNetworkAgent.mNetworkMonitor).launchCaptivePortalApp();

        // NetworkMonitor uses startCaptivePortal(Network, Bundle) (startCaptivePortalAppInternal)
        final Bundle testBundle = new Bundle();
        final String testKey = "testkey";
        final String testValue = "testvalue";
        testBundle.putString(testKey, testValue);
        mCm.startCaptivePortalApp(wifiNetwork, testBundle);
        final Intent signInIntent = mServiceContext.expectStartActivityIntent(TIMEOUT_MS);
        assertEquals(ACTION_CAPTIVE_PORTAL_SIGN_IN, signInIntent.getAction());
        assertEquals(testValue, signInIntent.getStringExtra(testKey));

        // Report that the captive portal is dismissed, and check that callbacks are fired
        mWiFiNetworkAgent.setNetworkValid();
        mWiFiNetworkAgent.mNetworkMonitor.forceReevaluation(Process.myUid());
        validatedCallback.expectAvailableCallbacksValidated(mWiFiNetworkAgent);
        captivePortalCallback.expectCallback(CallbackState.LOST, mWiFiNetworkAgent);
        verify(mNotificationManager, times(1)).notifyAsUser(anyString(),
                eq(NotificationType.LOGGED_IN.eventId), any(), eq(UserHandle.ALL));

        mCm.unregisterNetworkCallback(validatedCallback);
        mCm.unregisterNetworkCallback(captivePortalCallback);