From ace322e441f20008b7525ab729ac2feb0dc64ce0 Mon Sep 17 00:00:00 2001 From: David Su Date: Mon, 25 Nov 2019 16:03:05 -0800 Subject: [PATCH] Expose additional WifiScanner @SystemApis Exposed scan type, HiddenNetwork, and register/unregisterScanListener(). Bug: 143614759 Test: atest FrameworkWifiApiTests Change-Id: Ie926dc8d75266dad13e5d1589f8e14011a856a01 --- api/system-current.txt | 12 + api/system-lint-baseline.txt | 4 +- .../android/net/wifi/SynchronousExecutor.java | 29 +++ wifi/java/android/net/wifi/WifiScanner.java | 221 +++++++++++------- .../src/android/net/wifi/WifiManagerTest.java | 9 - .../src/android/net/wifi/WifiScannerTest.java | 166 ++++++++++++- 6 files changed, 340 insertions(+), 101 deletions(-) create mode 100644 wifi/java/android/net/wifi/SynchronousExecutor.java diff --git a/api/system-current.txt b/api/system-current.txt index adfda2fe527a..054fd26d221c 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5405,6 +5405,7 @@ package android.net.wifi { method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List getAvailableChannels(int); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public boolean getScanResults(); method @NonNull @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public java.util.List getSingleScanResults(); + method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void registerScanListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiScanner.ScanListener); method @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public void setScanningEnabled(boolean); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener); method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void startBackgroundScan(android.net.wifi.WifiScanner.ScanSettings, android.net.wifi.WifiScanner.ScanListener, android.os.WorkSource); @@ -5416,6 +5417,7 @@ package android.net.wifi { method @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE) public void stopScan(android.net.wifi.WifiScanner.ScanListener); method @Deprecated public void stopTrackingBssids(android.net.wifi.WifiScanner.BssidListener); method @Deprecated public void stopTrackingWifiChange(android.net.wifi.WifiScanner.WifiChangeListener); + method public void unregisterScanListener(@NonNull android.net.wifi.WifiScanner.ScanListener); field public static final int MAX_SCAN_PERIOD_MS = 1024000; // 0xfa000 field public static final int MIN_SCAN_PERIOD_MS = 1000; // 0x3e8 field public static final int REASON_DUPLICATE_REQEUST = -5; // 0xfffffffb @@ -5428,6 +5430,9 @@ package android.net.wifi { field public static final int REPORT_EVENT_AFTER_EACH_SCAN = 1; // 0x1 field public static final int REPORT_EVENT_FULL_SCAN_RESULT = 2; // 0x2 field public static final int REPORT_EVENT_NO_BATCH = 4; // 0x4 + field public static final int SCAN_TYPE_HIGH_ACCURACY = 2; // 0x2 + field public static final int SCAN_TYPE_LOW_LATENCY = 0; // 0x0 + field public static final int SCAN_TYPE_LOW_POWER = 1; // 0x1 field public static final int WIFI_BAND_24_GHZ = 1; // 0x1 field public static final int WIFI_BAND_5_GHZ = 2; // 0x2 field public static final int WIFI_BAND_5_GHZ_DFS_ONLY = 4; // 0x4 @@ -5496,6 +5501,7 @@ package android.net.wifi { ctor public WifiScanner.ScanSettings(); field public int band; field public android.net.wifi.WifiScanner.ChannelSpec[] channels; + field @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public final java.util.List hiddenNetworks; field public boolean hideFromAppOps; field public boolean ignoreLocationSettings; field public int maxPeriodInMs; @@ -5504,6 +5510,12 @@ package android.net.wifi { field public int periodInMs; field public int reportEvents; field public int stepCount; + field @RequiresPermission(android.Manifest.permission.NETWORK_STACK) public int type; + } + + public static class WifiScanner.ScanSettings.HiddenNetwork { + ctor public WifiScanner.ScanSettings.HiddenNetwork(@NonNull String); + field @NonNull public final String ssid; } @Deprecated public static interface WifiScanner.WifiChangeListener extends android.net.wifi.WifiScanner.ActionListener { diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt index ab0f0f987d54..fcf5178716e1 100644 --- a/api/system-lint-baseline.txt +++ b/api/system-lint-baseline.txt @@ -65,7 +65,7 @@ MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #1: MissingNullability: android.media.tv.TvRecordingClient.RecordingCallback#onEvent(String, String, android.os.Bundle) parameter #2: - + MissingNullability: android.net.wifi.rtt.RangingRequest.Builder#addResponder(android.net.wifi.rtt.ResponderConfig): MissingNullability: android.printservice.recommendation.RecommendationService#attachBaseContext(android.content.Context) parameter #0: @@ -181,6 +181,8 @@ MutableBareField: android.net.wifi.WifiConfiguration#saePasswordId: Bare field saePasswordId must be marked final, or moved behind accessors if mutable MutableBareField: android.net.wifi.WifiConfiguration#shared: Bare field shared must be marked final, or moved behind accessors if mutable +MutableBareField: android.net.wifi.WifiScanner.ScanSettings#type: + Bare field type must be marked final, or moved behind accessors if mutable NoClone: android.service.contentcapture.ContentCaptureService#dump(java.io.FileDescriptor, java.io.PrintWriter, String[]) parameter #0: diff --git a/wifi/java/android/net/wifi/SynchronousExecutor.java b/wifi/java/android/net/wifi/SynchronousExecutor.java new file mode 100644 index 000000000000..9926b1b5f7dc --- /dev/null +++ b/wifi/java/android/net/wifi/SynchronousExecutor.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2019 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; + +import java.util.concurrent.Executor; + +/** + * An executor implementation that runs synchronously on the current thread. + * @hide + */ +public class SynchronousExecutor implements Executor { + @Override + public void execute(Runnable r) { + r.run(); + } +} diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java index 0de506610ec1..77f7b9e23fc0 100644 --- a/wifi/java/android/net/wifi/WifiScanner.java +++ b/wifi/java/android/net/wifi/WifiScanner.java @@ -17,13 +17,16 @@ package android.net.wifi; import android.Manifest; +import android.annotation.CallbackExecutor; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.content.Context; +import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -45,6 +48,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.concurrent.Executor; /** * This class provides a way to scan the Wifi universe around the device @@ -196,24 +200,29 @@ public class WifiScanner { */ public static final int REPORT_EVENT_NO_BATCH = (1 << 2); + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"SCAN_TYPE_"}, value = { + SCAN_TYPE_LOW_LATENCY, + SCAN_TYPE_LOW_POWER, + SCAN_TYPE_HIGH_ACCURACY}) + public @interface ScanType {} + /** - * This is used to indicate the purpose of the scan to the wifi chip in - * {@link ScanSettings#type}. - * On devices with multiple hardware radio chains (and hence different modes of scan), - * this type serves as an indication to the hardware on what mode of scan to perform. - * Only apps holding android.Manifest.permission.NETWORK_STACK permission can set this value. - * - * Note: This serves as an intent and not as a stipulation, the wifi chip - * might honor or ignore the indication based on the current radio conditions. Always - * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration used - * to receive the corresponding scan result. + * Optimize the scan for lower latency. + * @see ScanSettings#type */ - /** {@hide} */ - public static final int TYPE_LOW_LATENCY = 0; - /** {@hide} */ - public static final int TYPE_LOW_POWER = 1; - /** {@hide} */ - public static final int TYPE_HIGH_ACCURACY = 2; + public static final int SCAN_TYPE_LOW_LATENCY = 0; + /** + * Optimize the scan for lower power usage. + * @see ScanSettings#type + */ + public static final int SCAN_TYPE_LOW_POWER = 1; + /** + * Optimize the scan for higher accuracy. + * @see ScanSettings#type + */ + public static final int SCAN_TYPE_HIGH_ACCURACY = 2; /** {@hide} */ public static final String SCAN_PARAMS_SCAN_SETTINGS_KEY = "ScanSettings"; @@ -228,18 +237,14 @@ public class WifiScanner { * scan configuration parameters to be sent to {@link #startBackgroundScan} */ public static class ScanSettings implements Parcelable { - /** - * Hidden network to be scanned for. - * {@hide} - */ + /** Hidden network to be scanned for. */ public static class HiddenNetwork { /** SSID of the network */ - public String ssid; + @NonNull + public final String ssid; - /** - * Default constructor for HiddenNetwork. - */ - public HiddenNetwork(String ssid) { + /** Default constructor for HiddenNetwork. */ + public HiddenNetwork(@NonNull String ssid) { this.ssid = ssid; } } @@ -249,12 +254,12 @@ public class WifiScanner { /** list of channels; used when band is set to WIFI_BAND_UNSPECIFIED */ public ChannelSpec[] channels; /** - * list of hidden networks to scan for. Explicit probe requests are sent out for such + * List of hidden networks to scan for. Explicit probe requests are sent out for such * networks during scan. Only valid for single scan requests. - * {@hide} */ + @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_STACK) - public HiddenNetwork[] hiddenNetworks; + public final List hiddenNetworks = new ArrayList<>(); /** period of background scan; in millisecond, 0 => single shot scan */ public int periodInMs; /** must have a valid REPORT_EVENT value */ @@ -285,11 +290,24 @@ public class WifiScanner { public boolean isPnoScan; /** * Indicate the type of scan to be performed by the wifi chip. - * Default value: {@link #TYPE_LOW_LATENCY}. - * {@hide} + * + * On devices with multiple hardware radio chains (and hence different modes of scan), + * this type serves as an indication to the hardware on what mode of scan to perform. + * Only apps holding {@link android.Manifest.permission.NETWORK_STACK} permission can set + * this value. + * + * Note: This serves as an intent and not as a stipulation, the wifi chip + * might honor or ignore the indication based on the current radio conditions. Always + * use the {@link ScanResult#radioChainInfos} to figure out the radio chain configuration + * used to receive the corresponding scan result. + * + * One of {@link #SCAN_TYPE_LOW_LATENCY}, {@link #SCAN_TYPE_LOW_POWER}, + * {@link #SCAN_TYPE_HIGH_ACCURACY}. + * Default value: {@link #SCAN_TYPE_LOW_LATENCY}. */ + @ScanType @RequiresPermission(android.Manifest.permission.NETWORK_STACK) - public int type = TYPE_LOW_LATENCY; + public int type = SCAN_TYPE_LOW_LATENCY; /** * This scan request may ignore location settings while receiving scans. This should only * be used in emergency situations. @@ -336,13 +354,9 @@ public class WifiScanner { } else { dest.writeInt(0); } - if (hiddenNetworks != null) { - dest.writeInt(hiddenNetworks.length); - for (int i = 0; i < hiddenNetworks.length; i++) { - dest.writeString(hiddenNetworks[i].ssid); - } - } else { - dest.writeInt(0); + dest.writeInt(hiddenNetworks.size()); + for (HiddenNetwork hiddenNetwork : hiddenNetworks) { + dest.writeString(hiddenNetwork.ssid); } } @@ -372,10 +386,10 @@ public class WifiScanner { settings.channels[i] = spec; } int numNetworks = in.readInt(); - settings.hiddenNetworks = new HiddenNetwork[numNetworks]; + settings.hiddenNetworks.clear(); for (int i = 0; i < numNetworks; i++) { String ssid = in.readString(); - settings.hiddenNetworks[i] = new HiddenNetwork(ssid);; + settings.hiddenNetworks.add(new HiddenNetwork(ssid)); } return settings; } @@ -801,33 +815,44 @@ public class WifiScanner { } /** - * Register a listener that will receive results from all single scans - * Either the onSuccess/onFailure will be called once when the listener is registered. After - * (assuming onSuccess was called) all subsequent single scan results will be delivered to the - * listener. It is possible that onFullResult will not be called for all results of the first - * scan if the listener was registered during the scan. + * Register a listener that will receive results from all single scans. + * Either the {@link ScanListener#onSuccess()} or {@link ScanListener#onFailure(int, String)} + * method will be called once when the listener is registered. + * Afterwards (assuming onSuccess was called), all subsequent single scan results will be + * delivered to the listener. It is possible that onFullResult will not be called for all + * results of the first scan if the listener was registered during the scan. * * @param listener specifies the object to report events to. This object is also treated as a * key for this request, and must also be specified to cancel the request. * Multiple requests should also not share this object. - * {@hide} */ @RequiresPermission(Manifest.permission.NETWORK_STACK) - public void registerScanListener(ScanListener listener) { + public void registerScanListener(@NonNull @CallbackExecutor Executor executor, + @NonNull ScanListener listener) { + Preconditions.checkNotNull(executor, "executor cannot be null"); Preconditions.checkNotNull(listener, "listener cannot be null"); - int key = addListener(listener); + int key = addListener(listener, executor); if (key == INVALID_KEY) return; validateChannel(); mAsyncChannel.sendMessage(CMD_REGISTER_SCAN_LISTENER, 0, key); } + /** + * Overload of {@link #registerScanListener(Executor, ScanListener)} that executes the callback + * synchronously. + * @hide + */ + @RequiresPermission(Manifest.permission.NETWORK_STACK) + public void registerScanListener(@NonNull ScanListener listener) { + registerScanListener(new SynchronousExecutor(), listener); + } + /** * Deregister a listener for ongoing single scans * @param listener specifies which scan to cancel; must be same object as passed in {@link * #registerScanListener} - * {@hide} */ - public void deregisterScanListener(ScanListener listener) { + public void unregisterScanListener(@NonNull ScanListener listener) { Preconditions.checkNotNull(listener, "listener cannot be null"); int key = removeListener(listener); if (key == INVALID_KEY) return; @@ -1280,6 +1305,7 @@ public class WifiScanner { private int mListenerKey = 1; private final SparseArray mListenerMap = new SparseArray(); + private final SparseArray mExecutorMap = new SparseArray<>(); private final Object mListenerMapLock = new Object(); private AsyncChannel mAsyncChannel; @@ -1327,10 +1353,14 @@ public class WifiScanner { "No permission to access and change wifi or a bad initialization"); } + private int addListener(ActionListener listener) { + return addListener(listener, null); + } + // Add a listener into listener map. If the listener already exists, return INVALID_KEY and // send an error message to internal handler; Otherwise add the listener to the listener map and // return the key of the listener. - private int addListener(ActionListener listener) { + private int addListener(ActionListener listener, Executor executor) { synchronized (mListenerMapLock) { boolean keyExists = (getListenerKey(listener) != INVALID_KEY); // Note we need to put the listener into listener map even if it's a duplicate as the @@ -1346,6 +1376,7 @@ public class WifiScanner { message.sendToTarget(); return INVALID_KEY; } else { + mExecutorMap.put(key, executor); return key; } } @@ -1363,11 +1394,22 @@ public class WifiScanner { return key; } - private Object getListener(int key) { - if (key == INVALID_KEY) return null; + private static class ListenerWithExecutor { + @Nullable final Object mListener; + @Nullable final Executor mExecutor; + + ListenerWithExecutor(@Nullable Object listener, @Nullable Executor executor) { + mListener = listener; + mExecutor = executor; + } + } + + private ListenerWithExecutor getListenerWithExecutor(int key) { + if (key == INVALID_KEY) return new ListenerWithExecutor(null, null); synchronized (mListenerMapLock) { Object listener = mListenerMap.get(key); - return listener; + Executor executor = mExecutorMap.get(key); + return new ListenerWithExecutor(listener, executor); } } @@ -1388,6 +1430,7 @@ public class WifiScanner { synchronized (mListenerMapLock) { Object listener = mListenerMap.get(key); mListenerMap.remove(key); + mExecutorMap.remove(key); return listener; } } @@ -1400,6 +1443,7 @@ public class WifiScanner { } synchronized (mListenerMapLock) { mListenerMap.remove(key); + mExecutorMap.remove(key); return key; } } @@ -1458,7 +1502,8 @@ public class WifiScanner { return; } - Object listener = getListener(msg.arg2); + ListenerWithExecutor listenerWithExecutor = getListenerWithExecutor(msg.arg2); + Object listener = listenerWithExecutor.mListener; if (listener == null) { if (DBG) Log.d(TAG, "invalid listener key = " + msg.arg2); @@ -1467,36 +1512,52 @@ public class WifiScanner { if (DBG) Log.d(TAG, "listener key = " + msg.arg2); } + Executor executor = listenerWithExecutor.mExecutor; + if (executor == null) { + executor = new SynchronousExecutor(); + } + switch (msg.what) { - /* ActionListeners grouped together */ - case CMD_OP_SUCCEEDED : - ((ActionListener) listener).onSuccess(); - break; - case CMD_OP_FAILED : { - OperationResult result = (OperationResult)msg.obj; - ((ActionListener) listener).onFailure(result.reason, result.description); - removeListener(msg.arg2); - } - break; - case CMD_SCAN_RESULT : - ((ScanListener) listener).onResults( - ((ParcelableScanData) msg.obj).getResults()); - return; - case CMD_FULL_SCAN_RESULT : + /* ActionListeners grouped together */ + case CMD_OP_SUCCEEDED: { + ActionListener actionListener = (ActionListener) listener; + Binder.clearCallingIdentity(); + executor.execute(actionListener::onSuccess); + } break; + case CMD_OP_FAILED: { + OperationResult result = (OperationResult) msg.obj; + ActionListener actionListener = (ActionListener) listener; + removeListener(msg.arg2); + Binder.clearCallingIdentity(); + executor.execute(() -> + actionListener.onFailure(result.reason, result.description)); + } break; + case CMD_SCAN_RESULT: { + ScanListener scanListener = (ScanListener) listener; + ParcelableScanData parcelableScanData = (ParcelableScanData) msg.obj; + Binder.clearCallingIdentity(); + executor.execute(() -> scanListener.onResults(parcelableScanData.getResults())); + } break; + case CMD_FULL_SCAN_RESULT: { ScanResult result = (ScanResult) msg.obj; - ((ScanListener) listener).onFullResult(result); - return; - case CMD_SINGLE_SCAN_COMPLETED: + ScanListener scanListener = ((ScanListener) listener); + Binder.clearCallingIdentity(); + executor.execute(() -> scanListener.onFullResult(result)); + } break; + case CMD_SINGLE_SCAN_COMPLETED: { if (DBG) Log.d(TAG, "removing listener for single scan"); removeListener(msg.arg2); - break; - case CMD_PNO_NETWORK_FOUND: - ((PnoScanListener) listener).onPnoNetworkFound( - ((ParcelableScanResults) msg.obj).getResults()); - return; - default: + } break; + case CMD_PNO_NETWORK_FOUND: { + PnoScanListener pnoScanListener = (PnoScanListener) listener; + ParcelableScanResults parcelableScanResults = (ParcelableScanResults) msg.obj; + Binder.clearCallingIdentity(); + executor.execute(() -> + pnoScanListener.onPnoNetworkFound(parcelableScanResults.getResults())); + } break; + default: { if (DBG) Log.d(TAG, "Ignoring message " + msg.what); - return; + } break; } } } diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java index 8cdcba67386b..d326201fa574 100644 --- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java @@ -1436,15 +1436,6 @@ public class WifiManagerTest { eq((int) listenerIdentifier.getValue())); } - /** - * Defined for testing purpose. - */ - class SynchronousExecutor implements Executor { - public void execute(Runnable r) { - r.run(); - } - } - /** * Test behavior of isEnhancedOpenSupported */ diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java index f4fa38beaa40..b1436c90f5dd 100644 --- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java +++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java @@ -22,7 +22,9 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.validateMockitoUsage; @@ -33,6 +35,7 @@ import android.content.Context; import android.net.wifi.WifiScanner.PnoSettings; import android.net.wifi.WifiScanner.PnoSettings.PnoNetwork; import android.net.wifi.WifiScanner.ScanData; +import android.net.wifi.WifiScanner.ScanListener; import android.net.wifi.WifiScanner.ScanSettings; import android.os.Bundle; import android.os.Handler; @@ -51,8 +54,10 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import java.util.Arrays; +import java.util.concurrent.Executor; /** * Unit tests for {@link android.net.wifi.WifiScanner}. @@ -63,6 +68,13 @@ public class WifiScannerTest { private Context mContext; @Mock private IWifiScanner mService; + @Spy + private Executor mExecutor = new SynchronousExecutor(); + @Mock + private ScanListener mScanListener; + @Mock + private WifiScanner.ParcelableScanData mParcelableScanData; + private ScanData[] mScanData = {}; private static final boolean TEST_PNOSETTINGS_IS_CONNECTED = false; private static final int TEST_PNOSETTINGS_MIN_5GHZ_RSSI = -60; @@ -76,6 +88,7 @@ public class WifiScannerTest { private static final String TEST_SSID_2 = "TEST2"; private static final int[] TEST_FREQUENCIES_1 = {}; private static final int[] TEST_FREQUENCIES_2 = {2500, 5124}; + private static final String DESCRIPTION_NOT_AUTHORIZED = "Not authorized"; private WifiScanner mWifiScanner; private TestLooper mLooper; @@ -95,6 +108,7 @@ public class WifiScannerTest { when(mService.getMessenger()).thenReturn(mBidirectionalAsyncChannelServer.getMessenger()); mWifiScanner = new WifiScanner(mContext, mService, mLooper.getLooper()); mLooper.dispatchAll(); + when(mParcelableScanData.getResults()).thenReturn(mScanData); } /** @@ -111,7 +125,7 @@ public class WifiScannerTest { @Test public void verifyScanSettingsParcelWithBand() throws Exception { ScanSettings writeSettings = new ScanSettings(); - writeSettings.type = WifiScanner.TYPE_LOW_POWER; + writeSettings.type = WifiScanner.SCAN_TYPE_LOW_POWER; writeSettings.band = WifiScanner.WIFI_BAND_BOTH_WITH_DFS; ScanSettings readSettings = parcelWriteRead(writeSettings); @@ -126,7 +140,7 @@ public class WifiScannerTest { @Test public void verifyScanSettingsParcelWithChannels() throws Exception { ScanSettings writeSettings = new ScanSettings(); - writeSettings.type = WifiScanner.TYPE_HIGH_ACCURACY; + writeSettings.type = WifiScanner.SCAN_TYPE_HIGH_ACCURACY; writeSettings.band = WifiScanner.WIFI_BAND_UNSPECIFIED; writeSettings.channels = new WifiScanner.ChannelSpec[] { new WifiScanner.ChannelSpec(5), @@ -243,13 +257,13 @@ public class WifiScannerTest { /** - * Test behavior of {@link WifiScanner#startScan(ScanSettings, WifiScanner.ScanListener)} + * Test behavior of {@link WifiScanner#startScan(ScanSettings, ScanListener)} * @throws Exception */ @Test public void testStartScan() throws Exception { ScanSettings scanSettings = new ScanSettings(); - WifiScanner.ScanListener scanListener = mock(WifiScanner.ScanListener.class); + ScanListener scanListener = mock(ScanListener.class); mWifiScanner.startScan(scanSettings, scanListener); mLooper.dispatchAll(); @@ -273,13 +287,13 @@ public class WifiScannerTest { } /** - * Test behavior of {@link WifiScanner#stopScan(WifiScanner.ScanListener)} + * Test behavior of {@link WifiScanner#stopScan(ScanListener)} * @throws Exception */ @Test public void testStopScan() throws Exception { ScanSettings scanSettings = new ScanSettings(); - WifiScanner.ScanListener scanListener = mock(WifiScanner.ScanListener.class); + ScanListener scanListener = mock(ScanListener.class); mWifiScanner.startScan(scanSettings, scanListener); mLooper.dispatchAll(); @@ -302,13 +316,13 @@ public class WifiScannerTest { } /** - * Test behavior of {@link WifiScanner#startScan(ScanSettings, WifiScanner.ScanListener)} + * Test behavior of {@link WifiScanner#startScan(ScanSettings, ScanListener)} * @throws Exception */ @Test public void testStartScanListenerOnSuccess() throws Exception { ScanSettings scanSettings = new ScanSettings(); - WifiScanner.ScanListener scanListener = mock(WifiScanner.ScanListener.class); + ScanListener scanListener = mock(ScanListener.class); mWifiScanner.startScan(scanSettings, scanListener); mLooper.dispatchAll(); @@ -332,13 +346,13 @@ public class WifiScannerTest { } /** - * Test behavior of {@link WifiScanner#startScan(ScanSettings, WifiScanner.ScanListener)} + * Test behavior of {@link WifiScanner#startScan(ScanSettings, ScanListener)} * @throws Exception */ @Test public void testStartScanListenerOnResults() throws Exception { ScanSettings scanSettings = new ScanSettings(); - WifiScanner.ScanListener scanListener = mock(WifiScanner.ScanListener.class); + ScanListener scanListener = mock(ScanListener.class); mWifiScanner.startScan(scanSettings, scanListener); mLooper.dispatchAll(); @@ -425,7 +439,7 @@ public class WifiScannerTest { } /** - * Test behavior of {@link WifiScanner#stopPnoScan(WifiScanner.ScanListener)} + * Test behavior of {@link WifiScanner#stopPnoScan(ScanListener)} * WifiScanner.PnoScanListener)} * @throws Exception */ @@ -480,4 +494,134 @@ public class WifiScannerTest { assertEquals(scanData.getResults().length, readScanData.getResults().length); assertEquals(scanData.getResults()[0].SSID, readScanData.getResults()[0].SSID); } + + /** Tests that upon registration success, {@link ScanListener#onSuccess()} is called. */ + @Test + public void testRegisterScanListenerSuccess() throws Exception { + mWifiScanner.registerScanListener(mExecutor, mScanListener); + mLooper.dispatchAll(); + + ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); + verify(mHandler).handleMessage(messageArgumentCaptor.capture()); + Message sentMessage = messageArgumentCaptor.getValue(); + assertNotNull(sentMessage); + + assertEquals(1, mBidirectionalAsyncChannelServer.getClientMessengers().size()); + Messenger scannerMessenger = + mBidirectionalAsyncChannelServer.getClientMessengers().iterator().next(); + + Message responseMessage = Message.obtain(); + responseMessage.what = WifiScanner.CMD_OP_SUCCEEDED; + responseMessage.arg2 = sentMessage.arg2; + scannerMessenger.send(responseMessage); + mLooper.dispatchAll(); + + verify(mExecutor).execute(any()); + verify(mScanListener).onSuccess(); + } + + /** + * Tests that upon registration failed, {@link ScanListener#onFailure(int, String)} is called. + */ + @Test + public void testRegisterScanListenerFailed() throws Exception { + mWifiScanner.registerScanListener(mExecutor, mScanListener); + mLooper.dispatchAll(); + + ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); + verify(mHandler).handleMessage(messageArgumentCaptor.capture()); + Message sentMessage = messageArgumentCaptor.getValue(); + assertNotNull(sentMessage); + + assertEquals(1, mBidirectionalAsyncChannelServer.getClientMessengers().size()); + Messenger scannerMessenger = + mBidirectionalAsyncChannelServer.getClientMessengers().iterator().next(); + + { + Message responseMessage = Message.obtain(); + responseMessage.what = WifiScanner.CMD_OP_FAILED; + responseMessage.arg2 = sentMessage.arg2; + responseMessage.obj = new WifiScanner.OperationResult( + WifiScanner.REASON_NOT_AUTHORIZED, DESCRIPTION_NOT_AUTHORIZED); + scannerMessenger.send(responseMessage); + mLooper.dispatchAll(); + } + + verify(mExecutor).execute(any()); + verify(mScanListener).onFailure( + WifiScanner.REASON_NOT_AUTHORIZED, DESCRIPTION_NOT_AUTHORIZED); + + // CMD_OP_FAILED should have caused the removal of the listener, verify this + { + Message responseMessage = Message.obtain(); + responseMessage.what = WifiScanner.CMD_SCAN_RESULT; + responseMessage.arg2 = sentMessage.arg2; + responseMessage.obj = mParcelableScanData; + scannerMessenger.send(responseMessage); + mLooper.dispatchAll(); + } + // execute() called once before, not called again + verify(mExecutor, times(1)).execute(any()); + // onResults() never triggered + verify(mScanListener, never()).onResults(any()); + } + + /** + * Tests that when the ScanListener is triggered, {@link ScanListener#onResults(ScanData[])} + * is called. + */ + @Test + public void testRegisterScanListenerReceiveScanResults() throws Exception { + mWifiScanner.registerScanListener(mExecutor, mScanListener); + mLooper.dispatchAll(); + + ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); + verify(mHandler).handleMessage(messageArgumentCaptor.capture()); + Message sentMessage = messageArgumentCaptor.getValue(); + assertNotNull(sentMessage); + + assertEquals(1, mBidirectionalAsyncChannelServer.getClientMessengers().size()); + Messenger scannerMessenger = + mBidirectionalAsyncChannelServer.getClientMessengers().iterator().next(); + + Message responseMessage = Message.obtain(); + responseMessage.what = WifiScanner.CMD_SCAN_RESULT; + responseMessage.arg2 = sentMessage.arg2; + responseMessage.obj = mParcelableScanData; + scannerMessenger.send(responseMessage); + mLooper.dispatchAll(); + + verify(mExecutor).execute(any()); + verify(mScanListener).onResults(mScanData); + } + + /** + * Tests that after unregistering a scan listener, {@link ScanListener#onResults(ScanData[])} + * is not called. + */ + @Test + public void testUnregisterScanListener() throws Exception { + mWifiScanner.registerScanListener(mExecutor, mScanListener); + mWifiScanner.unregisterScanListener(mScanListener); + mLooper.dispatchAll(); + + assertEquals(1, mBidirectionalAsyncChannelServer.getClientMessengers().size()); + Messenger scannerMessenger = + mBidirectionalAsyncChannelServer.getClientMessengers().iterator().next(); + + ArgumentCaptor messageArgumentCaptor = ArgumentCaptor.forClass(Message.class); + verify(mHandler, times(2)).handleMessage(messageArgumentCaptor.capture()); + Message sentMessage = messageArgumentCaptor.getValue(); + assertNotNull(sentMessage); + + Message responseMessage = Message.obtain(); + responseMessage.what = WifiScanner.CMD_SCAN_RESULT; + responseMessage.obj = mParcelableScanData; + responseMessage.arg2 = sentMessage.arg2; + scannerMessenger.send(responseMessage); + mLooper.dispatchAll(); + + verify(mExecutor, never()).execute(any()); + verify(mScanListener, never()).onResults(mScanData); + } }