ScanRequestProxy: Handle scan requests & results.

Implementation of the scan request & results processing.

Bug: 70359905
Test: Unit tests.
Change-Id: I0b88334b61ce88fa1a3117d8755cceddd1a72bc3
diff --git a/service/java/com/android/server/wifi/ScanRequestProxy.java b/service/java/com/android/server/wifi/ScanRequestProxy.java
index 2c60925..5ed5988 100644
--- a/service/java/com/android/server/wifi/ScanRequestProxy.java
+++ b/service/java/com/android/server/wifi/ScanRequestProxy.java
@@ -23,9 +23,11 @@
 import android.net.wifi.WifiScanner;
 import android.os.Binder;
 import android.os.UserHandle;
+import android.os.WorkSource;
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import javax.annotation.concurrent.NotThreadSafe;
@@ -61,6 +63,53 @@
     private boolean mScanningForHiddenNetworksEnabled = false;
     // Scan results cached from the last full single scan request.
     private final List<ScanResult> mLastScanResults = new ArrayList<>();
+    // Common scan listener for scan requests.
+    private final WifiScanner.ScanListener mScanListener = new WifiScanner.ScanListener() {
+        @Override
+        public void onSuccess() {
+            // Scan request succeeded, wait for results to report to external clients.
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Scan request succeeded");
+            }
+        }
+
+        @Override
+        public void onFailure(int reason, String description) {
+            Log.e(TAG, "Scan failure received");
+            sendScanResultBroadcast(false);
+        }
+
+        @Override
+        public void onResults(WifiScanner.ScanData[] scanDatas) {
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Scan results received");
+            }
+            // For single scans, the array size should always be 1.
+            if (scanDatas.length != 1) {
+                Log.e(TAG, "Found more than 1 batch of scan results, Ignoring...");
+                sendScanResultBroadcast(false);
+            }
+            WifiScanner.ScanData scanData = scanDatas[0];
+            ScanResult[] scanResults = scanData.getResults();
+            if (mVerboseLoggingEnabled) {
+                Log.d(TAG, "Received " + scanResults.length + " scan results");
+            }
+            // Store the last scan results & send out the scan completion broadcast.
+            mLastScanResults.clear();
+            mLastScanResults.addAll(Arrays.asList(scanResults));
+            sendScanResultBroadcast(true);
+        }
+
+        @Override
+        public void onFullResult(ScanResult fullScanResult) {
+            // Ignore for single scans.
+        }
+
+        @Override
+        public void onPeriodChanged(int periodInMs) {
+            // Ignore for single scans.
+        }
+    };
 
     ScanRequestProxy(Context context, WifiInjector wifiInjector, WifiConfigManager configManager) {
         mContext = context;
@@ -123,15 +172,26 @@
     public boolean startScan(int callingUid) {
         if (!retrieveWifiScannerIfNecessary()) {
             Log.e(TAG, "Failed to retrieve wifiscanner");
+            sendScanResultBroadcast(false);
             return false;
         }
+        // Create a worksource using the caller's UID.
+        WorkSource workSource = new WorkSource(callingUid);
 
-        // Retrieve the list of hidden network SSIDs to scan for, if enabled.
+        // Create the scan settings.
+        WifiScanner.ScanSettings settings = new WifiScanner.ScanSettings();
+        // always do full scans
+        settings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS;
+        settings.reportEvents = WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
+                | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
         if (mScanningForHiddenNetworksEnabled) {
+            // retrieve the list of hidden network SSIDs to scan for, if enabled.
             List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworkList =
                     mWifiConfigManager.retrieveHiddenNetworkList();
+            settings.hiddenNetworks = hiddenNetworkList.toArray(
+                    new WifiScanner.ScanSettings.HiddenNetwork[hiddenNetworkList.size()]);
         }
-        // TODO: Implementation
+        mWifiScanner.startScan(settings, mScanListener, workSource);
         return true;
     }
 
@@ -143,4 +203,11 @@
     public List<ScanResult> getScanResults() {
         return mLastScanResults;
     }
+
+    /**
+     * Clear the stored scan results.
+     */
+    public void clearScanResults() {
+        mLastScanResults.clear();
+    }
 }
diff --git a/service/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java b/service/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java
index 5ab57cc..f1f5abb 100644
--- a/service/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java
+++ b/service/tests/wifitests/src/com/android/server/wifi/ScanRequestProxyTest.java
@@ -20,16 +20,24 @@
 import static org.mockito.Mockito.*;
 
 import android.content.Context;
+import android.content.Intent;
+import android.net.wifi.ScanResult;
+import android.net.wifi.WifiManager;
 import android.net.wifi.WifiScanner;
+import android.os.UserHandle;
+import android.os.WorkSource;
 import android.test.suitebuilder.annotation.SmallTest;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Unit tests for {@link com.android.server.wifi.ScanRequestProxy}.
@@ -37,11 +45,26 @@
 @SmallTest
 public class ScanRequestProxyTest {
     private static final int TEST_UID = 5;
+    private static final List<WifiScanner.ScanSettings.HiddenNetwork> TEST_HIDDEN_NETWORKS_LIST =
+            new ArrayList<WifiScanner.ScanSettings.HiddenNetwork>() {{
+                add(new WifiScanner.ScanSettings.HiddenNetwork("test_ssid_1"));
+                add(new WifiScanner.ScanSettings.HiddenNetwork("test_ssid_2"));
+
+            }};
 
     @Mock private Context mContext;
     @Mock private WifiInjector mWifiInjector;
     @Mock private WifiConfigManager mWifiConfigManager;
     @Mock private WifiScanner mWifiScanner;
+    private ArgumentCaptor<WorkSource> mWorkSourceArgumentCaptor =
+            ArgumentCaptor.forClass(WorkSource.class);
+    private ArgumentCaptor<WifiScanner.ScanSettings> mScanSettingsArgumentCaptor =
+            ArgumentCaptor.forClass(WifiScanner.ScanSettings.class);
+    private ArgumentCaptor<WifiScanner.ScanListener> mScanListenerArgumentCaptor =
+            ArgumentCaptor.forClass(WifiScanner.ScanListener.class);
+    private WifiScanner.ScanData[] mTestScanDatas1;
+    private WifiScanner.ScanData[] mTestScanDatas2;
+    private InOrder mInOrder;
 
     private ScanRequestProxy mScanRequestProxy;
 
@@ -50,7 +73,16 @@
         MockitoAnnotations.initMocks(this);
 
         when(mWifiInjector.getWifiScanner()).thenReturn(mWifiScanner);
-        when(mWifiConfigManager.retrieveHiddenNetworkList()).thenReturn(new ArrayList());
+        when(mWifiConfigManager.retrieveHiddenNetworkList()).thenReturn(TEST_HIDDEN_NETWORKS_LIST);
+        doNothing().when(mWifiScanner).startScan(
+                mScanSettingsArgumentCaptor.capture(),
+                mScanListenerArgumentCaptor.capture(),
+                mWorkSourceArgumentCaptor.capture());
+
+        mInOrder = inOrder(mWifiScanner, mWifiConfigManager, mContext);
+        mTestScanDatas1 = ScanTestUtil.createScanDatas(new int[][]{ { 2417, 2427, 5180, 5170 } });
+        mTestScanDatas2 = ScanTestUtil.createScanDatas(new int[][]{ { 2412, 2422, 5200, 5210 } });
+
         mScanRequestProxy = new ScanRequestProxy(mContext, mWifiInjector, mWifiConfigManager);
     }
 
@@ -66,14 +98,23 @@
     public void testStartScanFailWithoutScanner() {
         when(mWifiInjector.getWifiScanner()).thenReturn(null);
         assertFalse(mScanRequestProxy.startScan(TEST_UID));
+        validateScanResultsAvailableBroadcastSent(false);
+
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext);
     }
 
     /**
      * Verify scan request will forwarded to wifiscanner if wifiscanner is present.
      */
     @Test
-    public void testStartScanSuccessWithScanner() {
+    public void testStartScanSuccess() {
         assertTrue(mScanRequestProxy.startScan(TEST_UID));
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
+
+        assertTrue(mWorkSourceArgumentCaptor.getValue().equals(new WorkSource(TEST_UID)));
+        validateScanSettings(mScanSettingsArgumentCaptor.getValue(), false);
+
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext);
     }
 
     /**
@@ -83,7 +124,13 @@
     public void testStartScanWithHiddenNetworkScanningDisabled() {
         mScanRequestProxy.enableScanningForHiddenNetworks(false);
         assertTrue(mScanRequestProxy.startScan(TEST_UID));
-        verify(mWifiConfigManager, never()).retrieveHiddenNetworkList();
+        mInOrder.verify(mWifiConfigManager, never()).retrieveHiddenNetworkList();
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
+
+        assertTrue(mWorkSourceArgumentCaptor.getValue().equals(new WorkSource(TEST_UID)));
+        validateScanSettings(mScanSettingsArgumentCaptor.getValue(), false);
+
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext);
     }
 
     /**
@@ -93,6 +140,175 @@
     public void testStartScanWithHiddenNetworkScanningEnabled() {
         mScanRequestProxy.enableScanningForHiddenNetworks(true);
         assertTrue(mScanRequestProxy.startScan(TEST_UID));
-        verify(mWifiConfigManager).retrieveHiddenNetworkList();
+        mInOrder.verify(mWifiConfigManager).retrieveHiddenNetworkList();
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
+
+        assertTrue(mWorkSourceArgumentCaptor.getValue().equals(new WorkSource(TEST_UID)));
+        validateScanSettings(mScanSettingsArgumentCaptor.getValue(), true);
+
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext);
+    }
+
+    /**
+     * Verify a successful scan request and processing of scan results.
+     */
+    @Test
+    public void testScanSuccess() {
+        // Make a scan request.
+        testStartScanSuccess();
+
+        // Verify the scan results processing.
+        mScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
+        validateScanResultsAvailableBroadcastSent(true);
+
+        // Validate the scan results in the cache.
+        ScanTestUtil.assertScanResultsEquals(
+                mTestScanDatas1[0].getResults(),
+                mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext);
+    }
+
+    /**
+     * Verify a successful scan request and processing of scan failure.
+     */
+    @Test
+    public void testScanFailure() {
+        // Make a scan request.
+        testStartScanSuccess();
+
+        // Verify the scan failure processing.
+        mScanListenerArgumentCaptor.getValue().onFailure(0, "failed");
+        validateScanResultsAvailableBroadcastSent(false);
+
+        // Ensure scan results in the cache is empty.
+        assertTrue(mScanRequestProxy.getScanResults().isEmpty());
+
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext);
+    }
+
+    /**
+     * Verify processing of successive successful scans.
+     */
+    @Test
+    public void testScanSuccessOverwritesPreviousResults() {
+        // Make scan request 1.
+        assertTrue(mScanRequestProxy.startScan(TEST_UID));
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
+        // Verify the scan results processing for request 1.
+        mScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
+        validateScanResultsAvailableBroadcastSent(true);
+        // Validate the scan results in the cache.
+        ScanTestUtil.assertScanResultsEquals(
+                mTestScanDatas1[0].getResults(),
+                mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+
+        // Make scan request 2.
+        assertTrue(mScanRequestProxy.startScan(TEST_UID));
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
+        // Verify the scan results processing for request 2.
+        mScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas2);
+        validateScanResultsAvailableBroadcastSent(true);
+        // Validate the scan results in the cache.
+        ScanTestUtil.assertScanResultsEquals(
+                mTestScanDatas2[0].getResults(),
+                mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext);
+    }
+
+    /**
+     * Verify processing of a successful scan followed by a failure.
+     */
+    @Test
+    public void testScanFailureDoesNotOverwritePreviousResults() {
+        // Make scan request 1.
+        assertTrue(mScanRequestProxy.startScan(TEST_UID));
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
+        // Verify the scan results processing for request 1.
+        mScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
+        validateScanResultsAvailableBroadcastSent(true);
+        // Validate the scan results in the cache.
+        ScanTestUtil.assertScanResultsEquals(
+                mTestScanDatas1[0].getResults(),
+                mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+
+        // Make scan request 2.
+        assertTrue(mScanRequestProxy.startScan(TEST_UID));
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
+        // Verify the scan failure processing.
+        mScanListenerArgumentCaptor.getValue().onFailure(0, "failed");
+        validateScanResultsAvailableBroadcastSent(false);
+        // Validate the scan results from a previous successful scan in the cache.
+        ScanTestUtil.assertScanResultsEquals(
+                mTestScanDatas1[0].getResults(),
+                mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext);
+    }
+
+    /**
+     * Verify that clear scan results invocation clears all stored scan results.
+     */
+    @Test
+    public void testClearScanResults() {
+        // Make scan request 1.
+        assertTrue(mScanRequestProxy.startScan(TEST_UID));
+        mInOrder.verify(mWifiScanner).startScan(any(), any(), any());
+        // Verify the scan results processing for request 1.
+        mScanListenerArgumentCaptor.getValue().onResults(mTestScanDatas1);
+        validateScanResultsAvailableBroadcastSent(true);
+        // Validate the scan results in the cache.
+        ScanTestUtil.assertScanResultsEquals(
+                mTestScanDatas1[0].getResults(),
+                mScanRequestProxy.getScanResults().stream().toArray(ScanResult[]::new));
+
+        mScanRequestProxy.clearScanResults();
+        assertTrue(mScanRequestProxy.getScanResults().isEmpty());
+        verifyNoMoreInteractions(mWifiScanner, mWifiConfigManager, mContext);
+    }
+
+    private void validateScanSettings(WifiScanner.ScanSettings scanSettings,
+                                      boolean expectHiddenNetworks) {
+        assertNotNull(scanSettings);
+        assertEquals(WifiScanner.WIFI_BAND_BOTH_WITH_DFS, scanSettings.band);
+        assertEquals(WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN
+                | WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT, scanSettings.reportEvents);
+        if (expectHiddenNetworks) {
+            assertNotNull(scanSettings.hiddenNetworks);
+            assertEquals(TEST_HIDDEN_NETWORKS_LIST.size(), scanSettings.hiddenNetworks.length);
+            for (int i = 0; i < scanSettings.hiddenNetworks.length; i++) {
+                validateHiddenNetworkInList(scanSettings.hiddenNetworks[i],
+                        TEST_HIDDEN_NETWORKS_LIST);
+            }
+        } else {
+            assertNull(scanSettings.hiddenNetworks);
+        }
+    }
+
+    private void validateHiddenNetworkInList(
+            WifiScanner.ScanSettings.HiddenNetwork expectedHiddenNetwork,
+            List<WifiScanner.ScanSettings.HiddenNetwork> hiddenNetworkList) {
+        for (WifiScanner.ScanSettings.HiddenNetwork hiddenNetwork : hiddenNetworkList) {
+            if (hiddenNetwork.ssid.equals(expectedHiddenNetwork.ssid)) {
+                return;
+            }
+        }
+        fail();
+    }
+
+    private void validateScanResultsAvailableBroadcastSent(boolean expectScanSuceeded) {
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<UserHandle> userHandleCaptor = ArgumentCaptor.forClass(UserHandle.class);
+        mInOrder.verify(mContext).sendBroadcastAsUser(
+                intentCaptor.capture(), userHandleCaptor.capture());
+
+        assertEquals(userHandleCaptor.getValue(), UserHandle.ALL);
+
+        Intent intent = intentCaptor.getValue();
+        assertEquals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION, intent.getAction());
+        assertEquals(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT, intent.getFlags());
+        boolean scanSucceeded = intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false);
+        assertEquals(expectScanSuceeded, scanSucceeded);
     }
 }