/*
 * Copyright 2017 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 com.android.server.wifi;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
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 static org.mockito.Mockito.withSettings;

import android.content.Context;
import android.database.ContentObserver;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkSuggestion;
import android.net.wifi.WifiScanner;
import android.net.wifi.WifiSsid;
import android.net.wifi.util.ScanResultUtil;
import android.os.Handler;
import android.os.test.TestLooper;
import android.provider.Settings;

import androidx.test.filters.SmallTest;

import com.android.server.wifi.ActiveModeWarden.PrimaryClientModeManagerChangedCallback;
import com.android.server.wifi.util.LastCallerInfoManager;
import com.android.server.wifi.util.WifiConfigStoreEncryptionUtil;
import com.android.server.wifi.util.WifiPermissionsUtil;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
import org.xmlpull.v1.XmlPullParserException;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * Unit tests for {@link WakeupController}.
 */
@SmallTest
public class WakeupControllerTest extends WifiBaseTest {

    private static final String SAVED_SSID = "test scan ssid";
    private static final int DFS_CHANNEL_FREQ = 5540;
    private static final int TEST_PRIORITY_GROUP =
            WifiNetworkSuggestionsManager.DEFAULT_PRIORITY_GROUP;

    @Mock private Context mContext;
    @Mock private WakeupLock mWakeupLock;
    @Mock private WakeupEvaluator mWakeupEvaluator;
    @Mock private WakeupOnboarding mWakeupOnboarding;
    @Mock private WifiConfigStore mWifiConfigStore;
    @Mock private WifiInjector mWifiInjector;
    @Mock private WifiScanner mWifiScanner;
    @Mock private WifiConfigManager mWifiConfigManager;
    @Mock private WifiNetworkSuggestionsManager mWifiNetworkSuggestionsManager;
    @Mock private FrameworkFacade mFrameworkFacade;
    @Mock private WifiSettingsStore mWifiSettingsStore;
    @Mock private WifiWakeMetrics mWifiWakeMetrics;
    @Mock private WifiMetrics mWifiMetrics;
    @Mock private WifiPermissionsUtil mWifiPermissionsUtil;
    @Mock private ActiveModeWarden mActiveModeWarden;
    @Mock private WifiNative mWifiNative;
    @Mock private Clock mClock;
    @Mock private ConcreteClientModeManager mPrimaryClientModeManager;
    @Mock private WifiGlobals mWifiGlobals;
    @Mock private LastCallerInfoManager mLastCallerInfoManager;

    @Captor private ArgumentCaptor<PrimaryClientModeManagerChangedCallback> mPrimaryChangedCaptor;

    private TestLooper mLooper;
    private WakeupController mWakeupController;
    private WakeupConfigStoreData mWakeupConfigStoreData;
    private WifiScanner.ScanData[] mTestScanDatas;
    private ScanResult mTestScanResult;
    private MockitoSession mSession;

    /** Initialize objects before each test run. */
    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        // static mocking
        mSession = mockitoSession()
                .mockStatic(WifiInjector.class, withSettings().lenient())
                .strictness(Strictness.LENIENT)
                .startMocking();

        when(WifiInjector.getInstance()).thenReturn(mWifiInjector);
        when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
        when(mWifiInjector.getWifiSettingsStore()).thenReturn(mWifiSettingsStore);
        when(mWifiInjector.getActiveModeWarden()).thenReturn(mActiveModeWarden);
        when(mActiveModeWarden.getPrimaryClientModeManager()).thenReturn(mPrimaryClientModeManager);
        when(mPrimaryClientModeManager.getSupportedFeatures()).thenReturn(
                WifiManager.WIFI_FEATURE_WPA3_SAE | WifiManager.WIFI_FEATURE_OWE);
        when(mWifiInjector.getWifiNative()).thenReturn(mWifiNative);
        when(mWifiNative.getChannelsForBand(WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY))
                .thenReturn(new int[]{DFS_CHANNEL_FREQ});
        when(mWifiInjector.getWifiGlobals()).thenReturn(mWifiGlobals);
        when(mWifiInjector.getLastCallerInfoManager()).thenReturn(mLastCallerInfoManager);
        when(mWifiInjector.getWifiMetrics()).thenReturn(mWifiMetrics);
        when(mWifiInjector.getWifiPermissionsUtil()).thenReturn(mWifiPermissionsUtil);
        when(mWifiGlobals.isWpa3SaeUpgradeEnabled()).thenReturn(true);
        when(mWifiGlobals.isOweUpgradeEnabled()).thenReturn(true);

        when(mWifiSettingsStore.handleWifiToggled(anyBoolean())).thenReturn(true);
        when(mWifiSettingsStore.isScanAlwaysAvailableToggleEnabled()).thenReturn(true);
        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(true);
        // Saved network needed to start wake.
        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork();
        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
        openNetwork.validatedInternetAccess = true;
        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(Arrays.asList(openNetwork));

        mLooper = new TestLooper();

        // scanlistener input
        mTestScanResult = new ScanResult();
        mTestScanResult.SSID = SAVED_SSID;
        mTestScanResult.setWifiSsid(WifiSsid.fromUtf8Text(SAVED_SSID));
        mTestScanResult.capabilities = "";
        mTestScanResult.frequency = 2412;
        ScanResult[] scanResults = new ScanResult[1];
        scanResults[0] = mTestScanResult;
        mTestScanDatas = new WifiScanner.ScanData[1];
        int scanBand = WifiScanner.WIFI_BAND_ALL & ~WifiScanner.WIFI_BAND_5_GHZ_DFS_ONLY;
        mTestScanDatas[0] = new WifiScanner.ScanData(0 /* id */, 0 /* flags */,
                0 /* bucketsScanned */, scanBand /* bandScanned */, scanResults);
    }

    /**
     * Called after each test
     */
    @After
    public void cleanup() {
        if (mSession != null) {
            mSession.finishMocking();
        }
    }

    /** Initializes the wakeupcontroller in the given {@code enabled} state. */
    private void initializeWakeupController(boolean enabled) {
        initializeWakeupController(enabled, true /* isRead */);
    }

    private void initializeWakeupController(boolean enabled, boolean isRead) {
        int settingsValue = enabled ? 1 : 0;
        when(mFrameworkFacade.getIntegerSetting(mContext,
                Settings.Global.WIFI_WAKEUP_ENABLED, 0)).thenReturn(settingsValue);
        when(mWakeupOnboarding.isOnboarded()).thenReturn(true);
        mWakeupController = new WakeupController(mContext,
                new Handler(mLooper.getLooper()),
                mWakeupLock,
                mWakeupEvaluator,
                mWakeupOnboarding,
                mWifiConfigManager,
                mWifiConfigStore,
                mWifiNetworkSuggestionsManager,
                mWifiWakeMetrics,
                mWifiInjector,
                mFrameworkFacade,
                mClock, mActiveModeWarden);
        mWakeupController.enableVerboseLogging(true);

        verify(mActiveModeWarden).registerPrimaryClientModeManagerChangedCallback(
                mPrimaryChangedCaptor.capture());

        ArgumentCaptor<WakeupConfigStoreData> captor =
                ArgumentCaptor.forClass(WakeupConfigStoreData.class);
        verify(mWifiConfigStore).registerStoreData(captor.capture());
        mWakeupConfigStoreData = captor.getValue();
        if (isRead) {
            readUserStore();
        }
    }

    private void readUserStore() {
        try {
            mWakeupConfigStoreData.deserializeData(null, 0,
                    WifiConfigStore.ENCRYPT_CREDENTIALS_CONFIG_STORE_DATA_VERSION,
                    mock(WifiConfigStoreEncryptionUtil.class));
        } catch (XmlPullParserException | IOException e) {
            // unreachable
        }
    }

    private ScanResult createOpenScanResult(String utf8Ssid, int frequency) {
        ScanResult scanResult = new ScanResult();
        scanResult.SSID = utf8Ssid;
        scanResult.setWifiSsid(WifiSsid.fromUtf8Text(utf8Ssid));
        scanResult.capabilities = "";
        scanResult.frequency = frequency;
        return scanResult;
    }

    private ScanResult createOweScanResult(String utf8Ssid, int frequency) {
        ScanResult scanResult = new ScanResult();
        scanResult.SSID = utf8Ssid;
        scanResult.setWifiSsid(WifiSsid.fromUtf8Text(utf8Ssid));
        scanResult.capabilities = "OWE";
        scanResult.frequency = frequency;
        return scanResult;
    }

    private void verifyDoesNotEnableWifi() {
        verify(mWifiSettingsStore, never()).handleWifiToggled(true /* wifiEnabled */);
        verify(mLastCallerInfoManager, never()).put(eq(WifiManager.API_WIFI_ENABLED),
                anyInt(), anyInt(), anyInt(), any(), anyBoolean());
    }

    /**
     * Verify WakeupController is enabled when the settings toggle is true.
     */
    @Test
    public void verifyEnabledWhenToggledOn() {
        initializeWakeupController(true /* enabled */);

        assertTrue(mWakeupController.isEnabledAndReady());
    }

    /**
     * Verify WakeupController is disabled when the settings toggle is false.
     */
    @Test
    public void verifyDisabledWhenToggledOff() {
        initializeWakeupController(false /* enabled */);

        assertFalse(mWakeupController.isEnabledAndReady());
    }

    /**
     * Verify WakeupController is disabled when the location mode is disabled.
     */
    @Test
    public void verifyDisabledWhenLocationOff() {
        initializeWakeupController(true /* enabled */);
        when(mWifiPermissionsUtil.isLocationModeEnabled()).thenReturn(false);
        assertFalse(mWakeupController.isEnabledAndReady());
    }

    /**
     * Verify WakeupController is disabled when scan is disabled.
     */
    @Test
    public void verifyDisabledWhenScanOff() {
        initializeWakeupController(true /* enabled */);
        when(mWifiSettingsStore.isScanAlwaysAvailableToggleEnabled()).thenReturn(false);
        assertFalse(mWakeupController.isEnabledAndReady());
    }

    /**
     * Verify WakeupController registers its store data with the WifiConfigStore on construction.
     */
    @Test
    public void registersWakeupConfigStoreData() {
        initializeWakeupController(true /* enabled */);
        verify(mWifiConfigStore).registerStoreData(any(WakeupConfigStoreData.class));
    }

    /**
     * Verify that dump calls also dump the state of the WakeupLock.
     */
    @Test
    public void dumpIncludesWakeupLock() {
        initializeWakeupController(true /* enabled */);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        PrintWriter writer = new PrintWriter(stream);
        mWakeupController.dump(null, writer, null);

        verify(mWakeupLock).dump(null, writer, null);
    }

    /**
     * Verify that start sets the wakeup lock.
     */
    @Test
    public void startSetsWakeupLock() {
        initializeWakeupController(true /* enabled */);
        mWakeupController.start();
        verify(mWakeupLock).setLock(any());
        verify(mWifiWakeMetrics).recordStartEvent(anyInt());
    }

    /**
     * Verify that start does not record an ignored start call if the controller is not yet active.
     */
    @Test
    public void startDoesNotRecordIgnoredStart() {
        initializeWakeupController(true /* enabled */);
        mWakeupController.start();
        verify(mWifiWakeMetrics, never()).recordIgnoredStart();
    }

    /**
     * Verify that start does not set the wakeup lock when feature is disabled.
     */
    @Test
    public void startDoesNotSetWakeupLockWhenDisabled() {
        initializeWakeupController(false /* enabled */);
        mWakeupController.start();
        verify(mWakeupLock, never()).setLock(any());
        verify(mWifiWakeMetrics, never()).recordStartEvent(anyInt());
    }


    /**
     * Verify that start does not set the wakeup lock when feature is disabled.
     */
    @Test
    public void startDoesNotSetWakeupLockWhenNoSavedNetworksOrSuggestions() {
        when(mWifiConfigManager.getSavedNetworks(anyInt())).thenReturn(Collections.emptyList());
        initializeWakeupController(false /* enabled */);
        mWakeupController.start();
        verify(mWakeupLock, never()).setLock(any());
        verify(mWifiWakeMetrics, never()).recordStartEvent(anyInt());
    }

    /**
     * If the controller is already active, verify that start() is ignored and no setup is done.
     */
    @Test
    public void startIsIgnoredIfAlreadyActive() {
        initializeWakeupController(true /* enabled */);
        InOrder lockInOrder = Mockito.inOrder(mWakeupLock);
        InOrder metricsInOrder = Mockito.inOrder(mWifiWakeMetrics);

        mWakeupController.start();
        lockInOrder.verify(mWakeupLock).setLock(any());
        metricsInOrder.verify(mWifiWakeMetrics).recordStartEvent(anyInt());

        mWakeupController.stop();
        mWakeupController.start();
        metricsInOrder.verify(mWifiWakeMetrics).recordIgnoredStart();
        metricsInOrder.verify(mWifiWakeMetrics, never()).recordStartEvent(anyInt());
        lockInOrder.verify(mWakeupLock, never()).setLock(any());
    }

    /**
     * Verify that start registers the scan listener on the wifi scanner.
     */
    @Test
    public void startRegistersScanListener() {
        initializeWakeupController(true /* enabled */);
        mWakeupController.start();
        verify(mWifiScanner).registerScanListener(any(), any());
    }

    /**
     * Verify that stop deregisters the scan listener from the wifi scanner.
     */
    @Test
    public void stopDeresgistersScanListener() {
        initializeWakeupController(true /* enabled */);
        mWakeupController.start();
        mWakeupController.stop();
        verify(mWifiScanner).unregisterScanListener(any());
    }

    /**
     * Verify that reset sets active to false.
     *
     * <p>This is accomplished by initiating another call to start and verifying that the wakeup
     * lock is re-set.
     */
    @Test
    public void resetSetsActiveToFalse() {
        initializeWakeupController(true /* enabled */);
        InOrder lockInOrder = Mockito.inOrder(mWakeupLock);
        InOrder metricsInOrder = Mockito.inOrder(mWifiWakeMetrics);

        mWakeupController.start();
        lockInOrder.verify(mWakeupLock).setLock(any());
        metricsInOrder.verify(mWifiWakeMetrics).recordStartEvent(anyInt());

        mWakeupController.stop();
        mPrimaryChangedCaptor.getValue().onChange(null, mPrimaryClientModeManager);
        metricsInOrder.verify(mWifiWakeMetrics).recordResetEvent(0 /* numScans */);

        mWakeupController.start();
        lockInOrder.verify(mWakeupLock).setLock(any());
        metricsInOrder.verify(mWifiWakeMetrics).recordStartEvent(anyInt());
    }

    /**
     * Verify that the wakeup lock is initialized with the intersection of ScanResults and saved
     * networks.
     */
    @Test
    public void startInitializesWakeupLockWithSavedScanResults() {
        String ssid1 = "ssid 1";
        String ssid2 = "ssid 2";
        String quotedSsid = ScanResultUtil.createQuotedSsid(ssid1);

        // saved configs
        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(quotedSsid);
        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
        openNetwork.validatedInternetAccess = true;
        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
        wepNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
        wepNetwork.validatedInternetAccess = true;
        when(mWifiConfigManager.getSavedNetworks(anyInt()))
                .thenReturn(Arrays.asList(openNetwork, wepNetwork));

        // scan results from most recent scan
        ScanResult savedScanResult = createOpenScanResult(ssid1, 2412 /* frequency */);
        ScanResult unsavedScanResult = createOpenScanResult(ssid2, 2412 /* frequency */);

        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                .thenReturn(Arrays.asList(savedScanResult, unsavedScanResult));

        // intersection of most recent scan + saved configs
        Set<ScanResultMatchInfo> expectedMatchInfos =
                Collections.singleton(ScanResultMatchInfo.fromScanResult(savedScanResult));

        initializeWakeupController(true /* enabled */);
        mWakeupController.start();
        verify(mWakeupLock).setLock(eq(expectedMatchInfos));
        verify(mWifiWakeMetrics).recordStartEvent(expectedMatchInfos.size());
    }

    /**
     * Verify that the wakeup lock is initialized with the intersection of ScanResults and network
     * suggestions.
     */
    @Test
    public void startInitializesWakeupLockWithNetworkSuggestions() {
        String ssid1 = "ssid 1";
        String ssid2 = "ssid 2";
        String quotedSsid = ScanResultUtil.createQuotedSsid(ssid1);

        // suggestions
        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(quotedSsid);
        WifiNetworkSuggestion openNetworkSuggestion =
                new WifiNetworkSuggestion(openNetwork, null, false, false, true, true,
                        TEST_PRIORITY_GROUP);
        WifiConfiguration wepNetwork = WifiConfigurationTestUtil.createWepNetwork();
        WifiNetworkSuggestion wepNetworkSuggestion =
                new WifiNetworkSuggestion(wepNetwork, null, false, false, true, true,
                        TEST_PRIORITY_GROUP);
        when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                .thenReturn(new HashSet<>(Arrays.asList(
                        openNetworkSuggestion, wepNetworkSuggestion)));

        // scan results from most recent scan
        ScanResult savedScanResult = createOpenScanResult(ssid1, 2412 /* frequency */);
        ScanResult unsavedScanResult = createOpenScanResult(ssid2, 2412 /* frequency */);

        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                .thenReturn(Arrays.asList(savedScanResult, unsavedScanResult));

        // intersection of most recent scan + saved configs
        Set<ScanResultMatchInfo> expectedMatchInfos =
                Collections.singleton(ScanResultMatchInfo.fromScanResult(savedScanResult));

        initializeWakeupController(true /* enabled */);
        mWakeupController.start();
        verify(mWakeupLock).setLock(eq(expectedMatchInfos));
        verify(mWifiWakeMetrics).recordStartEvent(expectedMatchInfos.size());
    }

    /**
     * Verify that the wakeup lock is initialized with the intersection of ScanResults and saved
     * networks/network suggestions.
     */
    @Test
    public void startInitializesWakeupLockWithSavedScanResultsAndNetworkSuggestions() {
        String ssid1 = "ssid 1";
        String ssid2 = "ssid 2";
        String ssid3 = "ssid 3";
        String quotedSsid1 = ScanResultUtil.createQuotedSsid(ssid1);
        String quotedSsid2 = ScanResultUtil.createQuotedSsid(ssid2);

        // saved config + suggestion
        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(quotedSsid1);
        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
        openNetwork.validatedInternetAccess = true;
        when(mWifiConfigManager.getSavedNetworks(anyInt()))
                .thenReturn(Arrays.asList(openNetwork));

        WifiConfiguration oweNetwork = WifiConfigurationTestUtil.createOweNetwork(quotedSsid2);
        WifiNetworkSuggestion oweNetworkSuggestion =
                new WifiNetworkSuggestion(oweNetwork, null, false, false, true, true,
                        TEST_PRIORITY_GROUP);
        when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                .thenReturn(new HashSet<>(Arrays.asList(oweNetworkSuggestion)));

        // scan results from most recent scan
        ScanResult savedScanResult = createOpenScanResult(ssid1, 2412 /* frequency */);
        ScanResult suggestionScanResult = createOweScanResult(ssid2, 2412 /* frequency */);
        ScanResult unknownScanResult = createOpenScanResult(ssid3, 2412 /* frequency */);

        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                .thenReturn(Arrays.asList(savedScanResult, suggestionScanResult,
                        unknownScanResult));

        // intersection of most recent scan + saved configs/suggestions
        Set<ScanResultMatchInfo> expectedMatchInfos = Set.of(
                ScanResultMatchInfo.fromScanResult(savedScanResult),
                ScanResultMatchInfo.fromScanResult(suggestionScanResult));

        initializeWakeupController(true /* enabled */);
        mWakeupController.start();
        verify(mWakeupLock).setLock(eq(expectedMatchInfos));
        verify(mWifiWakeMetrics).recordStartEvent(expectedMatchInfos.size());
    }

    /**
     * Verify that saved networks suggestions ignore captive portal without validated network.
     */
    @Test
    public void getGoodSavedNetworksAndSuggestionsIgnoreInvalidatedCaptivePortal() {
        String ssid1 = "ssid 1";
        String quotedSsid1 = ScanResultUtil.createQuotedSsid(ssid1);

        // saved captive portal config without validated network
        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(quotedSsid1);
        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
        openNetwork.getNetworkSelectionStatus().setHasNeverDetectedCaptivePortal(false);
        openNetwork.validatedInternetAccess = false;
        when(mWifiConfigManager.getSavedNetworks(anyInt()))
                .thenReturn(Arrays.asList(openNetwork));

        initializeWakeupController(true);
        mWakeupController.start();
        verify(mWifiInjector, never()).getWifiScanner();
    }

    /**
     * Verify that saved networks suggestions include captive portal with validated network.
     */
    @Test
    public void getGoodSavedNetworksAndSuggestionsIncludeValidatedCaptivePortal() {
        String ssid1 = "ssid 1";
        String quotedSsid1 = ScanResultUtil.createQuotedSsid(ssid1);

        // saved captive portal config with validated network
        WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(quotedSsid1);
        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
        openNetwork.getNetworkSelectionStatus().setHasNeverDetectedCaptivePortal(false);
        openNetwork.validatedInternetAccess = true;
        when(mWifiConfigManager.getSavedNetworks(anyInt()))
                .thenReturn(Arrays.asList(openNetwork));

        initializeWakeupController(true);
        mWakeupController.start();
        verify(mWifiInjector).getWifiScanner();
    }

    /**
     * Verify that start filters out DFS channels.
     */
    @Test
    public void startFiltersOutDfsScanResults() {
        String ssidDfs = "DFS scan";
        String ssid24 = "2.4 scan";

        // saved configs
        WifiConfiguration openNetworkDfs = WifiConfigurationTestUtil
                .createOpenNetwork(ScanResultUtil.createQuotedSsid(ssidDfs));
        openNetworkDfs.getNetworkSelectionStatus().setHasEverConnected(true);
        openNetworkDfs.validatedInternetAccess = true;
        WifiConfiguration openNetwork24 = WifiConfigurationTestUtil
                .createOpenNetwork(ScanResultUtil.createQuotedSsid(ssid24));
        openNetwork24.getNetworkSelectionStatus().setHasEverConnected(true);
        openNetwork24.validatedInternetAccess = true;

        when(mWifiConfigManager.getSavedNetworks(anyInt()))
                .thenReturn(Arrays.asList(openNetworkDfs, openNetwork24));

        // scan results from most recent scan
        ScanResult scanResultDfs = createOpenScanResult(ssidDfs, DFS_CHANNEL_FREQ);
        ScanResult scanResult24 = createOpenScanResult(ssid24, 2412 /* frequency */);

        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                .thenReturn(Arrays.asList(scanResultDfs, scanResult24));

        // should filter out scanResultDfs
        Set<ScanResultMatchInfo> expectedMatchInfos =
                Collections.singleton(ScanResultMatchInfo.fromScanResult(scanResult24));

        initializeWakeupController(true /* enabled */);
        mWakeupController.start();
        verify(mWakeupLock).setLock(eq(expectedMatchInfos));
        verify(mWifiWakeMetrics).recordStartEvent(expectedMatchInfos.size());
    }

    /**
     * Verify that onResults updates the WakeupLock.
     */
    @Test
    public void onResultsUpdatesWakeupLockForSavedNetworks() {
        // saved config
        WifiConfiguration openNetwork = WifiConfigurationTestUtil
                .createOpenNetwork(ScanResultUtil.createQuotedSsid(SAVED_SSID));
        openNetwork.getNetworkSelectionStatus().setHasEverConnected(true);
        openNetwork.validatedInternetAccess = true;
        when(mWifiConfigManager.getSavedNetworks(anyInt()))
                .thenReturn(Collections.singletonList(openNetwork));

        initializeWakeupController(true /* enabled */);
        mWakeupController.start();

        ArgumentCaptor<WifiScanner.ScanListener> scanListenerArgumentCaptor =
                ArgumentCaptor.forClass(WifiScanner.ScanListener.class);

        verify(mWifiScanner).registerScanListener(any(), scanListenerArgumentCaptor.capture());
        WifiScanner.ScanListener scanListener = scanListenerArgumentCaptor.getValue();

        // incoming scan results
        scanListener.onResults(mTestScanDatas);

        ScanResultMatchInfo expectedMatchInfo = ScanResultMatchInfo.fromScanResult(mTestScanResult);
        verify(mWakeupLock).update(eq(Collections.singleton(expectedMatchInfo)));
    }

    /**
     * Verify that onResults updates the WakeupLock.
     */
    @Test
    public void onResultsUpdatesWakeupLockForNetworkSuggestions() {
        // suggestions
        WifiConfiguration openNetwork = WifiConfigurationTestUtil
                .createOpenNetwork(ScanResultUtil.createQuotedSsid(SAVED_SSID));
        WifiNetworkSuggestion openNetworkSuggestion =
                new WifiNetworkSuggestion(openNetwork, null, false, false, true, true,
                        TEST_PRIORITY_GROUP);
        when(mWifiNetworkSuggestionsManager.getAllApprovedNetworkSuggestions())
                .thenReturn(new HashSet<>(Collections.singletonList(openNetworkSuggestion)));

        initializeWakeupController(true /* enabled */);
        mWakeupController.start();

        ArgumentCaptor<WifiScanner.ScanListener> scanListenerArgumentCaptor =
                ArgumentCaptor.forClass(WifiScanner.ScanListener.class);

        verify(mWifiScanner).registerScanListener(any(), scanListenerArgumentCaptor.capture());
        WifiScanner.ScanListener scanListener = scanListenerArgumentCaptor.getValue();

        // incoming scan results
        scanListener.onResults(mTestScanDatas);

        ScanResultMatchInfo expectedMatchInfo = ScanResultMatchInfo.fromScanResult(mTestScanResult);
        verify(mWakeupLock).update(eq(Collections.singleton(expectedMatchInfo)));
    }

    /**
     * Verify that onResults filters out unsaved networks when updating the WakeupLock.
     */
    @Test
    public void onResultsUpdatesWakeupLockWithOnlySavedNetworks() {
        // no matching saved configs

        initializeWakeupController(true /* enabled */);
        mWakeupController.start();

        ArgumentCaptor<WifiScanner.ScanListener> scanListenerArgumentCaptor =
                ArgumentCaptor.forClass(WifiScanner.ScanListener.class);

        verify(mWifiScanner).registerScanListener(any(), scanListenerArgumentCaptor.capture());
        WifiScanner.ScanListener scanListener = scanListenerArgumentCaptor.getValue();

        // incoming scan results
        scanListener.onResults(mTestScanDatas);

        verify(mWakeupLock).update(eq(Collections.emptySet()));
    }

    /**
     * Verify that onResults filters out DFS channels.
     */
    @Test
    public void onResultsFiltersOutDfsScanResults() {
        initializeWakeupController(true /* enabled */);
        mWakeupController.start();

        ArgumentCaptor<WifiScanner.ScanListener> scanListenerArgumentCaptor =
                ArgumentCaptor.forClass(WifiScanner.ScanListener.class);

        verify(mWifiScanner).registerScanListener(any(), scanListenerArgumentCaptor.capture());
        WifiScanner.ScanListener scanListener = scanListenerArgumentCaptor.getValue();

        // incoming scan results
        mTestScanResult.frequency = DFS_CHANNEL_FREQ;
        scanListener.onResults(mTestScanDatas);

        verify(mWakeupLock).update(eq(Collections.emptySet()));
    }

    /**
     * Verify that the controller searches for viable networks during onResults when WakeupLock is
     * unlocked.
     */
    @Test
    public void onResultsSearchesForViableNetworkWhenWakeupLockIsUnlocked() {
        // unlock wakeup lock
        when(mWakeupLock.isUnlocked()).thenReturn(true);
        // do not find viable network
        when(mWakeupEvaluator.findViableNetwork(any(), any())).thenReturn(null);

        initializeWakeupController(true /* enabled */);
        mWakeupController.start();

        ArgumentCaptor<WifiScanner.ScanListener> scanListenerArgumentCaptor =
                ArgumentCaptor.forClass(WifiScanner.ScanListener.class);

        verify(mWifiScanner).registerScanListener(any(), scanListenerArgumentCaptor.capture());
        WifiScanner.ScanListener scanListener = scanListenerArgumentCaptor.getValue();

        // incoming scan results
        scanListener.onResults(mTestScanDatas);

        verify(mWakeupEvaluator).findViableNetwork(any(), any());
        verifyDoesNotEnableWifi();
    }

    /**
     * Verify that the controller updates the WakeupLock even if the user is not onboarded.
     */
    @Test
    public void onResultsUpdatesIfNotOnboarded() {
        initializeWakeupController(true /* enabled */);
        when(mWakeupOnboarding.isOnboarded()).thenReturn(false);
        when(mWakeupLock.isUnlocked()).thenReturn(false);
        mWakeupController.start();

        ArgumentCaptor<WifiScanner.ScanListener> scanListenerArgumentCaptor =
                ArgumentCaptor.forClass(WifiScanner.ScanListener.class);

        verify(mWifiScanner).registerScanListener(any(), scanListenerArgumentCaptor.capture());
        WifiScanner.ScanListener scanListener = scanListenerArgumentCaptor.getValue();

        // incoming scan results
        scanListener.onResults(mTestScanDatas);

        verify(mWakeupLock).update(any());
        verify(mWakeupLock).isUnlocked();
        verifyDoesNotEnableWifi();
    }

    /**
     * Verify that the controller enables wifi and notifies user when all criteria are met.
     */
    @Test
    public void onResultsEnablesWifi() {
        // unlock wakeup lock
        when(mWakeupLock.isUnlocked()).thenReturn(true);
        // find viable network
        when(mWakeupEvaluator.findViableNetwork(any(), any())).thenReturn(mTestScanResult);

        initializeWakeupController(true /* enabled */);
        mWakeupController.start();

        ArgumentCaptor<WifiScanner.ScanListener> scanListenerArgumentCaptor =
                ArgumentCaptor.forClass(WifiScanner.ScanListener.class);

        verify(mWifiScanner).registerScanListener(any(), scanListenerArgumentCaptor.capture());
        WifiScanner.ScanListener scanListener = scanListenerArgumentCaptor.getValue();

        // incoming scan results
        scanListener.onResults(mTestScanDatas);

        verify(mWakeupEvaluator).findViableNetwork(any(), any());
        verify(mWifiSettingsStore).handleWifiToggled(true /* wifiEnabled */);
        verify(mWifiWakeMetrics).recordWakeupEvent(1 /* numScans */);
        verify(mWifiMetrics).reportWifiStateChanged(true, true, true);
        verify(mLastCallerInfoManager).put(eq(WifiManager.API_WIFI_ENABLED), anyInt(), anyInt(),
                anyInt(), eq("android_wifi_wake"), eq(true));
    }

    /**
     * Verify that the controller will not do any work if the user store has not been read.
     */
    @Test
    public void controllerDoesNoWorkIfUserStoreIsNotRead() {
        initializeWakeupController(true /* enabled */, false /* isRead */);
        mWakeupController.start();

        ArgumentCaptor<WifiScanner.ScanListener> scanListenerArgumentCaptor =
                ArgumentCaptor.forClass(WifiScanner.ScanListener.class);

        verify(mWifiScanner).registerScanListener(any(), scanListenerArgumentCaptor.capture());
        WifiScanner.ScanListener scanListener = scanListenerArgumentCaptor.getValue();

        // incoming scan results
        scanListener.onResults(mTestScanDatas);

        verify(mWakeupLock, never()).setLock(any());
        verify(mWakeupLock, never()).update(any());
        verify(mWakeupLock, never()).isUnlocked();
        verify(mWakeupOnboarding, never()).maybeShowNotification();
        verify(mWakeupEvaluator, never()).findViableNetwork(any(), any());
    }

    @Test
    public void userIsNotOnboardedByInitialization() {
        initializeWakeupController(true /* enabled */);
        verify(mWakeupOnboarding, never()).setOnboarded();
    }

    @Test
    public void userIsOnboardedBySettingChange() {
        initializeWakeupController(true /* enabled */);
        ArgumentCaptor<ContentObserver> argumentCaptor =
                ArgumentCaptor.forClass(ContentObserver.class);
        verify(mFrameworkFacade).registerContentObserver(any(), any(), eq(true),
                argumentCaptor.capture());
        ContentObserver contentObserver = argumentCaptor.getValue();
        contentObserver.onChange(false /* selfChange */);
        verify(mWakeupOnboarding).setOnboarded();
    }

    /**
     * When Wifi disconnects from a network, and within LAST_DISCONNECT_TIMEOUT_MILLIS Wifi is
     * disabled, then the last connected Wifi network should be added to the wakeup lock.
     *
     * This simulates when a Wifi network sporadically connects and disconnects. During the
     * disconnected phase, the user forcibly disables Wifi to stop this intermittent behavior. Then,
     * we should add the network to the wake lock to ensure Wifi Wake does not automatically
     * reconnect to this network.
     *
     * Also verifies that after the above flow, when Wifi is re-enabled, then disabled again, the
     * last connected network should be reset and no networks should be added to the wakeup lock.
     */
    @Test
    public void lastConnectedNetworkAddedToLock() {
        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L,
                (long) (0.8 * WakeupController.LAST_DISCONNECT_TIMEOUT_MILLIS));
        ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromScanResult(mTestScanResult);
        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                .thenReturn(Collections.emptyList());
        initializeWakeupController(true);

        mWakeupController.setLastDisconnectInfo(matchInfo);
        mWakeupController.start();

        verify(mWakeupLock).setLock(Set.of(matchInfo));

        mWakeupController.stop();
        mPrimaryChangedCaptor.getValue().onChange(null, mPrimaryClientModeManager);
        mWakeupController.start();

        verify(mWakeupLock).setLock(Set.of());
    }

    /**
     * When Wifi disconnects from a network, and Wifi is disabled after more than
     * LAST_DISCONNECT_TIMEOUT_MILLIS, the last connected Wifi network should not be added to the
     * wakeup lock.
     */
    @Test
    public void expiredLastConnectedNetworkNotAddedToLock() {
        when(mClock.getElapsedSinceBootMillis()).thenReturn(0L,
                (long) (1.2 * WakeupController.LAST_DISCONNECT_TIMEOUT_MILLIS));
        ScanResultMatchInfo matchInfo = ScanResultMatchInfo.fromScanResult(mTestScanResult);
        when(mWifiConfigManager.getMostRecentScanResultsForConfiguredNetworks(anyInt()))
                .thenReturn(Collections.emptyList());
        initializeWakeupController(true);

        mWakeupController.setLastDisconnectInfo(matchInfo);
        mWakeupController.start();

        verify(mWakeupLock).setLock(eq(Collections.emptySet()));
    }
}
