1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.wifi.cts;
18 
19 import static android.content.Context.RECEIVER_EXPORTED;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 import static com.google.common.truth.Truth.assertWithMessage;
23 
24 import static org.junit.Assert.assertEquals;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertNull;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assume.assumeTrue;
29 
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.net.wifi.MloLink;
35 import android.net.wifi.ScanResult;
36 import android.net.wifi.ScanResult.InformationElement;
37 import android.net.wifi.WifiInfo;
38 import android.net.wifi.WifiManager;
39 import android.net.wifi.WifiManager.WifiLock;
40 import android.net.wifi.WifiScanner;
41 import android.net.wifi.WifiSsid;
42 import android.os.Build;
43 import android.os.Parcel;
44 import android.platform.test.annotations.AppModeFull;
45 import android.platform.test.annotations.RequiresDevice;
46 import android.support.test.uiautomator.UiDevice;
47 
48 import androidx.test.ext.junit.runners.AndroidJUnit4;
49 import androidx.test.platform.app.InstrumentationRegistry;
50 
51 import com.android.compatibility.common.util.ApiLevelUtil;
52 import com.android.compatibility.common.util.PollingCheck;
53 import com.android.compatibility.common.util.ShellIdentityUtils;
54 
55 import org.junit.AfterClass;
56 import org.junit.Before;
57 import org.junit.BeforeClass;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 
61 import java.nio.ByteBuffer;
62 import java.nio.charset.StandardCharsets;
63 import java.util.List;
64 
65 
66 @RunWith(AndroidJUnit4.class)
67 @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
68 public class ScanResultTest extends WifiJUnit4TestBase {
69     private static Context sContext;
70 
71     private static boolean sShouldRunTest = false;
72 
73     private static class MySync {
74         int expectedState = STATE_NULL;
75     }
76 
77     private static WifiManager sWifiManager;
78     private static WifiLock sWifiLock;
79     private static TestHelper sTestHelper;
80     private static MySync sMySync;
81     private static boolean sWasVerboseLoggingEnabled;
82     private static boolean sWasScanThrottleEnabled;
83 
84     private static final int STATE_NULL = 0;
85     private static final int STATE_WIFI_CHANGING = 1;
86     private static final int STATE_WIFI_CHANGED = 2;
87     private static final int STATE_START_SCAN = 3;
88     private static final int STATE_SCAN_RESULTS_AVAILABLE = 4;
89     private static final int STATE_SCAN_FAILURE = 5;
90 
91     private static final String TAG = "WifiInfoTest";
92     private static final int TIMEOUT_MSEC = 6000;
93     private static final int WAIT_MSEC = 60;
94     private static final int ENABLE_WAIT_MSEC = 10000;
95     private static final int SCAN_WAIT_MSEC = 10000;
96     private static final int SCAN_MAX_RETRY_COUNT = 6;
97     private static final int SCAN_FIND_BSSID_MAX_RETRY_COUNT = 5;
98     private static final long SCAN_FIND_BSSID_WAIT_MSEC = 5_000L;
99     private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000;
100 
101     // Note: defined in ScanRequestProxy.java
102     public static final int SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS = 4;
103 
104     private static final String TEST_SSID = "TEST_SSID";
105     public static final String TEST_BSSID = "04:ac:fe:45:34:10";
106     public static final String TEST_CAPS = "CCMP";
107     public static final int TEST_LEVEL = -56;
108     public static final int TEST_FREQUENCY = 2412;
109     public static final long TEST_TIMESTAMP = 4660L;
110 
111     private static final BroadcastReceiver RECEIVER = new BroadcastReceiver() {
112         @Override
113         public void onReceive(Context context, Intent intent) {
114             final String action = intent.getAction();
115             if (action.equals(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION)) {
116                 synchronized (sMySync) {
117                     sMySync.expectedState = STATE_WIFI_CHANGED;
118                     sMySync.notify();
119                 }
120             } else if (action.equals(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION)) {
121                 synchronized (sMySync) {
122                     if (intent.getBooleanExtra(WifiManager.EXTRA_RESULTS_UPDATED, false)) {
123                         sMySync.expectedState = STATE_SCAN_RESULTS_AVAILABLE;
124                     } else {
125                         sMySync.expectedState = STATE_SCAN_FAILURE;
126                     }
127                     sMySync.notify();
128                 }
129             }
130         }
131     };
132 
133     @BeforeClass
setUpClass()134     public static void setUpClass() throws Exception {
135         sContext = InstrumentationRegistry.getInstrumentation().getContext();
136         if (!WifiFeature.isWifiSupported(sContext)) {
137             // skip the test if WiFi is not supported
138             return;
139         }
140         sShouldRunTest = true;
141         sMySync = new MySync();
142         IntentFilter intentFilter = new IntentFilter();
143         intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
144         intentFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
145         intentFilter.addAction(WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION);
146         intentFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
147         intentFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
148         intentFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
149         intentFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
150         intentFilter.addAction(WifiManager.ACTION_PICK_WIFI_NETWORK);
151 
152         if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)) {
153             sContext.registerReceiver(RECEIVER, intentFilter, RECEIVER_EXPORTED);
154         } else {
155             sContext.registerReceiver(RECEIVER, intentFilter);
156         }
157         sWifiManager = sContext.getSystemService(WifiManager.class);
158         assertThat(sWifiManager).isNotNull();
159 
160         sTestHelper = new TestHelper(InstrumentationRegistry.getInstrumentation().getContext(),
161                 UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()));
162 
163         // turn on verbose logging for tests
164         sWasVerboseLoggingEnabled = ShellIdentityUtils.invokeWithShellPermissions(
165                 () -> sWifiManager.isVerboseLoggingEnabled());
166         ShellIdentityUtils.invokeWithShellPermissions(
167                 () -> sWifiManager.setVerboseLoggingEnabled(true));
168         // Disable scan throttling for tests.
169         sWasScanThrottleEnabled = ShellIdentityUtils.invokeWithShellPermissions(
170                 () -> sWifiManager.isScanThrottleEnabled());
171         ShellIdentityUtils.invokeWithShellPermissions(
172                 () -> sWifiManager.setScanThrottleEnabled(false));
173 
174         sWifiLock = sWifiManager.createWifiLock(TAG);
175         sWifiLock.acquire();
176 
177         // enable Wifi
178         if (!sWifiManager.isWifiEnabled()) setWifiEnabled(true);
179         PollingCheck.check("Wifi not enabled", ENABLE_WAIT_MSEC,
180                 () -> sWifiManager.isWifiEnabled());
181 
182         sMySync.expectedState = STATE_NULL;
183     }
184 
185     @AfterClass
tearDownClass()186     public static void tearDownClass() throws Exception {
187         if (!sShouldRunTest) {
188             return;
189         }
190         sWifiLock.release();
191         sContext.unregisterReceiver(RECEIVER);
192         if (!sWifiManager.isWifiEnabled()) setWifiEnabled(true);
193         ShellIdentityUtils.invokeWithShellPermissions(
194                 () -> sWifiManager.setScanThrottleEnabled(sWasScanThrottleEnabled));
195         ShellIdentityUtils.invokeWithShellPermissions(
196                 () -> sWifiManager.setVerboseLoggingEnabled(sWasVerboseLoggingEnabled));
197     }
198 
199     @Before
setUp()200     public void setUp() throws Exception {
201         assumeTrue(sShouldRunTest);
202     }
203 
setWifiEnabled(boolean enable)204     private static void setWifiEnabled(boolean enable) throws Exception {
205         synchronized (sMySync) {
206             sMySync.expectedState = STATE_WIFI_CHANGING;
207             ShellIdentityUtils.invokeWithShellPermissions(
208                     () -> sWifiManager.setWifiEnabled(enable));
209             waitForBroadcast(TIMEOUT_MSEC, STATE_WIFI_CHANGED);
210        }
211     }
212 
waitForBroadcast(long timeout, int expectedState)213     private static boolean waitForBroadcast(long timeout, int expectedState) throws Exception {
214         long waitTime = System.currentTimeMillis() + timeout;
215         while (System.currentTimeMillis() < waitTime
216                 && sMySync.expectedState != expectedState)
217             sMySync.wait(WAIT_MSEC);
218         return sMySync.expectedState == expectedState;
219     }
220 
221     @Test
testScanResultProperties()222     public void testScanResultProperties() {
223         // this test case should in Wifi environment
224         for (ScanResult scanResult : sWifiManager.getScanResults()) {
225             assertThat(scanResult.toString()).isNotNull();
226 
227             for (InformationElement ie : scanResult.getInformationElements()) {
228                 testInformationElementCopyConstructor(ie);
229                 testInformationElementFields(ie);
230             }
231 
232             assertThat(scanResult.getWifiStandard()).isAnyOf(
233                     ScanResult.WIFI_STANDARD_UNKNOWN,
234                     ScanResult.WIFI_STANDARD_LEGACY,
235                     ScanResult.WIFI_STANDARD_11N,
236                     ScanResult.WIFI_STANDARD_11AC,
237                     ScanResult.WIFI_STANDARD_11AX,
238                     ScanResult.WIFI_STANDARD_11AD,
239                     ScanResult.WIFI_STANDARD_11BE
240             );
241 
242             scanResult.isPasspointNetwork();
243         }
244     }
245 
testInformationElementCopyConstructor(InformationElement ie)246     private void testInformationElementCopyConstructor(InformationElement ie) {
247         InformationElement copy = new InformationElement(ie);
248 
249         assertThat(copy.getId()).isEqualTo(ie.getId());
250         assertThat(copy.getIdExt()).isEqualTo(ie.getIdExt());
251         assertThat(copy.getBytes()).isEqualTo(ie.getBytes());
252     }
253 
testInformationElementFields(InformationElement ie)254     private void testInformationElementFields(InformationElement ie) {
255         // id is 1 octet
256         int id = ie.getId();
257         assertThat(id).isAtLeast(0);
258         assertThat(id).isAtMost(255);
259 
260         // idExt is 0 or 1 octet
261         int idExt = ie.getIdExt();
262         assertThat(idExt).isAtLeast(0);
263         assertThat(idExt).isAtMost(255);
264 
265         ByteBuffer bytes = ie.getBytes();
266         assertThat(bytes).isNotNull();
267     }
268 
269     /* Multiple scans to ensure bssid is updated */
scanAndWait()270     private void scanAndWait() throws Exception {
271         synchronized (sMySync) {
272             for (int retry  = 0; retry < SCAN_MAX_RETRY_COUNT; retry++) {
273                 sMySync.expectedState = STATE_START_SCAN;
274                 sWifiManager.startScan();
275                 if (waitForBroadcast(SCAN_WAIT_MSEC, STATE_SCAN_RESULTS_AVAILABLE)) {
276                     break;
277                 }
278             }
279         }
280    }
281 
282     @RequiresDevice
283     @Test
testScanResultTimeStamp()284     public void testScanResultTimeStamp() throws Exception {
285         long timestamp = 0;
286         String BSSID = null;
287 
288         scanAndWait();
289 
290         List<ScanResult> scanResults = sWifiManager.getScanResults();
291         for (ScanResult result : scanResults) {
292             BSSID = result.BSSID;
293             timestamp = result.timestamp;
294             assertThat(timestamp).isNotEqualTo(0);
295             break;
296         }
297 
298         scanAndWait();
299 
300         scanResults = sWifiManager.getScanResults();
301         for (ScanResult result : scanResults) {
302             if (result.BSSID.equals(BSSID)) {
303                 long timeDiff = (result.timestamp - timestamp) / 1000;
304                 assertThat(timeDiff).isGreaterThan(0L);
305                 assertThat(timeDiff).isLessThan(6L * SCAN_WAIT_MSEC);
306             }
307         }
308     }
309 
310     /** Test that the copy constructor copies fields correctly. */
311     @Test
testScanResultConstructors()312     public void testScanResultConstructors() {
313         ScanResult scanResult = new ScanResult();
314         scanResult.SSID = TEST_SSID;
315         scanResult.setWifiSsid(WifiSsid.fromBytes(TEST_SSID.getBytes(StandardCharsets.UTF_8)));
316         scanResult.BSSID = TEST_BSSID;
317         scanResult.capabilities = TEST_CAPS;
318         scanResult.level = TEST_LEVEL;
319         scanResult.frequency = TEST_FREQUENCY;
320         scanResult.timestamp = TEST_TIMESTAMP;
321 
322         ScanResult scanResult2 = new ScanResult(scanResult);
323         assertThat(scanResult2.SSID).isEqualTo(TEST_SSID);
324         assertThat(scanResult2.getWifiSsid()).isEqualTo(scanResult.getWifiSsid());
325         assertThat(scanResult2.BSSID).isEqualTo(TEST_BSSID);
326         assertThat(scanResult2.capabilities).isEqualTo(TEST_CAPS);
327         assertThat(scanResult2.level).isEqualTo(TEST_LEVEL);
328         assertThat(scanResult2.frequency).isEqualTo(TEST_FREQUENCY);
329         assertThat(scanResult2.timestamp).isEqualTo(TEST_TIMESTAMP);
330     }
331 
332     @Test
testScanResultMatchesWifiInfo()333     public void testScanResultMatchesWifiInfo() throws Exception {
334         // ensure Wifi is connected
335         ShellIdentityUtils.invokeWithShellPermissions(() -> sWifiManager.reconnect());
336         PollingCheck.check(
337                 "Wifi not connected",
338                 WIFI_CONNECT_TIMEOUT_MILLIS,
339                 () -> sWifiManager.getConnectionInfo().getNetworkId() != -1);
340 
341         final WifiInfo wifiInfo = sWifiManager.getConnectionInfo();
342         assertThat(wifiInfo).isNotNull();
343 
344         ScanResult currentNetwork = null;
345         for (int i = 0; i < SCAN_FIND_BSSID_MAX_RETRY_COUNT; i++) {
346             scanAndWait();
347             final List<ScanResult> scanResults = sWifiManager.getScanResults();
348             currentNetwork = scanResults.stream().filter(r -> r.BSSID.equals(wifiInfo.getBSSID()))
349                     .findAny().orElse(null);
350 
351             if (currentNetwork != null) {
352                 break;
353             }
354             Thread.sleep(SCAN_FIND_BSSID_WAIT_MSEC);
355         }
356         assertWithMessage("Current network not found in scan results")
357                 .that(currentNetwork).isNotNull();
358 
359         String wifiInfoSsidQuoted = wifiInfo.getSSID();
360         String scanResultSsidUnquoted = currentNetwork.SSID;
361 
362         assertWithMessage(
363                 "SSID mismatch: make sure this isn't a hidden network or an SSID containing "
364                         + "non-UTF-8 characters - neither is supported by this CTS test.")
365                 .that("\"" + scanResultSsidUnquoted + "\"")
366                 .isEqualTo(wifiInfoSsidQuoted);
367         assertThat(currentNetwork.frequency).isEqualTo(wifiInfo.getFrequency());
368         assertThat(currentNetwork.getSecurityTypes())
369                 .asList().contains(wifiInfo.getCurrentSecurityType());
370     }
371 
372     /**
373      * Verify that scan throttling is enforced.
374      */
375     @Test
testScanThrottling()376     public void testScanThrottling() throws Exception {
377         // re-enable scan throttling
378         ShellIdentityUtils.invokeWithShellPermissions(
379                 () -> sWifiManager.setScanThrottleEnabled(true));
380         sTestHelper.turnScreenOn();
381 
382         try {
383             synchronized (sMySync) {
384                 for (int i = 0; i < SCAN_REQUEST_THROTTLE_MAX_IN_TIME_WINDOW_FG_APPS; ++i) {
385                     sWifiManager.startScan();
386                     // TODO(b/277663385): Increased timeout until cuttlefish's mac80211_hwsim uses
387                     //  more than 1 channel.
388                     assertTrue("Iteration #" + i,
389                             waitForBroadcast(SCAN_WAIT_MSEC * 2, STATE_SCAN_RESULTS_AVAILABLE));
390                 }
391 
392                 sWifiManager.startScan();
393                 assertTrue("Should be throttled",
394                         waitForBroadcast(SCAN_WAIT_MSEC, STATE_SCAN_FAILURE));
395             }
396         } finally {
397             ShellIdentityUtils.invokeWithShellPermissions(
398                     () -> sWifiManager.setScanThrottleEnabled(false));
399         }
400     }
401 
402     /**
403      * Test MLO Attributes in ScanResult Constructor (WiFi-7)
404      */
405     @Test
testScanResultMloAttributes()406     public void testScanResultMloAttributes() {
407         ScanResult scanResult = new ScanResult();
408         assertNull(scanResult.getApMldMacAddress());
409         assertEquals(MloLink.INVALID_MLO_LINK_ID, scanResult.getApMloLinkId());
410         assertNotNull(scanResult.getAffiliatedMloLinks());
411         assertTrue(scanResult.getAffiliatedMloLinks().isEmpty());
412     }
413 
414     /**
415      * Test MLO Link Constructor (WiFi-7)
416      */
417     @Test
testMloLinkConstructor()418     public void testMloLinkConstructor() {
419         MloLink mloLink = new MloLink();
420         assertEquals(WifiScanner.WIFI_BAND_UNSPECIFIED, mloLink.getBand());
421         assertEquals(0, mloLink.getChannel());
422         assertEquals(MloLink.INVALID_MLO_LINK_ID, mloLink.getLinkId());
423         assertNull(mloLink.getStaMacAddress());
424         assertNull(mloLink.getApMacAddress());
425         assertEquals(MloLink.MLO_LINK_STATE_UNASSOCIATED, mloLink.getState());
426         assertEquals(WifiInfo.INVALID_RSSI, mloLink.getRssi());
427         assertEquals(WifiInfo.LINK_SPEED_UNKNOWN, mloLink.getRxLinkSpeedMbps());
428         assertEquals(WifiInfo.LINK_SPEED_UNKNOWN, mloLink.getTxLinkSpeedMbps());
429     }
430 
431     /**
432      * Test MLO Link parcelable APIs
433      */
434     @Test
testMloLinkParcelable()435     public void testMloLinkParcelable() {
436         Parcel p = Parcel.obtain();
437         MloLink mloLink = new MloLink();
438 
439         mloLink.writeToParcel(p, 0);
440         p.setDataPosition(0);
441         MloLink newMloLink = MloLink.CREATOR.createFromParcel(p);
442         assertEquals(mloLink, newMloLink);
443         assertEquals("hashCode() did not get right hashCode",
444                 mloLink.hashCode(), newMloLink.hashCode());
445     }
446 }
447