[DO NOT MERGE]wifi: scan enhancement

Bug: 282660969
Bug: 295775602
Test: unit test & manual test

Change-Id: I2b306b4d1793bffc3b0227043fd774018ba4e675
Merged-In: I2f9cdb638dcb1f98113f39b60a96b0d1e8b22d8c
Merged-In: I85b8c1e94d6270006cce866e13a1879f55161cd1
diff --git a/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java b/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java
new file mode 100644
index 0000000..433e88c
--- /dev/null
+++ b/wifi/java/src/android/net/wifi/nl80211/InstantWifi.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.wifi.nl80211;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @hide
+ */
+public class InstantWifi {
+    private static final String INSTANT_WIFI_TAG = "InstantWifi";
+    private static final int OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS = 1000;
+    private static final int WIFI_NETWORK_EXPIRED_MS = 7 * 24 * 60 * 60 * 1000; // a week
+    private static final String NO_CONNECTION_TIMEOUT_ALARM_TAG =
+            INSTANT_WIFI_TAG + " No Connection Timeout";
+
+    private Context mContext;
+    private AlarmManager mAlarmManager;
+    private Handler mEventHandler;
+    private ConnectivityManager mConnectivityManager;
+    private WifiManager mWifiManager;
+    private PowerManager mPowerManager;
+    private long mLastWifiOnSinceBootMs;
+    private long mLastScreenOnSinceBootMs;
+    private boolean mIsWifiConnected = false;
+    private boolean mScreenOn = false;
+    private boolean mWifiEnabled = false;
+    private boolean mIsNoConnectionAlarmSet = false;
+    private ArrayList<WifiNetwork> mConnectedWifiNetworkList = new ArrayList<>();
+    private AlarmManager.OnAlarmListener mNoConnectionTimeoutCallback =
+            new AlarmManager.OnAlarmListener() {
+                public void onAlarm() {
+                    Log.i(INSTANT_WIFI_TAG, "Timed out waiting for wifi connection");
+                    mIsNoConnectionAlarmSet = false;
+                    mWifiManager.startScan();
+                }
+            };
+
+    public InstantWifi(Context context, AlarmManager alarmManager, Handler eventHandler) {
+        mContext = context;
+        mAlarmManager = alarmManager;
+        mEventHandler = eventHandler;
+        mWifiManager = mContext.getSystemService(WifiManager.class);
+        mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+        mConnectivityManager.registerNetworkCallback(
+                new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+                .build(), new WifiNetworkCallback());
+        // System power service was initialized before wifi nl80211 service.
+        mPowerManager = mContext.getSystemService(PowerManager.class);
+        IntentFilter screenEventfilter = new IntentFilter();
+        screenEventfilter.addAction(Intent.ACTION_SCREEN_ON);
+        screenEventfilter.addAction(Intent.ACTION_SCREEN_OFF);
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                @Override
+                    public void onReceive(Context context, Intent intent) {
+                        String action = intent.getAction();
+                        if (action.equals(Intent.ACTION_SCREEN_ON)) {
+                            if (!mScreenOn) {
+                                mLastScreenOnSinceBootMs = getMockableElapsedRealtime();
+                            }
+                            mScreenOn = true;
+                        } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
+                            mScreenOn = false;
+                        }
+                        Log.d(INSTANT_WIFI_TAG, "mScreenOn is changed to " + mScreenOn);
+                    }
+                }, screenEventfilter, null, mEventHandler);
+        mScreenOn = mPowerManager.isInteractive();
+        mContext.registerReceiver(
+                new BroadcastReceiver() {
+                    @Override
+                    public void onReceive(Context context, Intent intent) {
+                        int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                                WifiManager.WIFI_STATE_UNKNOWN);
+                        mWifiEnabled = state == WifiManager.WIFI_STATE_ENABLED;
+                        if (mWifiEnabled) {
+                            mLastWifiOnSinceBootMs = getMockableElapsedRealtime();
+                        }
+                        Log.d(INSTANT_WIFI_TAG, "mWifiEnabled is changed to " + mWifiEnabled);
+                    }
+                },
+                new IntentFilter(WifiManager.WIFI_STATE_CHANGED_ACTION),
+                null, mEventHandler);
+    }
+
+    @VisibleForTesting
+    protected long getMockableElapsedRealtime() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    private class WifiNetwork {
+        private final int mNetId;
+        private Set<Integer> mConnectedFrequencies = new HashSet<Integer>();
+        private int[] mLastTwoConnectedFrequencies = new int[2];
+        private long mLastConnectedTimeMillis;
+        WifiNetwork(int netId) {
+            mNetId = netId;
+        }
+
+        public int getNetId() {
+            return mNetId;
+        }
+
+        public boolean addConnectedFrequency(int channelFrequency) {
+            mLastConnectedTimeMillis = getMockableElapsedRealtime();
+            if (mLastTwoConnectedFrequencies[0] != channelFrequency
+                    && mLastTwoConnectedFrequencies[1] != channelFrequency) {
+                mLastTwoConnectedFrequencies[0] = mLastTwoConnectedFrequencies[1];
+                mLastTwoConnectedFrequencies[1] = channelFrequency;
+            }
+            return mConnectedFrequencies.add(channelFrequency);
+        }
+
+        public Set<Integer> getConnectedFrequencies() {
+            return mConnectedFrequencies;
+        }
+
+        public int[] getLastTwoConnectedFrequencies() {
+            if ((getMockableElapsedRealtime() - mLastConnectedTimeMillis)
+                    > WIFI_NETWORK_EXPIRED_MS) {
+                return new int[0];
+            }
+            return mLastTwoConnectedFrequencies;
+        }
+
+        public long getLastConnectedTimeMillis() {
+            return mLastConnectedTimeMillis;
+        }
+    }
+
+    private class WifiNetworkCallback extends NetworkCallback {
+        @Override
+        public void onAvailable(@NonNull Network network) {
+        }
+
+        @Override
+        public void onCapabilitiesChanged(Network network,
+                NetworkCapabilities networkCapabilities) {
+            if (networkCapabilities != null && network != null) {
+                WifiInfo wifiInfo = (WifiInfo) networkCapabilities.getTransportInfo();
+                if (wifiInfo == null || mWifiManager == null) {
+                    return;
+                }
+                WifiConfiguration config = mWifiManager.getPrivilegedConnectedNetwork();
+                if (config == null) {
+                    return;
+                }
+                final int currentNetworkId = config.networkId;
+                final int connectecFrequency = wifiInfo.getFrequency();
+                if (connectecFrequency < 0 || currentNetworkId < 0) {
+                    return;
+                }
+                mIsWifiConnected = true;
+                if (mIsNoConnectionAlarmSet) {
+                    mAlarmManager.cancel(mNoConnectionTimeoutCallback);
+                }
+                Log.d(INSTANT_WIFI_TAG, "Receive Wifi is connected, freq =  " + connectecFrequency
+                        + " and currentNetworkId : " + currentNetworkId
+                        + ", wifiinfo = " + wifiInfo);
+                boolean isExist = false;
+                for (WifiNetwork wifiNetwork : mConnectedWifiNetworkList) {
+                    if (wifiNetwork.getNetId() == currentNetworkId) {
+                        if (wifiNetwork.addConnectedFrequency(connectecFrequency)) {
+                            Log.d(INSTANT_WIFI_TAG, "Update connected frequency: "
+                                    + connectecFrequency + " to Network currentNetworkId : "
+                                    + currentNetworkId);
+                        }
+                        isExist = true;
+                    }
+                }
+                if (!isExist) {
+                    WifiNetwork currentNetwork = new WifiNetwork(currentNetworkId);
+                    currentNetwork.addConnectedFrequency(connectecFrequency);
+                    if (mConnectedWifiNetworkList.size() < 5) {
+                        mConnectedWifiNetworkList.add(currentNetwork);
+                    } else {
+                        ArrayList<WifiNetwork> lastConnectedWifiNetworkList = new ArrayList<>();
+                        WifiNetwork legacyNetwork = mConnectedWifiNetworkList.get(0);
+                        for (WifiNetwork connectedNetwork : mConnectedWifiNetworkList) {
+                            if (connectedNetwork.getNetId() == legacyNetwork.getNetId()) {
+                                continue;
+                            }
+                            // Keep the used recently network in the last connected list
+                            if (connectedNetwork.getLastConnectedTimeMillis()
+                                    > legacyNetwork.getLastConnectedTimeMillis()) {
+                                lastConnectedWifiNetworkList.add(connectedNetwork);
+                            } else {
+                                lastConnectedWifiNetworkList.add(legacyNetwork);
+                                legacyNetwork = connectedNetwork;
+                            }
+                        }
+                        mConnectedWifiNetworkList = lastConnectedWifiNetworkList;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onLost(@NonNull Network network) {
+            mIsWifiConnected = false;
+        }
+    }
+
+    /**
+     * Returns whether or not the scan freqs should be overrided by using predicted channels.
+     */
+    public boolean isUsePredictedScanningChannels() {
+        if (mIsWifiConnected || mConnectedWifiNetworkList.size() == 0
+                || !mWifiManager.isWifiEnabled() || !mPowerManager.isInteractive()) {
+            return false;
+        }
+        if (!mWifiEnabled || !mScreenOn) {
+            Log.d(INSTANT_WIFI_TAG, "WiFi/Screen State mis-match, run instant Wifi anyway!");
+            return true;
+        }
+        return (((getMockableElapsedRealtime() - mLastWifiOnSinceBootMs)
+                        < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS)
+                || ((getMockableElapsedRealtime() - mLastScreenOnSinceBootMs)
+                        < OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS));
+    }
+
+    /**
+     * Overrides the frequenies in SingleScanSetting
+     *
+     * @param settings the SingleScanSettings will be overrided.
+     * @param freqs new frequencies of SingleScanSettings
+     */
+    @Nullable
+    public void overrideFreqsForSingleScanSettingsIfNecessary(
+            @Nullable SingleScanSettings settings, @Nullable Set<Integer> freqs) {
+        if (!isUsePredictedScanningChannels() || settings == null || freqs == null
+                || freqs.size() == 0) {
+            return;
+        }
+        if (settings.channelSettings == null) {
+            settings.channelSettings = new ArrayList<>();
+        } else {
+            settings.channelSettings.clear();
+        }
+        for (int freq : freqs) {
+            if (freq > 0) {
+                ChannelSettings channel = new ChannelSettings();
+                channel.frequency = freq;
+                settings.channelSettings.add(channel);
+            }
+        }
+        // Monitor connection after last override scan request.
+        if (mIsNoConnectionAlarmSet) {
+            mAlarmManager.cancel(mNoConnectionTimeoutCallback);
+        }
+        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                getMockableElapsedRealtime() + OVERRIDED_SCAN_CONNECTION_TIMEOUT_MS,
+                NO_CONNECTION_TIMEOUT_ALARM_TAG, mNoConnectionTimeoutCallback, mEventHandler);
+        mIsNoConnectionAlarmSet = true;
+    }
+
+    /**
+     * Returns the predicted scanning chcnnels set.
+     */
+    @NonNull
+    public Set<Integer> getPredictedScanningChannels() {
+        Set<Integer> predictedScanChannels = new HashSet<>();
+        if (!isUsePredictedScanningChannels()) {
+            Log.d(INSTANT_WIFI_TAG, "Drop, size: " + mConnectedWifiNetworkList.size());
+            return predictedScanChannels;
+        }
+        for (WifiNetwork network : mConnectedWifiNetworkList) {
+            for (int connectedFrequency : network.getLastTwoConnectedFrequencies()) {
+                if (connectedFrequency > 0) {
+                    predictedScanChannels.add(connectedFrequency);
+                    Log.d(INSTANT_WIFI_TAG, "Add channel: " + connectedFrequency
+                            + " to predicted channel");
+                }
+            }
+        }
+        return predictedScanChannels;
+    }
+}
diff --git a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
index 2a199d2..ca12c4c 100644
--- a/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
+++ b/wifi/java/src/android/net/wifi/nl80211/WifiNl80211Manager.java
@@ -105,6 +105,8 @@
 
     // Cached wificond binder handlers.
     private IWificond mWificond;
+    private Context mContext;
+    private InstantWifi mInstantWifi;
     private WificondEventHandler mWificondEventHandler = new WificondEventHandler();
     private HashMap<String, IClientInterface> mClientInterfaces = new HashMap<>();
     private HashMap<String, IApInterface> mApInterfaces = new HashMap<>();
@@ -174,6 +176,12 @@
 
     /** @hide */
     @VisibleForTesting
+    protected InstantWifi getInstantWifiMockable() {
+        return mInstantWifi;
+    }
+
+    /** @hide */
+    @VisibleForTesting
     public class WificondEventHandler extends IWificondEventCallback.Stub {
         private Map<CountryCodeChangedListener, Executor> mCountryCodeChangedListenerHolder =
                 new HashMap<>();
@@ -419,6 +427,7 @@
     public WifiNl80211Manager(Context context) {
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mEventHandler = new Handler(context.getMainLooper());
+        mContext = context;
     }
 
     /**
@@ -434,6 +443,7 @@
         if (mWificond == null) {
             Log.e(TAG, "Failed to get reference to wificond");
         }
+        mContext = context;
     }
 
     /** @hide */
@@ -441,6 +451,7 @@
     public WifiNl80211Manager(Context context, IWificond wificond) {
         this(context);
         mWificond = wificond;
+        mContext = context;
     }
 
     /** @hide */
@@ -744,6 +755,9 @@
             Log.e(TAG, "Failed to refresh wificond scanner due to remote exception");
         }
 
+        if (getInstantWifiMockable() == null) {
+            mInstantWifi = new InstantWifi(mContext, mAlarmManager, mEventHandler);
+        }
         return true;
     }
 
@@ -1071,6 +1085,10 @@
         if (settings == null) {
             return false;
         }
+        if (getInstantWifiMockable() != null) {
+            getInstantWifiMockable().overrideFreqsForSingleScanSettingsIfNecessary(settings,
+                    getInstantWifiMockable().getPredictedScanningChannels());
+        }
         try {
             return scannerImpl.scan(settings);
         } catch (RemoteException e1) {
@@ -1115,6 +1133,10 @@
         if (settings == null) {
             return WifiScanner.REASON_INVALID_ARGS;
         }
+        if (getInstantWifiMockable() != null) {
+            getInstantWifiMockable().overrideFreqsForSingleScanSettingsIfNecessary(settings,
+                    getInstantWifiMockable().getPredictedScanningChannels());
+        }
         try {
             int status = scannerImpl.scanRequest(settings);
             return toFrameworkScanStatusCode(status);
diff --git a/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java b/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java
new file mode 100644
index 0000000..ebff0e2
--- /dev/null
+++ b/wifi/tests/src/android/net/wifi/nl80211/InstantWifiTest.java
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.test.TestAlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.IThermalService;
+import android.os.PowerManager;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.InstantWifi}.
+ */
+@SmallTest
+public class InstantWifiTest {
+    @Mock private Context mContext;
+    @Mock private ConnectivityManager mMockConnectivityManager;
+    @Mock private WifiManager mMockWifiManager;
+    @Mock private Network mMockWifiNetwork;
+    @Mock private WifiInfo mMockWifiInfo;
+    @Mock private WifiConfiguration mMockWifiConfiguration;
+    @Mock private IPowerManager mPowerManagerService;
+    private InstantWifi mInstantWifi;
+    private TestLooper mLooper;
+    private Handler mHandler;
+    private TestAlarmManager mTestAlarmManager;
+    private AlarmManager mAlarmManager;
+    private PowerManager mMockPowerManager;
+
+    private final ArgumentCaptor<NetworkCallback> mWifiNetworkCallbackCaptor =
+            ArgumentCaptor.forClass(NetworkCallback.class);
+    private final ArgumentCaptor<BroadcastReceiver> mScreenBroadcastReceiverCaptor =
+            ArgumentCaptor.forClass(BroadcastReceiver.class);
+    private final ArgumentCaptor<BroadcastReceiver> mWifiStateBroadcastReceiverCaptor =
+            ArgumentCaptor.forClass(BroadcastReceiver.class);
+
+    private static final int TEST_NETWORK_ID = 1;
+    private static final int TEST_24G_FREQUENCY = 2412;
+    private static final int TEST_5G_FREQUENCY = 5745;
+    private long mTimeOffsetMs = 0;
+
+    private class InstantWifiSpy extends InstantWifi {
+        InstantWifiSpy(Context context, AlarmManager alarmManager, Handler handler) {
+            super(context, alarmManager, handler);
+        }
+
+        @Override
+        protected long getMockableElapsedRealtime() {
+            return mTimeOffsetMs;
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mLooper = new TestLooper();
+        mHandler = new Handler(mLooper.getLooper());
+
+        mTestAlarmManager = new TestAlarmManager();
+        mAlarmManager = mTestAlarmManager.getAlarmManager();
+        when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn(Context.ALARM_SERVICE);
+        when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+        when(mContext.getSystemServiceName(WifiManager.class)).thenReturn(Context.WIFI_SERVICE);
+        when(mContext.getSystemService(WifiManager.class)).thenReturn(mMockWifiManager);
+        when(mContext.getSystemServiceName(ConnectivityManager.class))
+                .thenReturn(Context.CONNECTIVITY_SERVICE);
+        when(mContext.getSystemService(ConnectivityManager.class))
+                .thenReturn(mMockConnectivityManager);
+        mMockPowerManager = new PowerManager(mContext, mPowerManagerService,
+                mock(IThermalService.class), mHandler);
+        when(mContext.getSystemServiceName(PowerManager.class)).thenReturn(Context.POWER_SERVICE);
+        when(mContext.getSystemService(PowerManager.class)).thenReturn(mMockPowerManager);
+        when(mPowerManagerService.isInteractive()).thenReturn(true);
+
+        doReturn(mMockWifiInfo).when(mMockWifiInfo).makeCopy(anyLong());
+        mTimeOffsetMs = 0;
+        mInstantWifi = new InstantWifiSpy(mContext, mAlarmManager, mHandler);
+        verifyInstantWifiInitialization();
+    }
+
+    private void verifyInstantWifiInitialization() {
+        verify(mMockConnectivityManager).registerNetworkCallback(any(),
+                mWifiNetworkCallbackCaptor.capture());
+        verify(mContext).registerReceiver(mScreenBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(Intent.ACTION_SCREEN_ON)
+                                && filter.hasAction(Intent.ACTION_SCREEN_OFF)), eq(null), any());
+
+        verify(mContext).registerReceiver(mWifiStateBroadcastReceiverCaptor.capture(),
+                argThat((IntentFilter filter) ->
+                        filter.hasAction(WifiManager.WIFI_STATE_CHANGED_ACTION)), eq(null), any());
+    }
+
+    private void mockWifiConnectedEvent(int networkId, int connectedFrequency) {
+        // Send wifi connected event
+        NetworkCapabilities mockWifiNetworkCapabilities =
+                new NetworkCapabilities.Builder().setTransportInfo(mMockWifiInfo).build();
+        mMockWifiConfiguration.networkId = networkId;
+        when(mMockWifiManager.getPrivilegedConnectedNetwork()).thenReturn(mMockWifiConfiguration);
+        when(mMockWifiInfo.getFrequency()).thenReturn(connectedFrequency);
+        mWifiNetworkCallbackCaptor.getValue().onCapabilitiesChanged(mMockWifiNetwork,
+                mockWifiNetworkCapabilities);
+        mLooper.dispatchAll();
+    }
+
+    private void mockWifiOnScreenOnBroadcast(boolean isWifiOn, boolean isScreenOn)
+            throws Exception {
+        // Send Wifi On broadcast
+        Intent wifiOnIntent = new Intent(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        wifiOnIntent.putExtra(WifiManager.EXTRA_WIFI_STATE,
+                isWifiOn ? WifiManager.WIFI_STATE_ENABLED : WifiManager.WIFI_STATE_DISABLED);
+        mWifiStateBroadcastReceiverCaptor.getValue().onReceive(mContext, wifiOnIntent);
+        mLooper.dispatchAll();
+        // Send Screen On broadcast
+        Intent screenOnIntent =
+                new Intent(isScreenOn ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF);
+        mScreenBroadcastReceiverCaptor.getValue().onReceive(mContext, screenOnIntent);
+        mLooper.dispatchAll();
+        when(mMockWifiManager.isWifiEnabled()).thenReturn(isWifiOn);
+        when(mPowerManagerService.isInteractive()).thenReturn(isScreenOn);
+    }
+
+    @Test
+    public void testisUsePredictedScanningChannels() throws Exception {
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, false /* isScreenOn */);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        mockWifiOnScreenOnBroadcast(false /* isWifiOn */, true /* isScreenOn */);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Send wifi connected event
+        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
+        // Shift time to make it expired
+        mTimeOffsetMs = 1100;
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+    }
+
+    @Test
+    public void testGetPredictedScanningChannels() throws Exception {
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        // Send wifi connected event on T0
+        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
+        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_24G_FREQUENCY));
+        mTimeOffsetMs += 1000; // T1 = 1000 ms
+        // Send wifi connected event
+        mockWifiConnectedEvent(TEST_NETWORK_ID + 1, TEST_5G_FREQUENCY);
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        // isUsePredictedScanningChannels is false since wifi on & screen on is expired
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Override the Wifi On & Screen on time
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_5G_FREQUENCY));
+        mTimeOffsetMs += 7 * 24 * 60 * 60 * 1000; // Make T0 expired
+        // Override the Wifi On & Screen on time
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        assertFalse(mInstantWifi.getPredictedScanningChannels().contains(TEST_24G_FREQUENCY));
+        assertTrue(mInstantWifi.getPredictedScanningChannels().contains(TEST_5G_FREQUENCY));
+    }
+
+    @Test
+    public void testOverrideFreqsForSingleScanSettings() throws Exception {
+        mockWifiOnScreenOnBroadcast(true /* isWifiOn */, true /* isScreenOn */);
+        // Send wifi connected event
+        mockWifiConnectedEvent(TEST_NETWORK_ID, TEST_24G_FREQUENCY);
+        assertFalse(mInstantWifi.isUsePredictedScanningChannels());
+        // Send wifi disconnect
+        mWifiNetworkCallbackCaptor.getValue().onLost(mMockWifiNetwork);
+        assertTrue(mInstantWifi.isUsePredictedScanningChannels());
+
+        final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+                ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+        doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+                alarmListenerCaptor.capture(), any());
+        Set<Integer> testFreqs = Set.of(
+                TEST_24G_FREQUENCY, TEST_5G_FREQUENCY);
+        SingleScanSettings testSingleScanSettings = new SingleScanSettings();
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(
+                testSingleScanSettings, new HashSet<Integer>());
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(
+                testSingleScanSettings, null);
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(null, null);
+        verify(mAlarmManager, never()).set(anyInt(), anyLong(), any(), any(), any());
+        mInstantWifi.overrideFreqsForSingleScanSettingsIfNecessary(testSingleScanSettings,
+                testFreqs);
+        verify(mAlarmManager).set(anyInt(), anyLong(), any(), any(), any());
+        Set<Integer> overridedFreqs = new HashSet<Integer>();
+        for (ChannelSettings channel : testSingleScanSettings.channelSettings) {
+            overridedFreqs.add(channel.frequency);
+        }
+        assertEquals(testFreqs, overridedFreqs);
+        alarmListenerCaptor.getValue().onAlarm();
+        verify(mMockWifiManager).startScan();
+    }
+}
diff --git a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
index 362eb14..f12818f 100644
--- a/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -26,6 +27,7 @@
 import static org.mockito.Matchers.argThat;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -37,6 +39,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AlarmManager;
+import android.app.test.MockAnswerUtil.AnswerWithArguments;
 import android.app.test.TestAlarmManager;
 import android.content.Context;
 import android.net.MacAddress;
@@ -104,6 +107,9 @@
     private WifiNl80211Manager.CountryCodeChangedListener mCountryCodeChangedListener2;
     @Mock
     private Context mContext;
+    @Mock
+    private InstantWifi mMockInstantWifi;
+
     private TestLooper mLooper;
     private TestAlarmManager mTestAlarmManager;
     private AlarmManager mAlarmManager;
@@ -167,6 +173,17 @@
             0x00, 0x00
     };
 
+    private class WifiNl80211ManagerSpy extends WifiNl80211Manager {
+        WifiNl80211ManagerSpy(Context context, IWificond wificond) {
+            super(context, wificond);
+        }
+
+        @Override
+        protected InstantWifi getInstantWifiMockable() {
+            return mMockInstantWifi;
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         // Setup mocks for successful WificondControl operation. Failure case mocks should be
@@ -181,6 +198,8 @@
         mLooper = new TestLooper();
         when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
 
+        doNothing().when(mMockInstantWifi).overrideFreqsForSingleScanSettingsIfNecessary(
+                any(), any());
         when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
@@ -189,7 +208,7 @@
         when(mWificond.tearDownApInterface(any())).thenReturn(true);
         when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
         when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
-        mWificondControl = new WifiNl80211Manager(mContext, mWificond);
+        mWificondControl = new WifiNl80211ManagerSpy(mContext, mWificond);
         mWificondEventHandler = mWificondControl.getWificondEventHandler();
         assertEquals(true,
                 mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
@@ -1159,6 +1178,40 @@
         verify(mWificond).notifyCountryCodeChanged();
     }
 
+    @Test
+    public void testInstantWifi() throws Exception {
+        doAnswer(new AnswerWithArguments() {
+            public void answer(SingleScanSettings settings, Set<Integer> freqs) {
+                if (settings.channelSettings == null) {
+                    settings.channelSettings = new ArrayList<>();
+                } else {
+                    settings.channelSettings.clear();
+                }
+                for (int freq : freqs) {
+                    if (freq > 0) {
+                        ChannelSettings channel = new ChannelSettings();
+                        channel.frequency = freq;
+                        settings.channelSettings.add(channel);
+                    }
+                }
+            }
+        }).when(mMockInstantWifi).overrideFreqsForSingleScanSettingsIfNecessary(
+                any(), any());
+        Set<Integer> testPredictedChannelsSet = Set.of(2412, 5745);
+        assertNotEquals(testPredictedChannelsSet, SCAN_FREQ_SET);
+        when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+        when(mMockInstantWifi.getPredictedScanningChannels()).thenReturn(testPredictedChannelsSet);
+
+        // Trigger scan to check scan settings are changed
+        assertTrue(mWificondControl.startScan(
+                TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+                SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+        verify(mMockInstantWifi).getPredictedScanningChannels();
+        verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+                IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+                testPredictedChannelsSet, SCAN_HIDDEN_NETWORK_SSID_LIST, false, null)));
+    }
+
     // Create a ArgumentMatcher which captures a SingleScanSettings parameter and checks if it
     // matches the provided frequency set and ssid set.
     private class ScanMatcher implements ArgumentMatcher<SingleScanSettings> {