Split updatable/non-updatable Wifi APIs into separate directories
This creates a clearer boundary between what is updatable
(part of the Wifi module) and what is not updatable (outside
the module).
Bug: 176105484
Test: compiles
Merged-In: Ia95e300da9286d79ee57c285c3bfa88ecc98e326
Change-Id: I1747f52f18210bb05bcc414461bf95aa461577a6
diff --git a/wifi/non-updatable/tests/Android.bp b/wifi/non-updatable/tests/Android.bp
new file mode 100644
index 0000000..3f5cacf
--- /dev/null
+++ b/wifi/non-updatable/tests/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2020 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.
+
+android_test {
+ name: "FrameworksWifiNonUpdatableApiTests",
+
+ defaults: ["framework-wifi-test-defaults"],
+
+ srcs: ["src/**/*.java"],
+
+ jacoco: {
+ include_filter: ["android.net.wifi.*"],
+ // TODO(b/147521214) need to exclude test classes
+ exclude_filter: [],
+ },
+
+ static_libs: [
+ "androidx.test.rules",
+ "frameworks-base-testutils",
+ "guava",
+ "mockito-target-minus-junit4",
+ "truth-prebuilt",
+ ],
+
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+
+ test_suites: [
+ "general-tests",
+ ],
+}
diff --git a/wifi/non-updatable/tests/AndroidManifest.xml b/wifi/non-updatable/tests/AndroidManifest.xml
new file mode 100644
index 0000000..b4b6b2d
--- /dev/null
+++ b/wifi/non-updatable/tests/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.net.wifi.test">
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ <activity android:label="WifiTestDummyLabel"
+ android:name="WifiTestDummyName"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.net.wifi.test"
+ android:label="Frameworks Wifi Non-updatable API Tests">
+ </instrumentation>
+
+</manifest>
diff --git a/wifi/non-updatable/tests/AndroidTest.xml b/wifi/non-updatable/tests/AndroidTest.xml
new file mode 100644
index 0000000..5f3fdd4
--- /dev/null
+++ b/wifi/non-updatable/tests/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<configuration description="Runs Frameworks Wifi Non-updatable API Tests.">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="FrameworksWifiNonUpdatableApiTests.apk" />
+ </target_preparer>
+
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-tag" value="FrameworksWifiNonUpdatableApiTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="android.net.wifi.test" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ <option name="hidden-api-checks" value="false"/>
+ </test>
+</configuration>
diff --git a/wifi/non-updatable/tests/README.md b/wifi/non-updatable/tests/README.md
new file mode 100644
index 0000000..ad535f4
--- /dev/null
+++ b/wifi/non-updatable/tests/README.md
@@ -0,0 +1,32 @@
+# Wifi Non-Updatable Framework Unit Tests
+This package contains unit tests for the non-updatable part (i.e. outside the Wifi module) of the
+Android Wifi framework APIs based on the
+[Android Testing Support Library](http://developer.android.com/tools/testing-support-library/index.html).
+The test cases are built using the [JUnit](http://junit.org/) and [Mockito](http://mockito.org/)
+libraries.
+
+## Running Tests
+The easiest way to run tests is simply run
+
+```
+atest android.net.wifi
+```
+
+To pick up changes in framework/base, you will need to:
+1. rebuild the framework library 'make -j32'
+2. sync over the updated library to the device 'adb sync'
+3. restart framework on the device 'adb shell stop' then 'adb shell start'
+
+To enable syncing data to the device for first time after clean reflash:
+1. adb disable-verity
+2. adb reboot
+3. adb remount
+
+## Adding Tests
+Tests can be added by adding classes to the src directory. JUnit4 style test cases can
+be written by simply annotating test methods with `org.junit.Test`.
+
+## Debugging Tests
+If you are trying to debug why tests are not doing what you expected, you can add android log
+statements and use logcat to view them. The beginning and end of every tests is automatically logged
+with the tag `TestRunner`.
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java b/wifi/non-updatable/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java
new file mode 100644
index 0000000..f49f387
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java
@@ -0,0 +1,199 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Unit tests for {@link android.net.wifi.SoftApConfToXmlMigrationUtilTest}.
+ */
+@SmallTest
+public class SoftApConfToXmlMigrationUtilTest {
+ private static final String TEST_SSID = "SSID";
+ private static final String TEST_PASSPHRASE = "TestPassphrase";
+ private static final int TEST_CHANNEL = 0;
+ private static final boolean TEST_HIDDEN = false;
+ private static final int TEST_BAND = SoftApConfiguration.BAND_5GHZ;
+ private static final int TEST_SECURITY = SoftApConfiguration.SECURITY_TYPE_WPA2_PSK;
+
+ private static final String TEST_EXPECTED_XML_STRING =
+ "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<WifiConfigStoreData>\n"
+ + "<int name=\"Version\" value=\"3\" />\n"
+ + "<SoftAp>\n"
+ + "<string name=\"SSID\">" + TEST_SSID + "</string>\n"
+ + "<int name=\"ApBand\" value=\"" + TEST_BAND + "\" />\n"
+ + "<int name=\"Channel\" value=\"" + TEST_CHANNEL + "\" />\n"
+ + "<boolean name=\"HiddenSSID\" value=\"" + TEST_HIDDEN + "\" />\n"
+ + "<int name=\"SecurityType\" value=\"" + TEST_SECURITY + "\" />\n"
+ + "<string name=\"Passphrase\">" + TEST_PASSPHRASE + "</string>\n"
+ + "<int name=\"MaxNumberOfClients\" value=\"0\" />\n"
+ + "<boolean name=\"ClientControlByUser\" value=\"false\" />\n"
+ + "<boolean name=\"AutoShutdownEnabled\" value=\"true\" />\n"
+ + "<long name=\"ShutdownTimeoutMillis\" value=\"0\" />\n"
+ + "<BlockedClientList />\n"
+ + "<AllowedClientList />\n"
+ + "</SoftAp>\n"
+ + "</WifiConfigStoreData>\n";
+
+ private byte[] createLegacyApConfFile(WifiConfiguration config) throws Exception {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ DataOutputStream out = new DataOutputStream(outputStream);
+ out.writeInt(3);
+ out.writeUTF(config.SSID);
+ out.writeInt(config.apBand);
+ out.writeInt(config.apChannel);
+ out.writeBoolean(config.hiddenSSID);
+ int authType = config.getAuthType();
+ out.writeInt(authType);
+ if (authType != WifiConfiguration.KeyMgmt.NONE) {
+ out.writeUTF(config.preSharedKey);
+ }
+ out.close();
+ return outputStream.toByteArray();
+ }
+
+ /**
+ * Generate a SoftApConfiguration based on the specified parameters.
+ */
+ private SoftApConfiguration setupApConfig(
+ String ssid, String preSharedKey, int keyManagement, int band, int channel,
+ boolean hiddenSSID) {
+ SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+ configBuilder.setSsid(ssid);
+ configBuilder.setPassphrase(preSharedKey, SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
+ if (channel == 0) {
+ configBuilder.setBand(band);
+ } else {
+ configBuilder.setChannel(channel, band);
+ }
+ configBuilder.setHiddenSsid(hiddenSSID);
+ return configBuilder.build();
+ }
+
+ /**
+ * Generate a WifiConfiguration based on the specified parameters.
+ */
+ private WifiConfiguration setupWifiConfigurationApConfig(
+ String ssid, String preSharedKey, int keyManagement, int band, int channel,
+ boolean hiddenSSID) {
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = ssid;
+ config.preSharedKey = preSharedKey;
+ config.allowedKeyManagement.set(keyManagement);
+ config.apBand = band;
+ config.apChannel = channel;
+ config.hiddenSSID = hiddenSSID;
+ return config;
+ }
+
+ /**
+ * Asserts that the WifiConfigurations equal to SoftApConfiguration.
+ * This only compares the elements saved
+ * for softAp used.
+ */
+ public static void assertWifiConfigurationEqualSoftApConfiguration(
+ WifiConfiguration backup, SoftApConfiguration restore) {
+ assertEquals(backup.SSID, restore.getSsid());
+ assertEquals(backup.BSSID, restore.getBssid());
+ assertEquals(SoftApConfToXmlMigrationUtil.convertWifiConfigBandToSoftApConfigBand(
+ backup.apBand),
+ restore.getBand());
+ assertEquals(backup.apChannel, restore.getChannel());
+ assertEquals(backup.preSharedKey, restore.getPassphrase());
+ if (backup.getAuthType() == WifiConfiguration.KeyMgmt.WPA2_PSK) {
+ assertEquals(SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, restore.getSecurityType());
+ } else {
+ assertEquals(SoftApConfiguration.SECURITY_TYPE_OPEN, restore.getSecurityType());
+ }
+ assertEquals(backup.hiddenSSID, restore.isHiddenSsid());
+ }
+
+ /**
+ * Note: This is a copy of {@link AtomicFile#readFully()} modified to use the passed in
+ * {@link InputStream} which was returned using {@link AtomicFile#openRead()}.
+ */
+ private static byte[] readFully(InputStream stream) throws IOException {
+ try {
+ int pos = 0;
+ int avail = stream.available();
+ byte[] data = new byte[avail];
+ while (true) {
+ int amt = stream.read(data, pos, data.length - pos);
+ if (amt <= 0) {
+ return data;
+ }
+ pos += amt;
+ avail = stream.available();
+ if (avail > data.length - pos) {
+ byte[] newData = new byte[pos + avail];
+ System.arraycopy(data, 0, newData, 0, pos);
+ data = newData;
+ }
+ }
+ } finally {
+ stream.close();
+ }
+ }
+
+ /**
+ * Tests conversion from legacy .conf file to XML file format.
+ */
+ @Test
+ public void testConversion() throws Exception {
+ WifiConfiguration backupConfig = setupWifiConfigurationApConfig(
+ TEST_SSID, /* SSID */
+ TEST_PASSPHRASE, /* preshared key */
+ WifiConfiguration.KeyMgmt.WPA2_PSK, /* key management */
+ 1, /* AP band (5GHz) */
+ TEST_CHANNEL, /* AP channel */
+ TEST_HIDDEN /* Hidden SSID */);
+ SoftApConfiguration expectedConfig = setupApConfig(
+ TEST_SSID, /* SSID */
+ TEST_PASSPHRASE, /* preshared key */
+ SoftApConfiguration.SECURITY_TYPE_WPA2_PSK, /* security type */
+ SoftApConfiguration.BAND_5GHZ, /* AP band (5GHz) */
+ TEST_CHANNEL, /* AP channel */
+ TEST_HIDDEN /* Hidden SSID */);
+
+ assertWifiConfigurationEqualSoftApConfiguration(backupConfig, expectedConfig);
+
+ byte[] confBytes = createLegacyApConfFile(backupConfig);
+ assertNotNull(confBytes);
+
+ InputStream xmlStream = SoftApConfToXmlMigrationUtil.convert(
+ new ByteArrayInputStream(confBytes));
+
+ byte[] xmlBytes = readFully(xmlStream);
+ assertNotNull(xmlBytes);
+
+ assertEquals(TEST_EXPECTED_XML_STRING, new String(xmlBytes));
+ }
+
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java b/wifi/non-updatable/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
new file mode 100644
index 0000000..c4967eb
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 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 static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.NetworkKey;
+import android.net.RssiCurve;
+import android.net.ScoredNetwork;
+import android.net.WifiKey;
+import android.net.wifi.WifiNetworkScoreCache.CacheListener;
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/** Unit tests for {@link WifiNetworkScoreCache}. */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class WifiNetworkScoreCacheTest {
+
+ public static final String SSID = "ssid";
+ public static final String SSID2 = "ssid2";
+ public static final String SSID3 = "ssid3";
+ public static final String FORMATTED_SSID = "\"" + SSID + "\"";
+ public static final String FORMATTED_SSID2 = "\"" + SSID2 + "\"";
+ public static final String FORMATTED_SSID3 = "\"" + SSID3 + "\"";
+ public static final String BSSID = "AA:AA:AA:AA:AA:AA";
+
+ public static final WifiKey VALID_KEY = new WifiKey(FORMATTED_SSID, BSSID);
+
+ public static final ScanResult VALID_SCAN_RESULT = buildScanResult(SSID, BSSID);
+
+ @Mock private Context mockApplicationContext;
+ @Mock private Context mockContext; // isn't used, can be null
+ @Mock private RssiCurve mockRssiCurve;
+
+
+ private CacheListener mCacheListener;
+ private CountDownLatch mLatch;
+ private Handler mHandler;
+ private List<ScoredNetwork> mUpdatedNetworksCaptor;
+ private ScoredNetwork mValidScoredNetwork;
+ private WifiNetworkScoreCache mScoreCache;
+
+ private static ScanResult buildScanResult(String ssid, String bssid) {
+ return new ScanResult(
+ WifiSsid.createFromAsciiEncoded(ssid),
+ bssid,
+ "" /* caps */,
+ 0 /* level */,
+ 0 /* frequency */,
+ 0 /* tsf */,
+ 0 /* distCm */,
+ 0 /* distSdCm*/);
+ }
+
+ private static ScoredNetwork buildScoredNetwork(WifiKey key, RssiCurve curve) {
+ return new ScoredNetwork(new NetworkKey(key), curve);
+ }
+
+ // Called from setup
+ private void initializeCacheWithValidScoredNetwork() {
+ mScoreCache.updateScores(ImmutableList.of(mValidScoredNetwork));
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mockContext.getApplicationContext()).thenReturn(mockApplicationContext);
+
+ mValidScoredNetwork = buildScoredNetwork(VALID_KEY, mockRssiCurve);
+ mScoreCache = new WifiNetworkScoreCache(mockContext);
+ initializeCacheWithValidScoredNetwork();
+
+ HandlerThread thread = new HandlerThread("WifiNetworkScoreCacheTest Handler Thread");
+ thread.start();
+ mHandler = new Handler(thread.getLooper());
+ mLatch = new CountDownLatch(1);
+ mCacheListener = new CacheListener(mHandler) {
+ @Override
+ public void networkCacheUpdated(List<ScoredNetwork> updatedNetworks) {
+ mUpdatedNetworksCaptor = updatedNetworks;
+ mLatch.countDown();
+ }
+ };
+ }
+
+
+ @Test
+ public void isScoredNetworkShouldReturnTrueAfterUpdateScoresIsCalled() {
+ assertThat(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)).isTrue();
+ }
+
+ @Test
+ public void isScoredNetworkShouldReturnFalseAfterClearScoresIsCalled() {
+ mScoreCache.clearScores();
+ assertThat(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)).isFalse();
+ }
+
+ @Test
+ public void updateScoresShouldAddNewNetwork() {
+ WifiKey key2 = new WifiKey("\"ssid2\"", BSSID);
+ ScoredNetwork network2 = buildScoredNetwork(key2, mockRssiCurve);
+ ScanResult result2 = buildScanResult("ssid2", BSSID);
+
+ mScoreCache.updateScores(ImmutableList.of(network2));
+
+ assertThat(mScoreCache.isScoredNetwork(VALID_SCAN_RESULT)).isTrue();
+ assertThat(mScoreCache.isScoredNetwork(result2)).isTrue();
+ }
+
+ @Test
+ public void hasScoreCurveShouldReturnTrue() {
+ assertThat(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)).isTrue();
+ }
+
+ @Test
+ public void hasScoreCurveShouldReturnFalseWhenNoCachedNetwork() {
+ ScanResult unscored = buildScanResult("fake", BSSID);
+ assertThat(mScoreCache.hasScoreCurve(unscored)).isFalse();
+ }
+
+ @Test
+ public void hasScoreCurveShouldReturnFalseWhenScoredNetworkHasNoCurve() {
+ ScoredNetwork noCurve = buildScoredNetwork(VALID_KEY, null /* rssiCurve */);
+ mScoreCache.updateScores(ImmutableList.of(noCurve));
+
+ assertThat(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)).isFalse();
+ }
+
+ @Test
+ public void getNetworkScoreShouldReturnScore() {
+ final byte score = 50;
+ final int rssi = -70;
+ ScanResult result = new ScanResult(VALID_SCAN_RESULT);
+ result.level = rssi;
+
+ when(mockRssiCurve.lookupScore(rssi)).thenReturn(score);
+
+ assertThat(mScoreCache.getNetworkScore(result)).isEqualTo(score);
+ }
+
+ @Test
+ public void getMeteredHintShouldReturnFalse() {
+ assertThat(mScoreCache.getMeteredHint(VALID_SCAN_RESULT)).isFalse();
+ }
+
+ @Test
+ public void getMeteredHintShouldReturnTrue() {
+ ScoredNetwork network =
+ new ScoredNetwork(
+ new NetworkKey(VALID_KEY), mockRssiCurve, true /* metered Hint */);
+ mScoreCache.updateScores(ImmutableList.of(network));
+
+ assertThat(mScoreCache.getMeteredHint(VALID_SCAN_RESULT)).isTrue();
+ }
+
+ @Test
+ public void updateScoresShouldInvokeCacheListener_networkCacheUpdated() {
+ mScoreCache = new WifiNetworkScoreCache(mockContext, mCacheListener);
+ initializeCacheWithValidScoredNetwork();
+
+ try {
+ mLatch.await(1, TimeUnit.SECONDS); // wait for listener to be executed
+ } catch (InterruptedException e) {
+ fail("Interrupted Exception while waiting for listener to be invoked.");
+ }
+ // One network should be updated.
+ assertThat(mUpdatedNetworksCaptor.size()).isEqualTo(1);
+ assertThat(mUpdatedNetworksCaptor.get(0)).isEqualTo(mValidScoredNetwork);
+ }
+
+ @Test
+ public void leastRecentlyUsedScore_shouldBeEvictedFromCache() {
+ mScoreCache = new WifiNetworkScoreCache(mockContext, mCacheListener, 2 /* maxCacheSize */);
+
+ ScoredNetwork network1 = mValidScoredNetwork;
+ ScoredNetwork network2 = buildScoredNetwork(
+ new WifiKey(FORMATTED_SSID2, BSSID), mockRssiCurve);
+ ScoredNetwork network3 = buildScoredNetwork(
+ new WifiKey(FORMATTED_SSID3, BSSID), mockRssiCurve);
+ mScoreCache.updateScores(ImmutableList.of(network1));
+ mScoreCache.updateScores(ImmutableList.of(network2));
+
+ // First score should be evicted because max cache size has been reached.
+ mScoreCache.updateScores(ImmutableList.of(network3));
+
+ assertThat(mScoreCache.hasScoreCurve(buildScanResult(SSID2, BSSID))).isTrue();
+ assertThat(mScoreCache.hasScoreCurve(buildScanResult(SSID3, BSSID))).isTrue();
+ assertThat(mScoreCache.hasScoreCurve(VALID_SCAN_RESULT)).isFalse();
+ }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
new file mode 100644
index 0000000..7b900fe
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2020 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 android.net.wifi.ScanResult;
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.DeviceWiphyCapabilities}.
+ */
+@SmallTest
+public class DeviceWiphyCapabilitiesTest {
+ @Before
+ public void setUp() {}
+
+ /**
+ * DeviceWiphyCapabilities object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ DeviceWiphyCapabilities capa = new DeviceWiphyCapabilities();
+
+ capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, true);
+ capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, true);
+ capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, false);
+ capa.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_160MHZ, true);
+ capa.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
+ capa.setMaxNumberTxSpatialStreams(2);
+ capa.setMaxNumberRxSpatialStreams(1);
+
+ Parcel parcel = Parcel.obtain();
+ capa.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ DeviceWiphyCapabilities capaDeserialized =
+ DeviceWiphyCapabilities.CREATOR.createFromParcel(parcel);
+
+ assertEquals(capa, capaDeserialized);
+ assertEquals(capa.hashCode(), capaDeserialized.hashCode());
+ }
+
+ /**
+ * Test mapping wifi standard support into channel width support
+ */
+ @Test
+ public void testMappingWifiStandardIntoChannelWidthSupport() {
+ DeviceWiphyCapabilities capa = new DeviceWiphyCapabilities();
+
+ capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, false);
+ capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, false);
+ capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, false);
+ assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_20MHZ));
+ assertEquals(false, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ));
+ assertEquals(false, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ));
+
+ capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, true);
+ assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_20MHZ));
+ assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ));
+ assertEquals(false, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ));
+
+ capa.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, true);
+ assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_20MHZ));
+ assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_40MHZ));
+ assertEquals(true, capa.isChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ));
+ }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/NativeScanResultTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/NativeScanResultTest.java
new file mode 100644
index 0000000..8ddd189
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/NativeScanResultTest.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 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 android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.NativeScanResult}.
+ */
+@SmallTest
+public class NativeScanResultTest {
+
+ private static final byte[] TEST_SSID =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_BSSID =
+ new byte[] {(byte) 0x12, (byte) 0xef, (byte) 0xa1,
+ (byte) 0x2c, (byte) 0x97, (byte) 0x8b};
+ private static final byte[] TEST_INFO_ELEMENT =
+ new byte[] {(byte) 0x01, (byte) 0x03, (byte) 0x12, (byte) 0xbe, (byte) 0xff};
+ private static final int TEST_FREQUENCY = 2456;
+ private static final int TEST_SIGNAL_MBM = -45;
+ private static final long TEST_TSF = 34455441;
+ private static final int TEST_CAPABILITY = (0x1 << 2) | (0x1 << 5);
+ private static final boolean TEST_ASSOCIATED = true;
+ private static final int[] RADIO_CHAIN_IDS = { 0, 1 };
+ private static final int[] RADIO_CHAIN_LEVELS = { -56, -65 };
+
+ /**
+ * NativeScanResult object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ NativeScanResult scanResult = new NativeScanResult();
+ scanResult.ssid = TEST_SSID;
+ scanResult.bssid = TEST_BSSID;
+ scanResult.infoElement = TEST_INFO_ELEMENT;
+ scanResult.frequency = TEST_FREQUENCY;
+ scanResult.signalMbm = TEST_SIGNAL_MBM;
+ scanResult.tsf = TEST_TSF;
+ scanResult.capability = TEST_CAPABILITY;
+ scanResult.associated = TEST_ASSOCIATED;
+ scanResult.radioChainInfos = new ArrayList<>(Arrays.asList(
+ new RadioChainInfo(RADIO_CHAIN_IDS[0], RADIO_CHAIN_LEVELS[0]),
+ new RadioChainInfo(RADIO_CHAIN_IDS[1], RADIO_CHAIN_LEVELS[1])));
+ Parcel parcel = Parcel.obtain();
+ scanResult.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ NativeScanResult scanResultDeserialized = NativeScanResult.CREATOR.createFromParcel(parcel);
+
+ assertArrayEquals(scanResult.ssid, scanResultDeserialized.ssid);
+ assertArrayEquals(scanResult.bssid, scanResultDeserialized.bssid);
+ assertArrayEquals(scanResult.infoElement, scanResultDeserialized.infoElement);
+ assertEquals(scanResult.frequency, scanResultDeserialized.frequency);
+ assertEquals(scanResult.signalMbm, scanResultDeserialized.signalMbm);
+ assertEquals(scanResult.tsf, scanResultDeserialized.tsf);
+ assertEquals(scanResult.capability, scanResultDeserialized.capability);
+ assertEquals(scanResult.associated, scanResultDeserialized.associated);
+ assertTrue(scanResult.radioChainInfos.containsAll(scanResultDeserialized.radioChainInfos));
+ }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/PnoSettingsTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/PnoSettingsTest.java
new file mode 100644
index 0000000..dec1db8
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/PnoSettingsTest.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 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 android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.PnoSettings}.
+ */
+@SmallTest
+public class PnoSettingsTest {
+
+ private static final byte[] TEST_SSID_1 =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_SSID_2 =
+ new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+ private static final int[] TEST_FREQUENCIES_1 = {};
+ private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+ private static final int TEST_INTERVAL_MS = 30000;
+ private static final int TEST_MIN_2G_RSSI = -60;
+ private static final int TEST_MIN_5G_RSSI = -65;
+ private static final int TEST_VALUE = 42;
+
+ private PnoNetwork mPnoNetwork1;
+ private PnoNetwork mPnoNetwork2;
+
+ @Before
+ public void setUp() {
+ mPnoNetwork1 = new PnoNetwork();
+ mPnoNetwork1.setSsid(TEST_SSID_1);
+ mPnoNetwork1.setHidden(true);
+ mPnoNetwork1.setFrequenciesMhz(TEST_FREQUENCIES_1);
+
+ mPnoNetwork2 = new PnoNetwork();
+ mPnoNetwork2.setSsid(TEST_SSID_2);
+ mPnoNetwork2.setHidden(false);
+ mPnoNetwork2.setFrequenciesMhz(TEST_FREQUENCIES_2);
+ }
+
+ /**
+ * PnoSettings object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ PnoSettings pnoSettings = new PnoSettings();
+ pnoSettings.setIntervalMillis(TEST_INTERVAL_MS);
+ pnoSettings.setMin2gRssiDbm(TEST_MIN_2G_RSSI);
+ pnoSettings.setMin5gRssiDbm(TEST_MIN_5G_RSSI);
+ pnoSettings.setPnoNetworks(new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2)));
+
+ Parcel parcel = Parcel.obtain();
+ pnoSettings.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ PnoSettings pnoSettingsDeserialized = PnoSettings.CREATOR.createFromParcel(parcel);
+
+ assertEquals(pnoSettings, pnoSettingsDeserialized);
+ assertEquals(pnoSettings.hashCode(), pnoSettingsDeserialized.hashCode());
+ }
+
+ /**
+ * Tests usage of {@link PnoSettings} as a HashMap key type.
+ */
+ @Test
+ public void testAsHashMapKey() {
+ PnoSettings pnoSettings1 = new PnoSettings();
+ pnoSettings1.setIntervalMillis(TEST_INTERVAL_MS);
+ pnoSettings1.setMin2gRssiDbm(TEST_MIN_2G_RSSI);
+ pnoSettings1.setMin5gRssiDbm(TEST_MIN_5G_RSSI);
+ pnoSettings1.setPnoNetworks(new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2)));
+
+ PnoSettings pnoSettings2 = new PnoSettings();
+ pnoSettings2.setIntervalMillis(TEST_INTERVAL_MS);
+ pnoSettings2.setMin2gRssiDbm(TEST_MIN_2G_RSSI);
+ pnoSettings2.setMin5gRssiDbm(TEST_MIN_5G_RSSI);
+ pnoSettings2.setPnoNetworks(new ArrayList<>(Arrays.asList(mPnoNetwork1, mPnoNetwork2)));
+
+ assertEquals(pnoSettings1, pnoSettings2);
+ assertEquals(pnoSettings1.hashCode(), pnoSettings2.hashCode());
+
+ HashMap<PnoSettings, Integer> map = new HashMap<>();
+ map.put(pnoSettings1, TEST_VALUE);
+
+ assertEquals(TEST_VALUE, map.get(pnoSettings2).intValue());
+ }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java
new file mode 100644
index 0000000..9059208
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 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 android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.SingleScanSettingsResult}.
+ */
+@SmallTest
+public class SingleScanSettingsTest {
+
+ private static final byte[] TEST_SSID_1 =
+ new byte[] {'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_SSID_2 =
+ new byte[] {'A', 'n', 'd', 'r', 'o', 'i', 'd', 'T', 'e', 's', 't'};
+ private static final int TEST_FREQUENCY_1 = 2456;
+ private static final int TEST_FREQUENCY_2 = 5215;
+ private static final int TEST_VALUE = 42;
+
+ private ChannelSettings mChannelSettings1;
+ private ChannelSettings mChannelSettings2;
+ private HiddenNetwork mHiddenNetwork1;
+ private HiddenNetwork mHiddenNetwork2;
+
+ @Before
+ public void setUp() {
+ mChannelSettings1 = new ChannelSettings();
+ mChannelSettings1.frequency = TEST_FREQUENCY_1;
+ mChannelSettings2 = new ChannelSettings();
+ mChannelSettings2.frequency = TEST_FREQUENCY_2;
+
+ mHiddenNetwork1 = new HiddenNetwork();
+ mHiddenNetwork1.ssid = TEST_SSID_1;
+ mHiddenNetwork2 = new HiddenNetwork();
+ mHiddenNetwork2.ssid = TEST_SSID_2;
+ }
+
+ /**
+ * SingleScanSettings object can be serialized and deserialized, while keeping the
+ * values unchanged.
+ */
+ @Test
+ public void canSerializeAndDeserialize() {
+ SingleScanSettings scanSettings = new SingleScanSettings();
+ scanSettings.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+
+ scanSettings.channelSettings =
+ new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+ scanSettings.hiddenNetworks =
+ new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+ Parcel parcel = Parcel.obtain();
+ scanSettings.writeToParcel(parcel, 0);
+ // Rewind the pointer to the head of the parcel.
+ parcel.setDataPosition(0);
+ SingleScanSettings scanSettingsDeserialized =
+ SingleScanSettings.CREATOR.createFromParcel(parcel);
+
+ assertEquals(scanSettings, scanSettingsDeserialized);
+ assertEquals(scanSettings.hashCode(), scanSettingsDeserialized.hashCode());
+ }
+
+ /**
+ * Tests usage of {@link SingleScanSettings} as a HashMap key type.
+ */
+ @Test
+ public void testAsHashMapKey() {
+ SingleScanSettings scanSettings1 = new SingleScanSettings();
+ scanSettings1.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ scanSettings1.channelSettings =
+ new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+ scanSettings1.hiddenNetworks =
+ new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+ SingleScanSettings scanSettings2 = new SingleScanSettings();
+ scanSettings2.scanType = IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY;
+ scanSettings2.channelSettings =
+ new ArrayList<>(Arrays.asList(mChannelSettings1, mChannelSettings2));
+ scanSettings2.hiddenNetworks =
+ new ArrayList<>(Arrays.asList(mHiddenNetwork1, mHiddenNetwork2));
+
+ assertEquals(scanSettings1, scanSettings2);
+ assertEquals(scanSettings1.hashCode(), scanSettings2.hashCode());
+
+ HashMap<SingleScanSettings, Integer> map = new HashMap<>();
+ map.put(scanSettings1, TEST_VALUE);
+
+ assertEquals(TEST_VALUE, map.get(scanSettings2).intValue());
+ }
+}
diff --git a/wifi/non-updatable/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
new file mode 100644
index 0000000..9ee0acbf
--- /dev/null
+++ b/wifi/non-updatable/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java
@@ -0,0 +1,1265 @@
+/*
+ * Copyright (C) 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 android.net.wifi.nl80211;
+
+import static org.junit.Assert.assertEquals;
+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.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Matchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.AlarmManager;
+import android.app.test.TestAlarmManager;
+import android.content.Context;
+import android.net.MacAddress;
+import android.net.wifi.ScanResult;
+import android.net.wifi.SoftApInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiScanner;
+import android.net.wifi.util.HexEncoding;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.test.TestLooper;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.AdditionalMatchers;
+import org.mockito.ArgumentCaptor;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.CharacterCodingException;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Unit tests for {@link android.net.wifi.nl80211.WifiNl80211Manager}.
+ */
+@SmallTest
+public class WifiNl80211ManagerTest {
+ @Mock
+ private IWificond mWificond;
+ @Mock
+ private IBinder mWifiCondBinder;
+ @Mock
+ private IClientInterface mClientInterface;
+ @Mock
+ private IWifiScannerImpl mWifiScannerImpl;
+ @Mock
+ private IApInterface mApInterface;
+ @Mock
+ private WifiNl80211Manager.SoftApCallback mSoftApListener;
+ @Mock
+ private WifiNl80211Manager.SendMgmtFrameCallback mSendMgmtFrameCallback;
+ @Mock
+ private WifiNl80211Manager.ScanEventCallback mNormalScanCallback;
+ @Mock
+ private WifiNl80211Manager.ScanEventCallback mPnoScanCallback;
+ @Mock
+ private WifiNl80211Manager.PnoScanRequestCallback mPnoScanRequestCallback;
+ @Mock
+ private Context mContext;
+ private TestLooper mLooper;
+ private TestAlarmManager mTestAlarmManager;
+ private AlarmManager mAlarmManager;
+ private WifiNl80211Manager mWificondControl;
+ private static final String TEST_INTERFACE_NAME = "test_wlan_if";
+ private static final String TEST_INTERFACE_NAME1 = "test_wlan_if1";
+ private static final String TEST_INVALID_INTERFACE_NAME = "asdf";
+ private static final byte[] TEST_SSID =
+ new byte[]{'G', 'o', 'o', 'g', 'l', 'e', 'G', 'u', 'e', 's', 't'};
+ private static final byte[] TEST_PSK =
+ new byte[]{'T', 'e', 's', 't'};
+
+ private static final Set<Integer> SCAN_FREQ_SET =
+ new HashSet<Integer>() {{
+ add(2410);
+ add(2450);
+ add(5050);
+ add(5200);
+ }};
+ private static final String TEST_QUOTED_SSID_1 = "\"testSsid1\"";
+ private static final String TEST_QUOTED_SSID_2 = "\"testSsid2\"";
+ private static final int[] TEST_FREQUENCIES_1 = {};
+ private static final int[] TEST_FREQUENCIES_2 = {2500, 5124};
+ private static final MacAddress TEST_RAW_MAC_BYTES = MacAddress.fromBytes(
+ new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05});
+
+ private static final List<byte[]> SCAN_HIDDEN_NETWORK_SSID_LIST =
+ new ArrayList<byte[]>() {{
+ add(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
+ add(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
+ }};
+
+ private static final PnoSettings TEST_PNO_SETTINGS = new PnoSettings();
+ static {
+ TEST_PNO_SETTINGS.setIntervalMillis(6000);
+ List<PnoNetwork> initPnoNetworks = new ArrayList<>();
+ PnoNetwork network = new PnoNetwork();
+ network.setSsid(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_1)));
+ network.setHidden(true);
+ network.setFrequenciesMhz(TEST_FREQUENCIES_1);
+ initPnoNetworks.add(network);
+ network.setSsid(LocalNativeUtil.byteArrayFromArrayList(
+ LocalNativeUtil.decodeSsid(TEST_QUOTED_SSID_2)));
+ network.setHidden(false);
+ network.setFrequenciesMhz(TEST_FREQUENCIES_2);
+ initPnoNetworks.add(network);
+ TEST_PNO_SETTINGS.setPnoNetworks(initPnoNetworks);
+ }
+
+ private static final int TEST_MCS_RATE = 5;
+ private static final int TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS = 100;
+ private static final byte[] TEST_PROBE_FRAME = {
+ 0x40, 0x00, 0x3c, 0x00, (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b,
+ 0x33, 0x72, (byte) 0xf4, (byte) 0xf5, (byte) 0xe8, 0x51, (byte) 0x9e, 0x09,
+ (byte) 0xa8, (byte) 0xbd, 0x27, 0x5b, 0x33, 0x72, (byte) 0xb0, 0x66,
+ 0x00, 0x00
+ };
+
+ @Before
+ public void setUp() throws Exception {
+ // Setup mocks for successful WificondControl operation. Failure case mocks should be
+ // created in specific tests
+ MockitoAnnotations.initMocks(this);
+
+ mTestAlarmManager = new TestAlarmManager();
+ mAlarmManager = mTestAlarmManager.getAlarmManager();
+ when(mContext.getSystemServiceName(AlarmManager.class)).thenReturn(Context.ALARM_SERVICE);
+ when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
+ mLooper = new TestLooper();
+ when(mContext.getMainLooper()).thenReturn(mLooper.getLooper());
+
+ when(mWificond.asBinder()).thenReturn(mWifiCondBinder);
+ when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+ when(mWificond.createClientInterface(any())).thenReturn(mClientInterface);
+ when(mWificond.createApInterface(any())).thenReturn(mApInterface);
+ when(mWificond.tearDownClientInterface(any())).thenReturn(true);
+ when(mWificond.tearDownApInterface(any())).thenReturn(true);
+ when(mClientInterface.getWifiScannerImpl()).thenReturn(mWifiScannerImpl);
+ when(mClientInterface.getInterfaceName()).thenReturn(TEST_INTERFACE_NAME);
+ mWificondControl = new WifiNl80211Manager(mContext, mWificond);
+ assertEquals(true,
+ mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
+ mNormalScanCallback, mPnoScanCallback));
+ }
+
+ /**
+ * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testSetupInterfaceForClientMode() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+ verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that setupInterfaceForClientMode(TEST_INTERFACE_NAME) calls subscribeScanEvents().
+ */
+ @Test
+ public void testSetupInterfaceForClientModeCallsScanEventSubscripiton() throws Exception {
+ verify(mWifiScannerImpl).subscribeScanEvents(any(IScanEvent.class));
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterface() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+ assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+ verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterfaceOnInvalidIface() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+ assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME1));
+ verify(mWifiScannerImpl, never()).unsubscribeScanEvents();
+ verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+ verify(mWificond, never()).tearDownClientInterface(any());
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterfaceFailDueToExceptionScannerUnsubscribe() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+ doThrow(new RemoteException()).when(mWifiScannerImpl).unsubscribeScanEvents();
+
+ assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ verify(mWifiScannerImpl, never()).unsubscribePnoScanEvents();
+ verify(mWificond, never()).tearDownClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownClientInterfaceErrorWhenWificondFailed() throws Exception {
+ when(mWificond.tearDownClientInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+ assertFalse(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ verify(mWifiScannerImpl).unsubscribePnoScanEvents();
+ verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that the client handles are cleared after teardown.
+ */
+ @Test
+ public void testTeardownClientInterfaceClearsHandles() throws Exception {
+ testTeardownClientInterface();
+
+ assertNull(mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+ verify(mClientInterface, never()).signalPoll();
+
+ assertFalse(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl, never()).scan(any());
+ }
+
+ /**
+ * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) calls wificond.
+ */
+ @Test
+ public void testSetupInterfaceForSoftApMode() throws Exception {
+ when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(mApInterface);
+
+ assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+ verify(mWificond).createApInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that setupInterfaceForSoftAp() returns null when wificond is not started.
+ */
+ @Test
+ public void testSetupInterfaceForSoftApModeErrorWhenWificondIsNotStarted() throws Exception {
+ // Invoke wificond death handler to clear the handle.
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+
+ assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that setupInterfaceForSoftApMode(TEST_INTERFACE_NAME) returns null when wificond
+ * failed to setup AP interface.
+ */
+ @Test
+ public void testSetupInterfaceForSoftApModeErrorWhenWificondFailedToSetupInterface()
+ throws Exception {
+ when(mWificond.createApInterface(TEST_INTERFACE_NAME)).thenReturn(null);
+
+ assertEquals(false, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownSoftApInterface() throws Exception {
+ testSetupInterfaceForSoftApMode();
+ when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(true);
+
+ assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+ verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that tearDownSoftapInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownSoftApInterfaceOnInvalidIface() throws Exception {
+ testSetupInterfaceForSoftApMode();
+ when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME1)).thenReturn(true);
+
+ assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+ verify(mWificond, never()).tearDownApInterface(any());
+ }
+
+ /**
+ * Verifies that tearDownClientInterface(TEST_INTERFACE_NAME) calls Wificond.
+ */
+ @Test
+ public void testTeardownSoftApInterfaceErrorWhenWificondFailed() throws Exception {
+ testSetupInterfaceForSoftApMode();
+ when(mWificond.tearDownApInterface(TEST_INTERFACE_NAME)).thenReturn(false);
+
+ assertFalse(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME));
+ verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME);
+ }
+
+ /**
+ * Verifies that the SoftAp handles are cleared after teardown.
+ */
+ @Test
+ public void testTeardownSoftApInterfaceClearsHandles() throws Exception {
+ testTeardownSoftApInterface();
+
+ assertFalse(mWificondControl.registerApCallback(
+ TEST_INTERFACE_NAME, Runnable::run, mSoftApListener));
+ verify(mApInterface, never()).registerCallback(any());
+ }
+
+ /**
+ * Verifies that we can setup concurrent interfaces.
+ */
+ @Test
+ public void testSetupMultipleInterfaces() throws Exception {
+ when(mWificond.createApInterface(TEST_INTERFACE_NAME1)).thenReturn(mApInterface);
+
+ assertEquals(true, mWificondControl.setupInterfaceForSoftApMode(TEST_INTERFACE_NAME1));
+
+ verify(mWificond).createClientInterface(TEST_INTERFACE_NAME);
+ verify(mWificond).createApInterface(TEST_INTERFACE_NAME1);
+ }
+
+ /**
+ * Verifies that we can setup concurrent interfaces.
+ */
+ @Test
+ public void testTeardownMultipleInterfaces() throws Exception {
+ testSetupMultipleInterfaces();
+ assertTrue(mWificondControl.tearDownClientInterface(TEST_INTERFACE_NAME));
+ assertTrue(mWificondControl.tearDownSoftApInterface(TEST_INTERFACE_NAME1));
+
+ verify(mWificond).tearDownClientInterface(TEST_INTERFACE_NAME);
+ verify(mWificond).tearDownApInterface(TEST_INTERFACE_NAME1);
+ }
+
+ /**
+ * Verifies that tearDownInterfaces() calls wificond.
+ */
+ @Test
+ public void testTearDownInterfaces() throws Exception {
+ assertTrue(mWificondControl.tearDownInterfaces());
+ verify(mWificond).tearDownInterfaces();
+ }
+
+ /**
+ * Verifies that tearDownInterfaces() calls unsubscribeScanEvents() when there was
+ * a configured client interface.
+ */
+ @Test
+ public void testTearDownInterfacesRemovesScanEventSubscription() throws Exception {
+ assertTrue(mWificondControl.tearDownInterfaces());
+ verify(mWifiScannerImpl).unsubscribeScanEvents();
+ }
+
+ /**
+ * Verifies that tearDownInterfaces() returns false when wificond is not started.
+ */
+ @Test
+ public void testTearDownInterfacesErrorWhenWificondIsNotStarterd() throws Exception {
+ // Invoke wificond death handler to clear the handle.
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+ assertFalse(mWificondControl.tearDownInterfaces());
+ }
+
+ /**
+ * Verifies that signalPoll() calls wificond.
+ */
+ @Test
+ public void testSignalPoll() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
+ mNormalScanCallback, mPnoScanCallback);
+ mWificondControl.signalPoll(TEST_INTERFACE_NAME);
+ verify(mClientInterface).signalPoll();
+ }
+
+ /**
+ * Verifies that signalPoll() returns null when there is no configured client interface.
+ */
+ @Test
+ public void testSignalPollErrorWhenNoClientInterfaceConfigured() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ // Configure client interface.
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ Runnable::run, mNormalScanCallback, mPnoScanCallback));
+
+ // Tear down interfaces.
+ assertTrue(mWificondControl.tearDownInterfaces());
+
+ // Signal poll should fail.
+ assertEquals(null, mWificondControl.signalPoll(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that getTxPacketCounters() calls wificond.
+ */
+ @Test
+ public void testGetTxPacketCounters() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME, Runnable::run,
+ mNormalScanCallback, mPnoScanCallback);
+ mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME);
+ verify(mClientInterface).getPacketCounters();
+ }
+
+ /**
+ * Verifies that getTxPacketCounters() returns null when there is no configured client
+ * interface.
+ */
+ @Test
+ public void testGetTxPacketCountersErrorWhenNoClientInterfaceConfigured() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ // Configure client interface.
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ Runnable::run, mNormalScanCallback, mPnoScanCallback));
+
+ // Tear down interfaces.
+ assertTrue(mWificondControl.tearDownInterfaces());
+
+ // Signal poll should fail.
+ assertEquals(null, mWificondControl.getTxPacketCounters(TEST_INTERFACE_NAME));
+ }
+
+ /**
+ * Verifies that getScanResults() returns null when there is no configured client
+ * interface.
+ */
+ @Test
+ public void testGetScanResultsErrorWhenNoClientInterfaceConfigured() throws Exception {
+ when(mWificond.createClientInterface(TEST_INTERFACE_NAME)).thenReturn(mClientInterface);
+
+ // Configure client interface.
+ assertEquals(true, mWificondControl.setupInterfaceForClientMode(TEST_INTERFACE_NAME,
+ Runnable::run, mNormalScanCallback, mPnoScanCallback));
+
+ // Tear down interfaces.
+ assertTrue(mWificondControl.tearDownInterfaces());
+
+ // getScanResults should fail.
+ assertEquals(0,
+ mWificondControl.getScanResults(TEST_INTERFACE_NAME,
+ WifiNl80211Manager.SCAN_TYPE_SINGLE_SCAN).size());
+ }
+
+ /**
+ * Verifies that Scan() can convert input parameters to SingleScanSettings correctly.
+ */
+ @Test
+ public void testScan() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+ assertTrue(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+ IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+ }
+
+ /**
+ * Verifies that Scan() removes duplicates hiddenSsids passed in from input.
+ */
+ @Test
+ public void testScanWithDuplicateHiddenSsids() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+ // Create a list of hiddenSsid that has a duplicate element
+ List<byte[]> hiddenSsidWithDup = new ArrayList<>(SCAN_HIDDEN_NETWORK_SSID_LIST);
+ hiddenSsidWithDup.add(SCAN_HIDDEN_NETWORK_SSID_LIST.get(0));
+ assertEquals(hiddenSsidWithDup.get(0),
+ hiddenSsidWithDup.get(hiddenSsidWithDup.size() - 1));
+ // Pass the List with duplicate elements into scan()
+ assertTrue(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, hiddenSsidWithDup));
+ // But the argument passed down should have the duplicate removed.
+ verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+ IWifiScannerImpl.SCAN_TYPE_LOW_POWER,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST)));
+ }
+
+ /**
+ * Verifies that Scan() can handle null input parameters correctly.
+ */
+ @Test
+ public void testScanNullParameters() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(true);
+ assertTrue(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_HIGH_ACCURACY, null, null));
+ verify(mWifiScannerImpl).scan(argThat(new ScanMatcher(
+ IWifiScannerImpl.SCAN_TYPE_HIGH_ACCURACY, null, null)));
+ }
+
+ /**
+ * Verifies that Scan() can handle wificond scan failure.
+ */
+ @Test
+ public void testScanFailure() throws Exception {
+ when(mWifiScannerImpl.scan(any(SingleScanSettings.class))).thenReturn(false);
+ assertFalse(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, WifiScanner.SCAN_TYPE_LOW_LATENCY,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl).scan(any(SingleScanSettings.class));
+ }
+
+ /**
+ * Verifies that Scan() can handle invalid type.
+ */
+ @Test
+ public void testScanFailureDueToInvalidType() throws Exception {
+ assertFalse(mWificondControl.startScan(
+ TEST_INTERFACE_NAME, 100,
+ SCAN_FREQ_SET, SCAN_HIDDEN_NETWORK_SSID_LIST));
+ verify(mWifiScannerImpl, never()).scan(any(SingleScanSettings.class));
+ }
+
+ /**
+ * Verifies that startPnoScan() can convert input parameters to PnoSettings correctly.
+ */
+ @Test
+ public void testStartPnoScan() throws Exception {
+ when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(true);
+ assertTrue(
+ mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS, Runnable::run,
+ mPnoScanRequestCallback));
+ verify(mWifiScannerImpl).startPnoScan(eq(TEST_PNO_SETTINGS));
+ verify(mPnoScanRequestCallback).onPnoRequestSucceeded();
+ }
+
+ /**
+ * Verifies that stopPnoScan() calls underlying wificond.
+ */
+ @Test
+ public void testStopPnoScan() throws Exception {
+ when(mWifiScannerImpl.stopPnoScan()).thenReturn(true);
+ assertTrue(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).stopPnoScan();
+ }
+
+ /**
+ * Verifies that stopPnoScan() can handle wificond failure.
+ */
+ @Test
+ public void testStopPnoScanFailure() throws Exception {
+
+ when(mWifiScannerImpl.stopPnoScan()).thenReturn(false);
+ assertFalse(mWificondControl.stopPnoScan(TEST_INTERFACE_NAME));
+ verify(mWifiScannerImpl).stopPnoScan();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+ * result event.
+ */
+ @Test
+ public void testScanResultEvent() throws Exception {
+ ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+ verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+ IScanEvent scanEvent = messageCaptor.getValue();
+ assertNotNull(scanEvent);
+ scanEvent.OnScanResultReady();
+
+ verify(mNormalScanCallback).onScanResultReady();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon scan
+ * failed event.
+ */
+ @Test
+ public void testScanFailedEvent() throws Exception {
+ ArgumentCaptor<IScanEvent> messageCaptor = ArgumentCaptor.forClass(IScanEvent.class);
+ verify(mWifiScannerImpl).subscribeScanEvents(messageCaptor.capture());
+ IScanEvent scanEvent = messageCaptor.getValue();
+ assertNotNull(scanEvent);
+ scanEvent.OnScanFailed();
+
+ verify(mNormalScanCallback).onScanFailed();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMonitor broadcast methods upon pno scan
+ * result event.
+ */
+ @Test
+ public void testPnoScanResultEvent() throws Exception {
+ ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+ verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+ IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+ assertNotNull(pnoScanEvent);
+ pnoScanEvent.OnPnoNetworkFound();
+ verify(mPnoScanCallback).onScanResultReady();
+ }
+
+ /**
+ * Verifies that WificondControl can invoke WifiMetrics pno scan count methods upon pno event.
+ */
+ @Test
+ public void testPnoScanEventsForMetrics() throws Exception {
+ ArgumentCaptor<IPnoScanEvent> messageCaptor = ArgumentCaptor.forClass(IPnoScanEvent.class);
+ verify(mWifiScannerImpl).subscribePnoScanEvents(messageCaptor.capture());
+ IPnoScanEvent pnoScanEvent = messageCaptor.getValue();
+ assertNotNull(pnoScanEvent);
+
+ pnoScanEvent.OnPnoNetworkFound();
+ verify(mPnoScanCallback).onScanResultReady();
+
+ pnoScanEvent.OnPnoScanFailed();
+ verify(mPnoScanCallback).onScanFailed();
+ }
+
+ /**
+ * Verifies that startPnoScan() can invoke WifiMetrics pno scan count methods correctly.
+ */
+ @Test
+ public void testStartPnoScanForMetrics() throws Exception {
+ when(mWifiScannerImpl.startPnoScan(any(PnoSettings.class))).thenReturn(false);
+
+ assertFalse(
+ mWificondControl.startPnoScan(TEST_INTERFACE_NAME, TEST_PNO_SETTINGS, Runnable::run,
+ mPnoScanRequestCallback));
+ verify(mPnoScanRequestCallback).onPnoRequestFailed();
+ }
+
+ /**
+ * Verifies that abortScan() calls underlying wificond.
+ */
+ @Test
+ public void testAbortScan() throws Exception {
+ mWificondControl.abortScan(TEST_INTERFACE_NAME);
+ verify(mWifiScannerImpl).abortScan();
+ }
+
+ /**
+ * Ensures that the Ap interface callbacks are forwarded to the
+ * SoftApListener used for starting soft AP.
+ */
+ @Test
+ public void testSoftApListenerInvocation() throws Exception {
+ testSetupInterfaceForSoftApMode();
+
+ WifiConfiguration config = new WifiConfiguration();
+ config.SSID = new String(TEST_SSID, StandardCharsets.UTF_8);
+
+ when(mApInterface.registerCallback(any())).thenReturn(true);
+
+ final ArgumentCaptor<IApInterfaceEventCallback> apInterfaceCallbackCaptor =
+ ArgumentCaptor.forClass(IApInterfaceEventCallback.class);
+
+ assertTrue(mWificondControl.registerApCallback(
+ TEST_INTERFACE_NAME, Runnable::run, mSoftApListener));
+ verify(mApInterface).registerCallback(apInterfaceCallbackCaptor.capture());
+
+ final NativeWifiClient testClient = new NativeWifiClient(TEST_RAW_MAC_BYTES);
+ apInterfaceCallbackCaptor.getValue().onConnectedClientsChanged(testClient, true);
+ verify(mSoftApListener).onConnectedClientsChanged(eq(testClient), eq(true));
+
+ int channelFrequency = 2437;
+ int channelBandwidth = IApInterfaceEventCallback.BANDWIDTH_20;
+ apInterfaceCallbackCaptor.getValue().onSoftApChannelSwitched(channelFrequency,
+ channelBandwidth);
+ verify(mSoftApListener).onSoftApChannelSwitched(eq(channelFrequency),
+ eq(SoftApInfo.CHANNEL_WIDTH_20MHZ));
+ }
+
+ /**
+ * Verifies registration and invocation of wificond death handler.
+ */
+ @Test
+ public void testRegisterDeathHandler() throws Exception {
+ Runnable deathHandler = mock(Runnable.class);
+ mWificondControl.setOnServiceDeadCallback(deathHandler);
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+ verify(deathHandler).run();
+ }
+
+ /**
+ * Verifies handling of wificond death and ensures that all internal state is cleared and
+ * handlers are invoked.
+ */
+ @Test
+ public void testDeathHandling() throws Exception {
+ Runnable deathHandler = mock(Runnable.class);
+ mWificondControl.setOnServiceDeadCallback(deathHandler);
+
+ testSetupInterfaceForClientMode();
+
+ mWificondControl.binderDied();
+ mLooper.dispatchAll();
+ verify(deathHandler).run();
+
+ // The handles should be cleared after death.
+ assertEquals(0, mWificondControl.getChannelsMhzForBand(WifiScanner.WIFI_BAND_5_GHZ).length);
+ verify(mWificond, never()).getAvailable5gNonDFSChannels();
+ }
+
+ /**
+ * sendMgmtFrame() should fail if a null callback is passed in.
+ */
+ @Test
+ public void testSendMgmtFrameNullCallback() throws Exception {
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, null);
+
+ verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+ }
+
+ /**
+ * sendMgmtFrame() should fail if a null frame is passed in.
+ */
+ @Test
+ public void testSendMgmtFrameNullFrame() throws Exception {
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, null, TEST_MCS_RATE, Runnable::run,
+ mSendMgmtFrameCallback);
+
+ verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+ verify(mSendMgmtFrameCallback).onFailure(anyInt());
+ }
+
+ /**
+ * sendMgmtFrame() should fail if an interface name that does not exist is passed in.
+ */
+ @Test
+ public void testSendMgmtFrameInvalidInterfaceName() throws Exception {
+ mWificondControl.sendMgmtFrame(TEST_INVALID_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, mSendMgmtFrameCallback);
+
+ verify(mClientInterface, never()).SendMgmtFrame(any(), any(), anyInt());
+ verify(mSendMgmtFrameCallback).onFailure(anyInt());
+ }
+
+ /**
+ * sendMgmtFrame() should fail if it is called a second time before the first call completed.
+ */
+ @Test
+ public void testSendMgmtFrameCalledTwiceBeforeFinished() throws Exception {
+ WifiNl80211Manager.SendMgmtFrameCallback cb1 = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
+ WifiNl80211Manager.SendMgmtFrameCallback cb2 = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb1);
+ verify(cb1, never()).onFailure(anyInt());
+ verify(mClientInterface, times(1))
+ .SendMgmtFrame(AdditionalMatchers.aryEq(TEST_PROBE_FRAME),
+ any(), eq(TEST_MCS_RATE));
+
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb2);
+ verify(cb2).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_ALREADY_STARTED);
+ // verify SendMgmtFrame() still was only called once i.e. not called again
+ verify(mClientInterface, times(1))
+ .SendMgmtFrame(any(), any(), anyInt());
+ }
+
+ /**
+ * Tests that when a RemoteException is triggered on AIDL call, onFailure() is called only once.
+ */
+ @Test
+ public void testSendMgmtFrameThrowsException() throws Exception {
+ WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+
+ doThrow(new RemoteException()).when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb);
+ mLooper.dispatchAll();
+
+ verify(cb).onFailure(anyInt());
+ verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+ sendMgmtFrameEventCaptor.getValue().OnFailure(
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ mLooper.dispatchAll();
+
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+
+ verifyNoMoreInteractions(cb);
+ }
+
+ /**
+ * Tests that the onAck() callback is triggered correctly.
+ */
+ @Test
+ public void testSendMgmtFrameSuccess() throws Exception {
+ WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb);
+
+ sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+ mLooper.dispatchAll();
+ verify(cb).onAck(eq(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS));
+ verify(cb, never()).onFailure(anyInt());
+ verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+ // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+ // triggered again
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+ verify(cb, times(1)).onAck(anyInt());
+ verify(cb, never()).onFailure(anyInt());
+ }
+
+ /**
+ * Tests that the onFailure() callback is triggered correctly.
+ */
+ @Test
+ public void testSendMgmtFrameFailure() throws Exception {
+ WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb);
+
+ sendMgmtFrameEventCaptor.getValue().OnFailure(
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ verify(mAlarmManager).cancel(eq(alarmListenerCaptor.getValue()));
+
+ // verify that even if timeout is triggered afterwards, SendMgmtFrameCallback is not
+ // triggered again
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb, times(1)).onFailure(anyInt());
+ }
+
+ /**
+ * Tests that the onTimeout() callback is triggered correctly.
+ */
+ @Test
+ public void testSendMgmtFrameTimeout() throws Exception {
+ WifiNl80211Manager.SendMgmtFrameCallback cb = mock(
+ WifiNl80211Manager.SendMgmtFrameCallback.class);
+
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, cb);
+
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb).onFailure(WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+
+ // verify that even if onAck() callback is triggered after timeout,
+ // SendMgmtFrameCallback is not triggered again
+ sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+ mLooper.dispatchAll();
+ verify(cb, never()).onAck(anyInt());
+ verify(cb, times(1)).onFailure(anyInt());
+ }
+
+ /**
+ * Tests every possible test outcome followed by every other test outcome to ensure that the
+ * internal state is reset correctly between calls.
+ * i.e. (success, success), (success, failure), (success, timeout),
+ * (failure, failure), (failure, success), (failure, timeout),
+ * (timeout, timeout), (timeout, success), (timeout, failure)
+ *
+ * Also tests that internal state is reset correctly after a transient AIDL RemoteException.
+ */
+ @Test
+ public void testSendMgmtFrameMixed() throws Exception {
+ testSendMgmtFrameThrowsException();
+ testSendMgmtFrameSuccess();
+ testSendMgmtFrameSuccess();
+ testSendMgmtFrameFailure();
+ testSendMgmtFrameFailure();
+ testSendMgmtFrameTimeout();
+ testSendMgmtFrameTimeout();
+ testSendMgmtFrameSuccess();
+ testSendMgmtFrameTimeout();
+ testSendMgmtFrameFailure();
+ testSendMgmtFrameSuccess();
+ }
+
+ /**
+ * Tests that OnAck() does not perform any non-thread-safe operations on the binder thread.
+ *
+ * The sequence of instructions are:
+ * 1. post onAlarm() onto main thread
+ * 2. OnAck()
+ * 3. mLooper.dispatchAll()
+ *
+ * The actual order of execution is:
+ * 1. binder thread portion of OnAck()
+ * 2. onAlarm() (which purely executes on the main thread)
+ * 3. main thread portion of OnAck()
+ *
+ * If the binder thread portion of OnAck() is not thread-safe, it can possibly mess up
+ * onAlarm(). Tests that this does not occur.
+ */
+ @Test
+ public void testSendMgmtFrameTimeoutAckThreadSafe() throws Exception {
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, mSendMgmtFrameCallback);
+
+ // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+ // triggering onAlarm() ourselves during the test, manually post onto handler
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ // OnAck posts to the handler
+ sendMgmtFrameEventCaptor.getValue().OnAck(TEST_SEND_MGMT_FRAME_ELAPSED_TIME_MS);
+ mLooper.dispatchAll();
+ verify(mSendMgmtFrameCallback, never()).onAck(anyInt());
+ verify(mSendMgmtFrameCallback).onFailure(
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ }
+
+ /**
+ * See {@link #testSendMgmtFrameTimeoutAckThreadSafe()}. This test replaces OnAck() with
+ * OnFailure().
+ */
+ @Test
+ public void testSendMgmtFrameTimeoutFailureThreadSafe() throws Exception {
+ final ArgumentCaptor<ISendMgmtFrameEvent> sendMgmtFrameEventCaptor =
+ ArgumentCaptor.forClass(ISendMgmtFrameEvent.class);
+ doNothing().when(mClientInterface)
+ .SendMgmtFrame(any(), sendMgmtFrameEventCaptor.capture(), anyInt());
+ final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListenerCaptor =
+ ArgumentCaptor.forClass(AlarmManager.OnAlarmListener.class);
+ final ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
+ doNothing().when(mAlarmManager).set(anyInt(), anyLong(), any(),
+ alarmListenerCaptor.capture(), handlerCaptor.capture());
+ mWificondControl.sendMgmtFrame(TEST_INTERFACE_NAME, TEST_PROBE_FRAME, TEST_MCS_RATE,
+ Runnable::run, mSendMgmtFrameCallback);
+
+ // AlarmManager should post the onAlarm() callback onto the handler, but since we are
+ // triggering onAlarm() ourselves during the test, manually post onto handler
+ handlerCaptor.getValue().post(() -> alarmListenerCaptor.getValue().onAlarm());
+ // OnFailure posts to the handler
+ sendMgmtFrameEventCaptor.getValue().OnFailure(
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_UNKNOWN);
+ mLooper.dispatchAll();
+ verify(mSendMgmtFrameCallback).onFailure(
+ WifiNl80211Manager.SEND_MGMT_FRAME_ERROR_TIMEOUT);
+ }
+
+ /**
+ * Tests getDeviceWiphyCapabililties
+ */
+ @Test
+ public void testGetDeviceWiphyCapabilities() throws Exception {
+ DeviceWiphyCapabilities capaExpected = new DeviceWiphyCapabilities();
+
+ capaExpected.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11N, true);
+ capaExpected.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AC, true);
+ capaExpected.setWifiStandardSupport(ScanResult.WIFI_STANDARD_11AX, false);
+ capaExpected.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_160MHZ, true);
+ capaExpected.setChannelWidthSupported(ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ, false);
+ capaExpected.setMaxNumberTxSpatialStreams(2);
+ capaExpected.setMaxNumberRxSpatialStreams(1);
+
+ when(mWificond.getDeviceWiphyCapabilities(TEST_INTERFACE_NAME))
+ .thenReturn(capaExpected);
+
+ DeviceWiphyCapabilities capaActual =
+ mWificondControl.getDeviceWiphyCapabilities(TEST_INTERFACE_NAME);
+ assertEquals(capaExpected, capaActual);
+ }
+
+ // 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> {
+ int mExpectedScanType;
+ private final Set<Integer> mExpectedFreqs;
+ private final List<byte[]> mExpectedSsids;
+
+ ScanMatcher(int expectedScanType, Set<Integer> expectedFreqs, List<byte[]> expectedSsids) {
+ this.mExpectedScanType = expectedScanType;
+ this.mExpectedFreqs = expectedFreqs;
+ this.mExpectedSsids = expectedSsids;
+ }
+
+ @Override
+ public boolean matches(SingleScanSettings settings) {
+ if (settings.scanType != mExpectedScanType) {
+ return false;
+ }
+ ArrayList<ChannelSettings> channelSettings = settings.channelSettings;
+ ArrayList<HiddenNetwork> hiddenNetworks = settings.hiddenNetworks;
+ if (mExpectedFreqs != null) {
+ Set<Integer> freqSet = new HashSet<Integer>();
+ for (ChannelSettings channel : channelSettings) {
+ freqSet.add(channel.frequency);
+ }
+ if (!mExpectedFreqs.equals(freqSet)) {
+ return false;
+ }
+ } else {
+ if (channelSettings != null && channelSettings.size() > 0) {
+ return false;
+ }
+ }
+
+ if (mExpectedSsids != null) {
+ List<byte[]> ssidSet = new ArrayList<>();
+ for (HiddenNetwork network : hiddenNetworks) {
+ ssidSet.add(network.ssid);
+ }
+ if (!mExpectedSsids.equals(ssidSet)) {
+ return false;
+ }
+
+ } else {
+ if (hiddenNetworks != null && hiddenNetworks.size() > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "ScanMatcher{mExpectedFreqs=" + mExpectedFreqs
+ + ", mExpectedSsids=" + mExpectedSsids + '}';
+ }
+ }
+
+ private static class LocalNativeUtil {
+ private static final int SSID_BYTES_MAX_LEN = 32;
+
+ /**
+ * Converts an ArrayList<Byte> of UTF_8 byte values to string.
+ * The string will either be:
+ * a) UTF-8 String encapsulated in quotes (if all the bytes are UTF-8 encodeable and non
+ * null),
+ * or
+ * b) Hex string with no delimiters.
+ *
+ * @param bytes List of bytes for ssid.
+ * @throws IllegalArgumentException for null bytes.
+ */
+ public static String bytesToHexOrQuotedString(ArrayList<Byte> bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("null ssid bytes");
+ }
+ byte[] byteArray = byteArrayFromArrayList(bytes);
+ // Check for 0's in the byte stream in which case we cannot convert this into a string.
+ if (!bytes.contains(Byte.valueOf((byte) 0))) {
+ CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
+ try {
+ CharBuffer decoded = decoder.decode(ByteBuffer.wrap(byteArray));
+ return "\"" + decoded.toString() + "\"";
+ } catch (CharacterCodingException cce) {
+ }
+ }
+ return hexStringFromByteArray(byteArray);
+ }
+
+ /**
+ * Converts an ssid string to an arraylist of UTF_8 byte values.
+ * These forms are acceptable:
+ * a) UTF-8 String encapsulated in quotes, or
+ * b) Hex string with no delimiters.
+ *
+ * @param ssidStr String to be converted.
+ * @throws IllegalArgumentException for null string.
+ */
+ public static ArrayList<Byte> decodeSsid(String ssidStr) {
+ ArrayList<Byte> ssidBytes = hexOrQuotedStringToBytes(ssidStr);
+ if (ssidBytes.size() > SSID_BYTES_MAX_LEN) {
+ throw new IllegalArgumentException(
+ "ssid bytes size out of range: " + ssidBytes.size());
+ }
+ return ssidBytes;
+ }
+
+ /**
+ * Convert from an array list of Byte to an array of primitive bytes.
+ */
+ public static byte[] byteArrayFromArrayList(ArrayList<Byte> bytes) {
+ byte[] byteArray = new byte[bytes.size()];
+ int i = 0;
+ for (Byte b : bytes) {
+ byteArray[i++] = b;
+ }
+ return byteArray;
+ }
+
+ /**
+ * Converts a byte array to hex string.
+ *
+ * @param bytes List of bytes for ssid.
+ * @throws IllegalArgumentException for null bytes.
+ */
+ public static String hexStringFromByteArray(byte[] bytes) {
+ if (bytes == null) {
+ throw new IllegalArgumentException("null hex bytes");
+ }
+ return new String(HexEncoding.encode(bytes)).toLowerCase();
+ }
+
+ /**
+ * Converts an string to an arraylist of UTF_8 byte values.
+ * These forms are acceptable:
+ * a) UTF-8 String encapsulated in quotes, or
+ * b) Hex string with no delimiters.
+ *
+ * @param str String to be converted.
+ * @throws IllegalArgumentException for null string.
+ */
+ public static ArrayList<Byte> hexOrQuotedStringToBytes(String str) {
+ if (str == null) {
+ throw new IllegalArgumentException("null string");
+ }
+ int length = str.length();
+ if ((length > 1) && (str.charAt(0) == '"') && (str.charAt(length - 1) == '"')) {
+ str = str.substring(1, str.length() - 1);
+ return stringToByteArrayList(str);
+ } else {
+ return byteArrayToArrayList(hexStringToByteArray(str));
+ }
+ }
+
+ /**
+ * Convert the string to byte array list.
+ *
+ * @return the UTF_8 char byte values of str, as an ArrayList.
+ * @throws IllegalArgumentException if a null or unencodable string is sent.
+ */
+ public static ArrayList<Byte> stringToByteArrayList(String str) {
+ if (str == null) {
+ throw new IllegalArgumentException("null string");
+ }
+ // Ensure that the provided string is UTF_8 encoded.
+ CharsetEncoder encoder = StandardCharsets.UTF_8.newEncoder();
+ try {
+ ByteBuffer encoded = encoder.encode(CharBuffer.wrap(str));
+ byte[] byteArray = new byte[encoded.remaining()];
+ encoded.get(byteArray);
+ return byteArrayToArrayList(byteArray);
+ } catch (CharacterCodingException cce) {
+ throw new IllegalArgumentException("cannot be utf-8 encoded", cce);
+ }
+ }
+
+ /**
+ * Convert from an array of primitive bytes to an array list of Byte.
+ */
+ public static ArrayList<Byte> byteArrayToArrayList(byte[] bytes) {
+ ArrayList<Byte> byteList = new ArrayList<>();
+ for (Byte b : bytes) {
+ byteList.add(b);
+ }
+ return byteList;
+ }
+
+ /**
+ * Converts a hex string to byte array.
+ *
+ * @param hexStr String to be converted.
+ * @throws IllegalArgumentException for null string.
+ */
+ public static byte[] hexStringToByteArray(String hexStr) {
+ if (hexStr == null) {
+ throw new IllegalArgumentException("null hex string");
+ }
+ return HexEncoding.decode(hexStr.toCharArray(), false);
+ }
+ }
+}