1 /* 2 * Copyright (C) 2020 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.cts.statsdatom.wifi; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import android.cts.statsdatom.lib.AtomTestUtils; 23 import android.cts.statsdatom.lib.ConfigUtils; 24 import android.cts.statsdatom.lib.DeviceUtils; 25 import android.cts.statsdatom.lib.ReportUtils; 26 import android.net.wifi.WifiModeEnum; 27 28 import com.android.compatibility.common.util.ApiLevelUtil; 29 import com.android.os.AtomsProto; 30 import com.android.os.StatsLog; 31 import com.android.tradefed.build.IBuildInfo; 32 import com.android.tradefed.testtype.DeviceTestCase; 33 import com.android.tradefed.testtype.IBuildReceiver; 34 import com.android.tradefed.util.RunUtil; 35 36 import com.google.common.collect.Range; 37 import com.google.protobuf.AbstractMessage; 38 39 import java.util.Arrays; 40 import java.util.Collections; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Set; 44 import java.util.function.Predicate; 45 import java.util.stream.Collectors; 46 47 public class WifiStatsTests extends DeviceTestCase implements IBuildReceiver { 48 private IBuildInfo mCtsBuild; 49 50 private static final int WIFI_CONNECT_TIMEOUT_MILLIS = 30_000; 51 private static final String FEATURE_PC = "android.hardware.type.pc"; 52 private static final String FEATURE_WIFI = "android.hardware.wifi"; 53 private static final String FEATURE_NAMESPACE = "wifi"; 54 private String mDefaultValue; 55 56 @Override setUp()57 protected void setUp() throws Exception { 58 super.setUp(); 59 assertThat(mCtsBuild).isNotNull(); 60 ConfigUtils.removeConfig(getDevice()); 61 ReportUtils.clearReports(getDevice()); 62 DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild); 63 saveFeature("high_perf_lock_deprecated"); 64 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 65 } 66 67 @Override tearDown()68 protected void tearDown() throws Exception { 69 restoreFeature("high_perf_lock_deprecated"); 70 ConfigUtils.removeConfig(getDevice()); 71 ReportUtils.clearReports(getDevice()); 72 DeviceUtils.uninstallStatsdTestApp(getDevice()); 73 super.tearDown(); 74 } 75 76 @Override setBuild(IBuildInfo buildInfo)77 public void setBuild(IBuildInfo buildInfo) { 78 mCtsBuild = buildInfo; 79 } 80 saveFeature(final String feature)81 private void saveFeature(final String feature) throws Exception { 82 mDefaultValue = DeviceUtils.getDeviceConfigFeature(getDevice(), FEATURE_NAMESPACE, 83 feature); 84 } 85 setFeature(final String feature, final String enable)86 private void setFeature(final String feature, final String enable) throws Exception { 87 DeviceUtils.putDeviceConfigFeature(getDevice(), FEATURE_NAMESPACE, feature, enable); 88 } 89 restoreFeature(String feature)90 private void restoreFeature(String feature) throws Exception { 91 if (mDefaultValue == null || mDefaultValue.equals("null")) { 92 DeviceUtils.deleteDeviceConfigFeature(getDevice(), FEATURE_NAMESPACE, feature); 93 } else { 94 DeviceUtils.putDeviceConfigFeature(getDevice(), FEATURE_NAMESPACE, feature, 95 mDefaultValue); 96 } 97 } 98 /** 99 * Test High Perf lock with the wifi:high_perf_lock_deprecated = false. 100 */ testWifiLockHighPerf()101 public void testWifiLockHighPerf() throws Exception { 102 if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return; 103 if (DeviceUtils.hasFeature(getDevice(), FEATURE_PC)) return; 104 105 setFeature("high_perf_lock_deprecated", "false"); 106 ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 107 AtomsProto.Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER, true); 108 DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiLockHighPerf"); 109 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 110 111 // Sorted list of events in order in which they occurred. 112 List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 113 114 Set<Integer> lockOn = new HashSet<>( 115 Collections.singletonList(AtomsProto.WifiLockStateChanged.State.ON_VALUE)); 116 Set<Integer> lockOff = new HashSet<>( 117 Collections.singletonList(AtomsProto.WifiLockStateChanged.State.OFF_VALUE)); 118 119 // Add state sets to the list in order. 120 List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff); 121 122 // Assert that the events happened in the expected order. 123 AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT, 124 atom -> atom.getWifiLockStateChanged().getState().getNumber()); 125 126 for (StatsLog.EventMetricData event : data) { 127 assertThat(event.getAtom().getWifiLockStateChanged().getMode()) 128 .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_HIGH_PERF); 129 } 130 } 131 132 /** 133 * Test High Perf lock with the wifi:high_perf_lock_deprecated = true. 134 */ testWifiLockHighPerfDeprecated()135 public void testWifiLockHighPerfDeprecated() throws Exception { 136 if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return; 137 if (DeviceUtils.hasFeature(getDevice(), FEATURE_PC)) return; 138 139 setFeature("high_perf_lock_deprecated", "true"); 140 ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 141 AtomsProto.Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER, true); 142 143 // High perf lock is deprecated from Android U onwards. Acquisition of High perf lock will 144 // be treated as a call to Low Latency Lock. 145 // 146 // For low latency lock to be active following conditions need to be met, 147 // - Wi-Fi is connected 148 // - Screen On 149 // - Application is foreground. 150 151 // Check Wi-Fi is connected. 152 DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiConnected"); 153 154 // Turn screen on. 155 DeviceUtils.turnScreenOn(getDevice()); 156 157 // Acquire and release highperf lock in foreground activity. 158 DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 159 "StatsdCtsForegroundActivity", "action", "action.acquire_release_wifi_hiperf_lock"); 160 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 161 162 // Sorted list of events in order in which they occurred. 163 List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 164 165 Set<Integer> lockOn = new HashSet<>( 166 Collections.singletonList(AtomsProto.WifiLockStateChanged.State.ON_VALUE)); 167 Set<Integer> lockOff = new HashSet<>( 168 Collections.singletonList(AtomsProto.WifiLockStateChanged.State.OFF_VALUE)); 169 170 // Add state sets to the list in order. 171 List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff); 172 173 // Assert that the events happened in the expected order. 174 AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT, 175 atom -> atom.getWifiLockStateChanged().getState().getNumber()); 176 177 for (StatsLog.EventMetricData event : data) { 178 // High perf lock is deprecated from Android U onwards. Acquisition of high perf lock is 179 // treated as low latency lock. 180 assertThat(event.getAtom().getWifiLockStateChanged().getMode()) 181 .isEqualTo( 182 (ApiLevelUtil.isAfter(getDevice(), "TIRAMISU") 183 || ApiLevelUtil.codenameStartsWith(getDevice(), "U")) 184 ? WifiModeEnum.WIFI_MODE_FULL_LOW_LATENCY 185 : WifiModeEnum.WIFI_MODE_FULL_HIGH_PERF); 186 } 187 } 188 testWifiLockLowLatency()189 public void testWifiLockLowLatency() throws Exception { 190 if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return; 191 if (DeviceUtils.hasFeature(getDevice(), FEATURE_PC)) return; 192 193 ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 194 AtomsProto.Atom.WIFI_LOCK_STATE_CHANGED_FIELD_NUMBER, true); 195 196 // For low latency lock to be active following conditions need to be met, 197 // - Wi-Fi is connected 198 // - Screen On 199 // - Application is foreground. 200 201 // Check Wi-Fi is connected. 202 DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiConnected"); 203 204 // Turn screen on. 205 DeviceUtils.turnScreenOn(getDevice()); 206 207 // Acquire and release low latency lock in foreground activity. 208 DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 209 "StatsdCtsForegroundActivity", "action", "action.acquire_release_wifi_ll_lock"); 210 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 211 212 // Sorted list of events in order in which they occurred. 213 List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 214 215 Set<Integer> lockOn = new HashSet<>( 216 Collections.singletonList(AtomsProto.WifiLockStateChanged.State.ON_VALUE)); 217 Set<Integer> lockOff = new HashSet<>( 218 Collections.singletonList(AtomsProto.WifiLockStateChanged.State.OFF_VALUE)); 219 220 // Add state sets to the list in order. 221 List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff); 222 223 // Assert that the events happened in the expected order. 224 AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT, 225 atom -> atom.getWifiLockStateChanged().getState().getNumber()); 226 227 for (StatsLog.EventMetricData event : data) { 228 assertThat(event.getAtom().getWifiLockStateChanged().getMode()) 229 .isEqualTo(WifiModeEnum.WIFI_MODE_FULL_LOW_LATENCY); 230 } 231 } 232 testWifiMulticastLock()233 public void testWifiMulticastLock() throws Exception { 234 if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return; 235 if (DeviceUtils.hasFeature(getDevice(), FEATURE_PC)) return; 236 237 ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 238 AtomsProto.Atom.WIFI_MULTICAST_LOCK_STATE_CHANGED_FIELD_NUMBER, true); 239 DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiMulticastLock"); 240 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 241 242 // Sorted list of events in order in which they occurred. 243 List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 244 245 Set<Integer> lockOn = new HashSet<>( 246 Collections.singletonList(AtomsProto.WifiMulticastLockStateChanged.State.ON_VALUE)); 247 Set<Integer> lockOff = new HashSet<>( 248 Collections.singletonList( 249 AtomsProto.WifiMulticastLockStateChanged.State.OFF_VALUE)); 250 251 final String EXPECTED_TAG = "StatsdCTSMulticastLock"; 252 253 // Add state sets to the list in order. 254 List<Set<Integer>> stateSet = Arrays.asList(lockOn, lockOff); 255 256 // Assert that the events happened in the expected order. 257 AtomTestUtils.assertStatesOccurredInOrder(stateSet, data, AtomTestUtils.WAIT_TIME_SHORT, 258 atom -> atom.getWifiMulticastLockStateChanged().getState().getNumber()); 259 260 for (StatsLog.EventMetricData event : data) { 261 String tag = event.getAtom().getWifiMulticastLockStateChanged().getTag(); 262 assertThat(tag).isEqualTo(EXPECTED_TAG); 263 } 264 } 265 testWifiReconnect()266 public void testWifiReconnect() throws Exception { 267 if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return; 268 269 ConfigUtils.uploadConfigForPushedAtoms(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 270 new int[] { 271 AtomsProto.Atom.WIFI_CONNECTION_RESULT_REPORTED_FIELD_NUMBER, 272 AtomsProto.Atom.WIFI_DISCONNECT_REPORTED_FIELD_NUMBER 273 }); 274 275 // This test on device checks if device is connected, and connects it if it is not; 276 // Afterwards, it disconnects from that network and connects back to it. 277 DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiReconnect"); 278 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 279 280 List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 281 282 // If device had Wifi connected, we'll see two atoms: disconnect, connect. 283 // If it was not connected, we'll see three: connect, disconnect, connect. 284 // We're only interested in the disconnect-connect pair. 285 assertWithMessage( 286 "Expected disconnected and connected atoms, got: \n" + 287 data.stream().map(AbstractMessage::toString).reduce((acc, i) -> acc + i) 288 ).that(data.size()).isIn(Range.closed(2, 3)); 289 290 AtomsProto.WifiDisconnectReported a0 = 291 data.get(data.size() - 2).getAtom().getWifiDisconnectReported(); 292 AtomsProto.WifiConnectionResultReported a1 = 293 data.get(data.size() - 1).getAtom().getWifiConnectionResultReported(); 294 295 assertThat(a0).isNotNull(); 296 assertThat(a1).isNotNull(); 297 298 assertThat(a0.getConnectedDurationSeconds()).isGreaterThan(0); 299 int maxLinkSpeedMbps = 1_000_000; /* 640K ought to be enough for anybody. */ 300 assertThat(a0.getLastLinkSpeed()).isIn(Range.open(0, maxLinkSpeedMbps)); 301 assertThat(a0.getLastRssi()).isIn(Range.closed(-127, 0)); 302 303 assertThat(a1.getConnectionResult()).isTrue(); 304 assertThat(a1.getRssi()).isIn(Range.closed(-127, 0)); 305 assertThat(a1.getConnectionAttemptDurationMillis()).isIn( 306 Range.open(0, WIFI_CONNECT_TIMEOUT_MILLIS)); 307 assertThat(a1.getTrigger()).isEqualTo( 308 AtomsProto.WifiConnectionResultReported.Trigger.RECONNECT_SAME_NETWORK); 309 assertThat(a1.getNetworkUsed()).isTrue(); 310 } 311 testWifiScanLogsScanAtoms()312 public void testWifiScanLogsScanAtoms() throws Exception { 313 if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return; 314 315 ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 316 AtomsProto.Atom.WIFI_SCAN_REPORTED_FIELD_NUMBER); 317 DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiScan"); 318 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 319 320 List<StatsLog.EventMetricData> metricData = ReportUtils.getEventMetricDataList(getDevice()); 321 List<AtomsProto.WifiScanReported> wifiScanAtoms = metricData.stream() 322 .map(eventLog -> eventLog.getAtom().getWifiScanReported()) 323 // Disregard interfering scans from other sources. 324 // If this test is run on a device that has a Settings app open that 325 // continuously performs frequent scans, quite often our scans requests 326 // are bundled together and get attributed to the Settings app. 327 .filter(scan -> List.of( 328 AtomsProto.WifiScanReported.Source.SOURCE_OTHER_APP, 329 AtomsProto.WifiScanReported.Source.SOURCE_SETTINGS_APP) 330 .contains(scan.getSource())) 331 .filter(Predicate.not(scan -> scan.getImportance().equals( 332 AtomsProto.WifiScanReported.Importance.IMPORTANCE_UNKNOWN))) 333 .collect(Collectors.toList()); 334 assertThat(wifiScanAtoms).isNotEmpty(); 335 336 for (AtomsProto.WifiScanReported scan : wifiScanAtoms) { 337 assertThat(scan.getResult()).isEqualTo( 338 AtomsProto.WifiScanReported.Result.RESULT_SUCCESS); 339 assertThat(scan.getType()).isEqualTo(AtomsProto.WifiScanReported.Type.TYPE_SINGLE); 340 List<AtomsProto.WifiScanReported.Importance> expectedResult = 341 Arrays.asList( 342 AtomsProto.WifiScanReported.Importance.IMPORTANCE_FOREGROUND_SERVICE, 343 AtomsProto.WifiScanReported.Importance.IMPORTANCE_FOREGROUND); 344 assertThat(scan.getImportance()).isIn(expectedResult); 345 assertThat(scan.getScanDurationMillis()).isGreaterThan(0); 346 } 347 } 348 testWifiScanLogsStateChangedAtoms()349 public void testWifiScanLogsStateChangedAtoms() throws Exception { 350 if (!DeviceUtils.hasFeature(getDevice(), FEATURE_WIFI)) return; 351 352 353 ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG, 354 AtomsProto.Atom.WIFI_SCAN_STATE_CHANGED_FIELD_NUMBER, true); 355 DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(), ".AtomTests", "testWifiScan"); 356 RunUtil.getDefault().sleep(AtomTestUtils.WAIT_TIME_LONG); 357 358 final int stateOn = AtomsProto.WifiScanStateChanged.State.ON_VALUE; 359 final int stateOff = AtomsProto.WifiScanStateChanged.State.OFF_VALUE; 360 final int minTimeDiffMillis = 250; 361 final int maxTimeDiffMillis = 60_000; 362 363 List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice()); 364 assertThat(data.size()).isIn(Range.closed(2, 4)); 365 AtomTestUtils.assertTimeDiffBetween(data.get(0), data.get(1), minTimeDiffMillis, 366 maxTimeDiffMillis); 367 AtomsProto.WifiScanStateChanged a0 = data.get(0).getAtom().getWifiScanStateChanged(); 368 AtomsProto.WifiScanStateChanged a1 = data.get(1).getAtom().getWifiScanStateChanged(); 369 assertThat(a0.getState().getNumber()).isEqualTo(stateOn); 370 assertThat(a1.getState().getNumber()).isEqualTo(stateOff); 371 } 372 } 373