Change single scan schedule for single saved network

This commit allows the OEM to use a differnet single scan schedule
when screen is on and device is connected to the only saved network.

Bug: 140596420
Test: atest com.android.server.wifi
Change-Id: I4de216783653b2933b69dcf6e708a05f5ab3a9f4
diff --git a/service/java/com/android/server/wifi/WifiConnectivityManager.java b/service/java/com/android/server/wifi/WifiConnectivityManager.java
index 575a410..d203edf 100644
--- a/service/java/com/android/server/wifi/WifiConnectivityManager.java
+++ b/service/java/com/android/server/wifi/WifiConnectivityManager.java
@@ -164,6 +164,7 @@
     private static final int[] DEFAULT_SCANNING_SCHEDULE = {20, 40, 80, 160};
     private int[] mConnectedSingleScanSchedule;
     private int[] mDisconnectedSingleScanSchedule;
+    private int[] mConnectedSingleSavedNetworkSingleScanSchedule;
 
     private final Object mLock = new Object();
 
@@ -518,32 +519,38 @@
             WifiConfigManager.OnNetworkUpdateListener {
         @Override
         public void onNetworkAdded(WifiConfiguration config) {
-            updatePnoScan();
+            updateScan();
         }
         @Override
         public void onNetworkEnabled(WifiConfiguration config) {
-            updatePnoScan();
+            updateScan();
         }
         @Override
         public void onNetworkRemoved(WifiConfiguration config) {
-            updatePnoScan();
+            updateScan();
         }
         @Override
         public void onNetworkUpdated(WifiConfiguration config) {
-            updatePnoScan();
+            updateScan();
         }
         @Override
         public void onNetworkTemporarilyDisabled(WifiConfiguration config, int disableReason) { }
 
         @Override
         public void onNetworkPermanentlyDisabled(WifiConfiguration config, int disableReason) {
-            updatePnoScan();
+            updateScan();
         }
-        private void updatePnoScan() {
-            // Update the PNO scan network list when screen is off. Here we
-            // rely on startConnectivityScan() to perform all the checks and clean up.
-            if (!mScreenOn) {
-                localLog("Saved networks updated");
+        private void updateScan() {
+            if (mScreenOn) {
+                // Update scanning schedule if needed
+                if (updateSingleScanningSchedule()) {
+                    localLog("Saved networks updated impacting single scan schedule");
+                    startConnectivityScan(false);
+                }
+            } else {
+                // Update the PNO scan network list when screen is off. Here we
+                // rely on startConnectivityScan() to perform all the checks and clean up.
+                localLog("Saved networks updated impacting pno scan");
                 startConnectivityScan(false);
             }
         }
@@ -585,14 +592,14 @@
     }
 
     /** Initialize single scanning schedules, and validate them */
-    private int[] initializeScanningSchedule(Context context, int state) {
+    private int[] initializeScanningSchedule(int state) {
         int[] schedule;
 
         if (state == WIFI_STATE_CONNECTED) {
-            schedule = context.getResources().getIntArray(
+            schedule = mContext.getResources().getIntArray(
                     R.array.config_wifiConnectedScanIntervalScheduleSec);
         } else if (state == WIFI_STATE_DISCONNECTED) {
-            schedule = context.getResources().getIntArray(
+            schedule = mContext.getResources().getIntArray(
                     R.array.config_wifiDisconnectedScanIntervalScheduleSec);
         } else {
             schedule = null;
@@ -880,6 +887,28 @@
         }
     }
 
+    // Update the single scanning schedule if needed, and return true if update occurs
+    private boolean updateSingleScanningSchedule() {
+        if (mWifiState != WIFI_STATE_CONNECTED) {
+            // No need to update the scanning schedule
+            return false;
+        }
+
+        boolean shouldUseSingleSavedNetworkSchedule = useSingleSavedNetworkSchedule();
+
+        if (mCurrentSingleScanSchedule == mConnectedSingleScanSchedule
+                && shouldUseSingleSavedNetworkSchedule) {
+            mCurrentSingleScanSchedule = mConnectedSingleSavedNetworkSingleScanSchedule;
+            return true;
+        }
+        if (mCurrentSingleScanSchedule == mConnectedSingleSavedNetworkSingleScanSchedule
+                && !shouldUseSingleSavedNetworkSchedule) {
+            mCurrentSingleScanSchedule = mConnectedSingleScanSchedule;
+            return true;
+        }
+        return false;
+    }
+
     // Reset the last periodic single scan time stamp so that the next periodic single
     // scan can start immediately.
     private void resetLastPeriodicSingleScanTimeStamp() {
@@ -1166,18 +1195,54 @@
     }
 
     /**
+     * Check if Single saved network schedule should be used
+     * This is true if the following is satisfied:
+     * 1. Device is in connected state (this method is only called in this state)
+     * 2. Device has a single saved network
+     * 3. The connected network is the saved network
+     */
+    private boolean useSingleSavedNetworkSchedule() {
+        List<WifiConfiguration> savedNetworks =
+                mConfigManager.getSavedNetworks(Process.WIFI_UID);
+
+        // return true if there is a single saved network which is the currently connected network
+        return (savedNetworks.size() == 1
+                && savedNetworks.get(0).status == WifiConfiguration.Status.CURRENT);
+    }
+
+    private int[] initSingleSavedNetworkSchedule() {
+        int[] schedule = mContext.getResources().getIntArray(
+                    R.array.config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec);
+        if (schedule == null || schedule.length == 0) {
+            return null;
+        }
+
+        for (int val : schedule) {
+            if (val <= 0) {
+                return null;
+            }
+        }
+        return schedule;
+    }
+
+    /**
      * Handler for WiFi state (connected/disconnected) changes
      */
     public void handleConnectionStateChanged(int state) {
         localLog("handleConnectionStateChanged: state=" + stateToString(state));
 
         if (mConnectedSingleScanSchedule == null) {
-            mConnectedSingleScanSchedule = initializeScanningSchedule(
-                  mContext, WIFI_STATE_CONNECTED);
+            mConnectedSingleScanSchedule = initializeScanningSchedule(WIFI_STATE_CONNECTED);
         }
         if (mDisconnectedSingleScanSchedule == null) {
-            mDisconnectedSingleScanSchedule = initializeScanningSchedule(
-                  mContext, WIFI_STATE_DISCONNECTED);
+            mDisconnectedSingleScanSchedule = initializeScanningSchedule(WIFI_STATE_DISCONNECTED);
+        }
+        if (mConnectedSingleSavedNetworkSingleScanSchedule == null) {
+            mConnectedSingleSavedNetworkSingleScanSchedule =
+                    initSingleSavedNetworkSchedule();
+            if (mConnectedSingleSavedNetworkSingleScanSchedule == null) {
+                mConnectedSingleSavedNetworkSingleScanSchedule = mConnectedSingleScanSchedule;
+            }
         }
 
         mWifiState = state;
@@ -1191,8 +1256,13 @@
             setSingleScanningSchedule(mDisconnectedSingleScanSchedule);
             startConnectivityScan(SCAN_IMMEDIATELY);
         } else if (mWifiState == WIFI_STATE_CONNECTED) {
-            // Switch to connected single scanning schedule
-            setSingleScanningSchedule(mConnectedSingleScanSchedule);
+            if (useSingleSavedNetworkSchedule()) {
+                // Switch to Single-Saved-Network connected schedule
+                setSingleScanningSchedule(mConnectedSingleSavedNetworkSingleScanSchedule);
+            } else {
+                // Switch to connected single scanning schedule
+                setSingleScanningSchedule(mConnectedSingleScanSchedule);
+            }
             startConnectivityScan(SCAN_ON_SCHEDULE);
         } else {
             // Intermediate state, no applicable single scanning schedule
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 6b92fb0..460dce4 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -299,6 +299,13 @@
         <item>160</item>
     </integer-array>
 
+    <!-- Array describing scanning schedule in seconds when device is connected and screen is on
+         and the connected network is the only saved network.
+         When this array is set to an empty array, the noraml connected scan schedule defined
+         in config_wifiConnectedScanIntervalScheduleSec will be used -->
+    <integer-array translatable="false" name="config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec">
+    </integer-array>
+
     <!-- Indicates that hidden networks are to be scanned during scan only mode -->
     <bool translatable="false" name="config_wifiScanHiddenNetworksScanOnlyMode">false</bool>
 
diff --git a/service/res/values/overlayable.xml b/service/res/values/overlayable.xml
index 09ed48b..0bfa508 100644
--- a/service/res/values/overlayable.xml
+++ b/service/res/values/overlayable.xml
@@ -94,6 +94,7 @@
           <item type="array" name="config_wifiRssiLevelThresholds" />
           <item type="array" name="config_wifiDisconnectedScanIntervalScheduleSec" />
           <item type="array" name="config_wifiConnectedScanIntervalScheduleSec" />
+          <item type="array" name="config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec" />
           <item type="bool" name="config_wifiScanHiddenNetworksScanOnlyMode" />
           <item type="integer" name="config_wifiHardwareSoftapMaxClientCount" />
           <item type="bool" name="config_wifiIsUnusableEventMetricsEnabled" />
diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
index 4e6e0a7..a00ec8c 100644
--- a/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
+++ b/tests/wifitests/src/com/android/server/wifi/WifiConnectivityManagerTest.java
@@ -160,8 +160,9 @@
     private static final long CURRENT_SYSTEM_TIME_MS = 1000;
     private static final int MAX_BSSID_BLACKLIST_SIZE = 16;
     private static final int[] VALID_CONNECTED_SINGLE_SCAN_SCHEDULE = {10, 30, 50};
+    private static final int[] VALID_CONNECTED_SINGLE_SAVED_NETWORK_SCHEDULE = {15, 35, 55};
     private static final int[] VALID_DISCONNECTED_SINGLE_SCAN_SCHEDULE = {25, 40, 60};
-    private static final int[] INVALID_SCHEDULE_EMPTY = {};
+    private static final int[] SCHEDULE_EMPTY = {};
     private static final int[] INVALID_SCHEDULE_NEGATIVE_VALUES = {10, -10, 20};
     private static final int[] INVALID_SCHEDULE_ZERO_VALUES = {10, 0, 20};
     private static final int MAX_SCAN_INTERVAL_IN_SCHEDULE = 60;
@@ -186,6 +187,9 @@
         when(resource.getIntArray(
                 R.array.config_wifiDisconnectedScanIntervalScheduleSec))
                 .thenReturn(VALID_DISCONNECTED_SINGLE_SCAN_SCHEDULE);
+        when(resource.getIntArray(R.array
+                .config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec))
+                .thenReturn(SCHEDULE_EMPTY);
         return resource;
     }
 
@@ -769,7 +773,7 @@
     @Test
     public void checkPeriodicScanIntervalWhenDisconnectedWithEmptySchedule() throws Exception {
         when(mResource.getIntArray(R.array.config_wifiDisconnectedScanIntervalScheduleSec))
-                .thenReturn(INVALID_SCHEDULE_EMPTY);
+                .thenReturn(SCHEDULE_EMPTY);
 
         checkWorkingWithDefaultSchedule();
     }
@@ -987,6 +991,133 @@
     }
 
     /**
+     * When screen on and single saved network schedule is set
+     * If we have multiple saved networks, the regular connected state scan schedule is used
+     */
+    @Test
+    public void checkScanScheduleForMultipleSavedNetwork() {
+        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+
+        // Set screen to ON
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        // Wait for max scanning interval so that any impact triggered
+        // by screen state change can settle
+        currentTimeStamp += MAX_SCAN_INTERVAL_IN_SCHEDULE * 1000;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+
+        when(mResource.getIntArray(R.array
+                .config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec))
+                .thenReturn(VALID_CONNECTED_SINGLE_SAVED_NETWORK_SCHEDULE);
+
+        WifiConfiguration wifiConfiguration1 = new WifiConfiguration();
+        WifiConfiguration wifiConfiguration2 = new WifiConfiguration();
+        wifiConfiguration1.status = WifiConfiguration.Status.CURRENT;
+        List<WifiConfiguration> wifiConfigurationList = new ArrayList<WifiConfiguration>();
+        wifiConfigurationList.add(wifiConfiguration1);
+        wifiConfigurationList.add(wifiConfiguration2);
+        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(wifiConfigurationList);
+
+        // Set firmware roaming to enabled
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        // Set WiFi to connected state to trigger periodic scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // Get the first periodic scan interval
+        long firstIntervalMs = mAlarmManager
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
+                - currentTimeStamp;
+        assertEquals(VALID_CONNECTED_SINGLE_SCAN_SCHEDULE[0] * 1000, firstIntervalMs);
+    }
+
+    /**
+     * When screen on and single saved network schedule is set
+     * If we have a single saved network (connected network),
+     * the single-saved-network connected state scan schedule is used
+     */
+    @Test
+    public void checkScanScheduleForSingleSavedNetworkConnected() {
+        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+
+        // Set screen to ON
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        // Wait for max scanning interval so that any impact triggered
+        // by screen state change can settle
+        currentTimeStamp += MAX_SCAN_INTERVAL_IN_SCHEDULE * 1000;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+
+        when(mResource.getIntArray(R.array
+                .config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec))
+                .thenReturn(VALID_CONNECTED_SINGLE_SAVED_NETWORK_SCHEDULE);
+
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.status = WifiConfiguration.Status.CURRENT;
+        List<WifiConfiguration> wifiConfigurationList = new ArrayList<WifiConfiguration>();
+        wifiConfigurationList.add(wifiConfiguration);
+        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(wifiConfigurationList);
+
+        // Set firmware roaming to enabled
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        // Set WiFi to connected state to trigger periodic scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // Get the first periodic scan interval
+        long firstIntervalMs = mAlarmManager
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
+                - currentTimeStamp;
+        assertEquals(VALID_CONNECTED_SINGLE_SAVED_NETWORK_SCHEDULE[0] * 1000, firstIntervalMs);
+    }
+
+    /**
+     * When screen on and single saved network schedule is set
+     * If we have a single saved network (not connected network),
+     * the regular connected state scan schedule is used
+     */
+    @Test
+    public void checkScanScheduleForSingleSavedNetwork() {
+        long currentTimeStamp = CURRENT_SYSTEM_TIME_MS;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+
+        // Set screen to ON
+        mWifiConnectivityManager.handleScreenStateChanged(true);
+
+        // Wait for max scanning interval so that any impact triggered
+        // by screen state change can settle
+        currentTimeStamp += MAX_SCAN_INTERVAL_IN_SCHEDULE * 1000;
+        when(mClock.getElapsedSinceBootMillis()).thenReturn(currentTimeStamp);
+
+        when(mResource.getIntArray(R.array
+                .config_wifiSingleSavedNetworkConnectedScanIntervalScheduleSec))
+                .thenReturn(VALID_CONNECTED_SINGLE_SAVED_NETWORK_SCHEDULE);
+
+        // Set firmware roaming to enabled
+        when(mWifiConnectivityHelper.isFirmwareRoamingSupported()).thenReturn(true);
+
+        WifiConfiguration wifiConfiguration = new WifiConfiguration();
+        wifiConfiguration.status = WifiConfiguration.Status.ENABLED;
+        List<WifiConfiguration> wifiConfigurationList = new ArrayList<WifiConfiguration>();
+        wifiConfigurationList.add(wifiConfiguration);
+        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(wifiConfigurationList);
+
+        // Set WiFi to connected state to trigger periodic scan
+        mWifiConnectivityManager.handleConnectionStateChanged(
+                WifiConnectivityManager.WIFI_STATE_CONNECTED);
+
+        // Get the first periodic scan interval
+        long firstIntervalMs = mAlarmManager
+                .getTriggerTimeMillis(WifiConnectivityManager.PERIODIC_SCAN_TIMER_TAG)
+                - currentTimeStamp;
+        assertEquals(VALID_CONNECTED_SINGLE_SCAN_SCHEDULE[0] * 1000, firstIntervalMs);
+    }
+
+    /**
      *  When screen on trigger a disconnected state change event then a connected state
      *  change event back to back to verify that the minium scan interval is enforced.
      *