Add Adaptive Connectivity toggle

The Adaptive Connectivity toggle is enabled by default:  no behavior
change in WifiScoreReport.   When it is disabled the following changes
are made:
1. Send a constant score of 51 to connectivity service.
2. Never recommend a NUD check.

Bug: 166644305

Test: atest com.android.server.wifi

Signed-off-by: Mingguang Xu <mingguangxu@google.com>
Change-Id: I72762fc129c9f026ca9323b11e4e159ed2706cb7
Merged-In: I72762fc129c9f026ca9323b11e4e159ed2706cb7
diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java
index 9598309..0b67fa3 100644
--- a/service/java/com/android/server/wifi/ClientModeImpl.java
+++ b/service/java/com/android/server/wifi/ClientModeImpl.java
@@ -812,7 +812,7 @@
         mWifiScoreReport = new WifiScoreReport(mWifiInjector.getScoringParams(), mClock,
                 mWifiMetrics, mWifiInfo, mWifiNative, mBssidBlocklistMonitor,
                 mWifiInjector.getWifiThreadRunner(), mWifiInjector.getDeviceConfigFacade(),
-                mContext);
+                mContext, looper, mFacade);
 
         mNetworkCapabilitiesFilter = new NetworkCapabilities.Builder()
                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
@@ -3441,6 +3441,7 @@
                     break;
                 case CMD_INITIALIZE:
                     mWifiNative.initialize();
+                    mWifiScoreReport.initialize();
                     break;
                 case CMD_BOOT_COMPLETED:
                     // get other services that we need to manage
diff --git a/service/java/com/android/server/wifi/WifiScoreReport.java b/service/java/com/android/server/wifi/WifiScoreReport.java
index 89f0445..381be7f 100644
--- a/service/java/com/android/server/wifi/WifiScoreReport.java
+++ b/service/java/com/android/server/wifi/WifiScoreReport.java
@@ -17,16 +17,22 @@
 package com.android.server.wifi;
 
 import android.content.Context;
+import android.database.ContentObserver;
 import android.net.Network;
 import android.net.NetworkAgent;
+import android.net.Uri;
 import android.net.wifi.IScoreUpdateObserver;
 import android.net.wifi.IWifiConnectedNetworkScorer;
 import android.net.wifi.WifiInfo;
 import android.net.wifi.nl80211.WifiNl80211Manager;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
+import android.provider.Settings;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.wifi.resources.R;
 
 import java.io.FileDescriptor;
@@ -57,6 +63,15 @@
     private static final long DURATION_TO_BLOCKLIST_BSSID_AFTER_FIRST_EXITING_MILLIS = 30000;
     private static final long INVALID_WALL_CLOCK_MILLIS = -1;
 
+    /**
+     * Copy of the settings string. Can't directly use the constant because it is @hide.
+     * See {@link android.provider.Settings.Secure.ADAPTIVE_CONNECTIVITY_ENABLED}.
+     * TODO(b/167709538) remove this hardcoded string and create new API in Wifi mainline.
+     */
+    @VisibleForTesting
+    public static final String SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED =
+            "adaptive_connectivity_enabled";
+
     // Cache of the last score
     private int mScore = ConnectedScore.WIFI_MAX_SCORE;
 
@@ -78,6 +93,8 @@
     WifiNative mWifiNative;
     WifiThreadRunner mWifiThreadRunner;
     DeviceConfigFacade mDeviceConfigFacade;
+    Handler mHandler;
+    FrameworkFacade mFrameworkFacade;
 
     /**
      * Callback proxy. See {@link android.net.wifi.WifiManager.ScoreUpdateObserver}.
@@ -200,6 +217,10 @@
                 return;
             }
         }
+        // Stay a notch above the transition score if adaptive connectivity is disabled.
+        if (!mAdaptiveConnectivityEnabled) {
+            score = ConnectedScore.WIFI_TRANSITION_SCORE + 1;
+        }
         mNetworkAgent.sendNetworkScore(score);
     }
 
@@ -286,10 +307,50 @@
 
     private WifiConnectedNetworkScorerHolder mWifiConnectedNetworkScorerHolder;
 
+    /**
+     * Observer for adaptive connectivity enable settings changes.
+     * This is enabled by default. Will be toggled off via adb command or a settings
+     * toggle by the user to disable adaptive connectivity.
+     */
+    private class AdaptiveConnectivityEnabledSettingObserver extends ContentObserver {
+        AdaptiveConnectivityEnabledSettingObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            mAdaptiveConnectivityEnabled = getValue();
+            Log.d(TAG, "Adaptive connectivity status changed: " + mAdaptiveConnectivityEnabled);
+        }
+
+        /**
+         * Register settings change observer.
+         */
+        public void initialize() {
+            Uri uri = Settings.Secure.getUriFor(SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED);
+            if (uri == null) {
+                Log.e(TAG, "Adaptive connectivity user toggle does not exist in Settings");
+                return;
+            }
+            mFrameworkFacade.registerContentObserver(mContext, uri, true, this);
+            mAdaptiveConnectivityEnabled = mAdaptiveConnectivityEnabledSettingObserver.getValue();
+        }
+
+        public boolean getValue() {
+            return mFrameworkFacade.getIntegerSetting(
+                    mContext, SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED, 1) == 1;
+        }
+    }
+
+    private final AdaptiveConnectivityEnabledSettingObserver
+            mAdaptiveConnectivityEnabledSettingObserver;
+    private boolean mAdaptiveConnectivityEnabled = true;
+
     WifiScoreReport(ScoringParams scoringParams, Clock clock, WifiMetrics wifiMetrics,
             WifiInfo wifiInfo, WifiNative wifiNative, BssidBlocklistMonitor bssidBlocklistMonitor,
             WifiThreadRunner wifiThreadRunner, DeviceConfigFacade deviceConfigFacade,
-            Context context) {
+            Context context, Looper looper, FrameworkFacade frameworkFacade) {
         mScoringParams = scoringParams;
         mClock = clock;
         mAggressiveConnectedScore = new AggressiveConnectedScore(scoringParams, clock);
@@ -301,6 +362,10 @@
         mWifiThreadRunner = wifiThreadRunner;
         mDeviceConfigFacade = deviceConfigFacade;
         mContext = context;
+        mFrameworkFacade = frameworkFacade;
+        mHandler = new Handler(looper);
+        mAdaptiveConnectivityEnabledSettingObserver =
+                new AdaptiveConnectivityEnabledSettingObserver(mHandler);
     }
 
     /**
@@ -463,6 +528,10 @@
      * @return true to indicate that an IP reachability check is recommended
      */
     public boolean shouldCheckIpLayer() {
+        // Don't recommend if adaptive connectivity is disabled.
+        if (!mAdaptiveConnectivityEnabled) {
+            return false;
+        }
         int nud = mScoringParams.getNudKnob();
         if (nud == 0) {
             return false;
@@ -703,4 +772,11 @@
         mWifiConnectedNetworkScorerHolder = null;
         mWifiMetrics.setIsExternalWifiScorerOn(false);
     }
+
+    /**
+     * Initialize WifiScoreReport
+     */
+    public void initialize() {
+        mAdaptiveConnectivityEnabledSettingObserver.initialize();
+    }
 }
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java b/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
index 82dbbe9..cf83fbe 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiScoreReportTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.AdditionalAnswers.answerVoid;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.anyLong;
@@ -38,6 +39,7 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
 import android.net.ConnectivityManager;
 import android.net.LinkProperties;
 import android.net.Network;
@@ -50,6 +52,7 @@
 import android.net.wifi.WifiInfo;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.RemoteException;
 import android.os.test.TestLooper;
 
@@ -60,6 +63,7 @@
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -102,6 +106,8 @@
     @Mock BssidBlocklistMonitor mBssidBlocklistMonitor;
     @Mock Network mNetwork;
     @Mock DeviceConfigFacade mDeviceConfigFacade;
+    @Mock Looper mWifiLooper;
+    @Mock FrameworkFacade mFrameworkFacade;
     private TestLooper mLooper;
 
     public class WifiConnectedNetworkScorerImpl extends IWifiConnectedNetworkScorer.Stub {
@@ -200,10 +206,14 @@
         mClock = new FakeClock();
         mScoringParams = new ScoringParams();
         mWifiThreadRunner = new WifiThreadRunner(new Handler(mLooper.getLooper()));
+        when(mFrameworkFacade.getIntegerSetting(any(Context.class),
+                eq(WifiScoreReport.SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), eq(1)))
+                .thenReturn(1);
         mWifiScoreReport = new WifiScoreReport(mScoringParams, mClock, mWifiMetrics, mWifiInfo,
                 mWifiNative, mBssidBlocklistMonitor, mWifiThreadRunner,
-                mDeviceConfigFacade, mContext);
+                mDeviceConfigFacade, mContext, mWifiLooper, mFrameworkFacade);
         mWifiScoreReport.setNetworkAgent(mNetworkAgent);
+        mWifiScoreReport.initialize();
         when(mDeviceConfigFacade.getMinConfirmationDurationSendLowScoreMs()).thenReturn(
                 DeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_LOW_SCORE_MS);
         when(mDeviceConfigFacade.getMinConfirmationDurationSendHighScoreMs()).thenReturn(
@@ -999,4 +1009,66 @@
         mLooper.dispatchAll();
         verify(mNetworkAgent).sendNetworkScore(53);
     }
+
+    /**
+     * Verify NUD check is not recommended and the score of 51 is sent to connectivity service
+     * when adaptive connectivity is disabled for AOSP scorer.
+     */
+    @Test
+    public void verifyNudCheckAndScoreIfToggleOffForAospScorer() throws Exception {
+        mWifiInfo.setFrequency(5220);
+        mWifiInfo.setRssi(-85);
+        ArgumentCaptor<ContentObserver> observer = ArgumentCaptor.forClass(ContentObserver.class);
+        verify(mFrameworkFacade).registerContentObserver(
+                any(), any(), eq(true), observer.capture());
+        when(mFrameworkFacade.getIntegerSetting(any(Context.class),
+                eq(WifiScoreReport.SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), eq(1)))
+                .thenReturn(0);
+        observer.getValue().onChange(true);
+        mWifiScoreReport.calculateAndReportScore();
+        assertFalse(mWifiScoreReport.shouldCheckIpLayer());
+        verify(mNetworkAgent).sendNetworkScore(51);
+    }
+
+    /**
+     * Verify NUD check is not recommended and the score of 51 is sent to connectivity service
+     * when adaptive connectivity is disabled for external Wi-Fi scorer.
+     */
+    @Test
+    public void verifyNudCheckAndScoreIfToggleOffForExternalScorer() throws Exception {
+        WifiConnectedNetworkScorerImpl scorerImpl = new WifiConnectedNetworkScorerImpl();
+        // Register Client for verification.
+        mWifiScoreReport.setWifiConnectedNetworkScorer(mAppBinder, scorerImpl);
+        when(mNetwork.getNetId()).thenReturn(TEST_NETWORK_ID);
+        mWifiScoreReport.startConnectedNetworkScorer(TEST_NETWORK_ID);
+        mClock.mStepMillis = 0;
+        when(mContext.getResources().getBoolean(
+                R.bool.config_wifiMinConfirmationDurationSendNetworkScoreEnabled)).thenReturn(true);
+        when(mDeviceConfigFacade.getMinConfirmationDurationSendHighScoreMs()).thenReturn(4000);
+
+        ArgumentCaptor<ContentObserver> observer = ArgumentCaptor.forClass(ContentObserver.class);
+        verify(mFrameworkFacade).registerContentObserver(
+                any(), any(), eq(true), observer.capture());
+        when(mFrameworkFacade.getIntegerSetting(any(Context.class),
+                eq(WifiScoreReport.SETTINGS_SECURE_ADAPTIVE_CONNECTIVITY_ENABLED), eq(1)))
+                .thenReturn(0);
+        observer.getValue().onChange(true);
+
+        mClock.mWallClockMillis = 10;
+        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 49);
+        mLooper.dispatchAll();
+        verify(mNetworkAgent, never()).sendNetworkScore(anyInt());
+        assertFalse(mWifiScoreReport.shouldCheckIpLayer());
+
+        mClock.mWallClockMillis = 10
+                + mDeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_LOW_SCORE_MS - 1;
+        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 48);
+        mLooper.dispatchAll();
+        verify(mNetworkAgent, never()).sendNetworkScore(anyInt());
+        mClock.mWallClockMillis = 10
+                + mDeviceConfigFacade.DEFAULT_MIN_CONFIRMATION_DURATION_SEND_LOW_SCORE_MS;
+        scorerImpl.mScoreUpdateObserver.notifyScoreUpdate(scorerImpl.mSessionId, 47);
+        mLooper.dispatchAll();
+        verify(mNetworkAgent).sendNetworkScore(51);
+    }
 }