diff options
author | David Su <dysu@google.com> | 2020-12-29 04:49:50 +0000 |
---|---|---|
committer | David Su <dysu@google.com> | 2020-12-29 04:49:50 +0000 |
commit | 3976a1d61b3d8f75eba9921428748e304c903365 (patch) | |
tree | 30c6f027c4016eea6377702ecb9c9ce193626e41 /wifi/tests/src | |
parent | 6bc7032b854bdec4d50b070a6f158539b63fce37 (diff) |
frameworks/base/wifi: unpack non-updatable/ folder
Everything remaining in frameworks/base/wifi is non-updatable.
Thus, unpack non-updatable/ folder and move everything under
wifi/.
Bug: 137323948
Test: presubmit
Change-Id: I6e1bd3fe6335b5dbe5f8ffa84a84df6434c95674
Diffstat (limited to 'wifi/tests/src')
7 files changed, 2092 insertions, 0 deletions
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java b/wifi/tests/src/android/net/wifi/SoftApConfToXmlMigrationUtilTest.java new file mode 100644 index 000000000000..f49f387cbc6b --- /dev/null +++ b/wifi/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/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkScoreCacheTest.java new file mode 100644 index 000000000000..c4967ebf1736 --- /dev/null +++ b/wifi/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/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java b/wifi/tests/src/android/net/wifi/nl80211/DeviceWiphyCapabilitiesTest.java new file mode 100644 index 000000000000..7b900fec70a8 --- /dev/null +++ b/wifi/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/tests/src/android/net/wifi/nl80211/NativeScanResultTest.java b/wifi/tests/src/android/net/wifi/nl80211/NativeScanResultTest.java new file mode 100644 index 000000000000..8ddd1899179a --- /dev/null +++ b/wifi/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/tests/src/android/net/wifi/nl80211/PnoSettingsTest.java b/wifi/tests/src/android/net/wifi/nl80211/PnoSettingsTest.java new file mode 100644 index 000000000000..dec1db8d274e --- /dev/null +++ b/wifi/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/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java b/wifi/tests/src/android/net/wifi/nl80211/SingleScanSettingsTest.java new file mode 100644 index 000000000000..905920888012 --- /dev/null +++ b/wifi/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/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java b/wifi/tests/src/android/net/wifi/nl80211/WifiNl80211ManagerTest.java new file mode 100644 index 000000000000..9ee0acbfbaa2 --- /dev/null +++ b/wifi/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); + } + } +} |