1 /* 2 * Copyright (C) 2016 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 com.android.cts.net.hostside; 18 19 import static android.cts.util.SystemUtil.runShellCommand; 20 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED; 21 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED; 22 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; 23 import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED; 24 25 import java.util.concurrent.LinkedBlockingQueue; 26 import java.util.concurrent.TimeUnit; 27 28 import android.app.Instrumentation; 29 import android.content.BroadcastReceiver; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.net.ConnectivityManager; 33 import android.net.NetworkInfo; 34 import android.net.NetworkInfo.DetailedState; 35 import android.net.NetworkInfo.State; 36 import android.net.wifi.WifiManager; 37 import android.os.SystemClock; 38 import android.service.notification.NotificationListenerService; 39 import android.test.InstrumentationTestCase; 40 import android.util.Log; 41 42 /** 43 * Superclass for tests related to background network restrictions. 44 */ 45 abstract class AbstractRestrictBackgroundNetworkTestCase extends InstrumentationTestCase { 46 protected static final String TAG = "RestrictBackgroundNetworkTests"; 47 48 protected static final String TEST_PKG = "com.android.cts.net.hostside"; 49 protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2"; 50 51 private static final int SLEEP_TIME_SEC = 1; 52 private static final boolean DEBUG = true; 53 54 // Constants below must match values defined on app2's Common.java 55 private static final String MANIFEST_RECEIVER = "ManifestReceiver"; 56 private static final String DYNAMIC_RECEIVER = "DynamicReceiver"; 57 private static final String ACTION_GET_COUNTERS = 58 "com.android.cts.net.hostside.app2.action.GET_COUNTERS"; 59 private static final String ACTION_GET_RESTRICT_BACKGROUND_STATUS = 60 "com.android.cts.net.hostside.app2.action.GET_RESTRICT_BACKGROUND_STATUS"; 61 private static final String ACTION_CHECK_NETWORK = 62 "com.android.cts.net.hostside.app2.action.CHECK_NETWORK"; 63 private static final String ACTION_RECEIVER_READY = 64 "com.android.cts.net.hostside.app2.action.RECEIVER_READY"; 65 static final String ACTION_SEND_NOTIFICATION = 66 "com.android.cts.net.hostside.app2.action.SEND_NOTIFICATION"; 67 private static final String EXTRA_ACTION = "com.android.cts.net.hostside.app2.extra.ACTION"; 68 private static final String EXTRA_RECEIVER_NAME = 69 "com.android.cts.net.hostside.app2.extra.RECEIVER_NAME"; 70 private static final String EXTRA_NOTIFICATION_ID = 71 "com.android.cts.net.hostside.app2.extra.NOTIFICATION_ID"; 72 private static final String NETWORK_STATUS_SEPARATOR = "\\|"; 73 private static final int SECOND_IN_MS = 1000; 74 static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS; 75 private static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; 76 77 78 // Must be higher than NETWORK_TIMEOUT_MS 79 private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4; 80 81 protected Context mContext; 82 protected Instrumentation mInstrumentation; 83 protected ConnectivityManager mCm; 84 protected WifiManager mWfm; 85 protected int mUid; 86 private String mMeteredWifi; 87 88 @Override setUp()89 protected void setUp() throws Exception { 90 super.setUp(); 91 92 mInstrumentation = getInstrumentation(); 93 mContext = mInstrumentation.getContext(); 94 mCm = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); 95 mWfm = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); 96 mUid = getUid(TEST_APP2_PKG); 97 final int myUid = getUid(mContext.getPackageName()); 98 99 Log.i(TAG, "Apps status on " + getName() + ":\n" 100 + "\ttest app: uid=" + myUid + ", state=" + getProcessStateByUid(myUid) + "\n" 101 + "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid)); 102 } 103 getUid(String packageName)104 protected int getUid(String packageName) throws Exception { 105 return mContext.getPackageManager().getPackageUid(packageName, 0); 106 } 107 assertRestrictBackgroundChangedReceived(int expectedCount)108 protected void assertRestrictBackgroundChangedReceived(int expectedCount) throws Exception { 109 assertRestrictBackgroundChangedReceived(DYNAMIC_RECEIVER, expectedCount); 110 assertRestrictBackgroundChangedReceived(MANIFEST_RECEIVER, 0); 111 } 112 assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)113 protected void assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount) 114 throws Exception { 115 int attempts = 0; 116 int count = 0; 117 final int maxAttempts = 5; 118 do { 119 attempts++; 120 count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED); 121 if (count == expectedCount) { 122 break; 123 } 124 Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after " 125 + attempts + " attempts; sleeping " 126 + SLEEP_TIME_SEC + " seconds before trying again"); 127 SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS); 128 } while (attempts <= maxAttempts); 129 assertEquals("Number of expected broadcasts for " + receiverName + " not reached after " 130 + maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count); 131 } 132 sendOrderedBroadcast(Intent intent)133 protected String sendOrderedBroadcast(Intent intent) throws Exception { 134 return sendOrderedBroadcast(intent, ORDERED_BROADCAST_TIMEOUT_MS); 135 } 136 sendOrderedBroadcast(Intent intent, int timeoutMs)137 protected String sendOrderedBroadcast(Intent intent, int timeoutMs) throws Exception { 138 final LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1); 139 Log.d(TAG, "Sending ordered broadcast: " + intent); 140 mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() { 141 142 @Override 143 public void onReceive(Context context, Intent intent) { 144 final String resultData = getResultData(); 145 if (resultData == null) { 146 Log.e(TAG, "Received null data from ordered intent"); 147 return; 148 } 149 result.offer(resultData); 150 } 151 }, null, 0, null, null); 152 153 final String resultData = result.poll(timeoutMs, TimeUnit.MILLISECONDS); 154 Log.d(TAG, "Ordered broadcast response after " + timeoutMs + "ms: " + resultData ); 155 return resultData; 156 } 157 getNumberBroadcastsReceived(String receiverName, String action)158 protected int getNumberBroadcastsReceived(String receiverName, String action) throws Exception { 159 final Intent intent = new Intent(ACTION_GET_COUNTERS); 160 intent.putExtra(EXTRA_ACTION, ACTION_RESTRICT_BACKGROUND_CHANGED); 161 intent.putExtra(EXTRA_RECEIVER_NAME, receiverName); 162 final String resultData = sendOrderedBroadcast(intent); 163 assertNotNull("timeout waiting for ordered broadcast result", resultData); 164 return Integer.valueOf(resultData); 165 } 166 assertRestrictBackgroundStatus(int expectedStatus)167 protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception { 168 final Intent intent = new Intent(ACTION_GET_RESTRICT_BACKGROUND_STATUS); 169 final String resultData = sendOrderedBroadcast(intent); 170 assertNotNull("timeout waiting for ordered broadcast result", resultData); 171 final String actualStatus = toString(Integer.parseInt(resultData)); 172 assertEquals("wrong status", toString(expectedStatus), actualStatus); 173 } 174 assertBackgroundNetworkAccess(boolean expectAllowed)175 protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception { 176 assertBackgroundState(); // Sanity check. 177 assertNetworkAccess(expectAllowed); 178 } 179 assertForegroundNetworkAccess()180 protected void assertForegroundNetworkAccess() throws Exception { 181 assertForegroundState(); // Sanity check. 182 assertNetworkAccess(true); 183 } 184 assertForegroundServiceNetworkAccess()185 protected void assertForegroundServiceNetworkAccess() throws Exception { 186 assertForegroundServiceState(); // Sanity check. 187 assertNetworkAccess(true); 188 } 189 190 /** 191 * Whether this device suport this type of test. 192 * 193 * <p>Should be overridden when necessary, and explicitly used before each test. Example: 194 * 195 * <pre><code> 196 * public void testSomething() { 197 * if (!isSupported()) return; 198 * </code></pre> 199 * 200 * @return {@code true} by default. 201 */ isSupported()202 protected boolean isSupported() throws Exception { 203 return true; 204 } 205 206 /** 207 * Asserts that an app always have access while on foreground or running a foreground service. 208 * 209 * <p>This method will launch an activity and a foreground service to make the assertion, but 210 * will finish the activity / stop the service afterwards. 211 */ assertsForegroundAlwaysHasNetworkAccess()212 protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{ 213 // Checks foreground first. 214 launchActivity(); 215 assertForegroundNetworkAccess(); 216 finishActivity(); 217 218 // Then foreground service 219 startForegroundService(); 220 assertForegroundServiceNetworkAccess(); 221 stopForegroundService(); 222 } 223 assertBackgroundState()224 protected final void assertBackgroundState() throws Exception { 225 final int maxTries = 30; 226 ProcessState state = null; 227 for (int i = 1; i <= maxTries; i++) { 228 state = getProcessStateByUid(mUid); 229 Log.v(TAG, "assertBackgroundState(): status for app2 (" + mUid + ") on attempt #" + i 230 + ": " + state); 231 if (isBackground(state.state)) { 232 return; 233 } 234 Log.d(TAG, "App not on background state on attempt #" + i 235 + "; sleeping 1s before trying again"); 236 SystemClock.sleep(SECOND_IN_MS); 237 } 238 fail("App2 is not on background state after " + maxTries + " attempts: " + state ); 239 } 240 assertForegroundState()241 protected final void assertForegroundState() throws Exception { 242 final int maxTries = 30; 243 ProcessState state = null; 244 for (int i = 1; i <= maxTries; i++) { 245 state = getProcessStateByUid(mUid); 246 Log.v(TAG, "assertForegroundState(): status for app2 (" + mUid + ") on attempt #" + i 247 + ": " + state); 248 if (!isBackground(state.state)) { 249 return; 250 } 251 Log.d(TAG, "App not on foreground state on attempt #" + i 252 + "; sleeping 1s before trying again"); 253 SystemClock.sleep(SECOND_IN_MS); 254 } 255 fail("App2 is not on foreground state after " + maxTries + " attempts: " + state ); 256 } 257 assertForegroundServiceState()258 protected final void assertForegroundServiceState() throws Exception { 259 final int maxTries = 30; 260 ProcessState state = null; 261 for (int i = 1; i <= maxTries; i++) { 262 state = getProcessStateByUid(mUid); 263 Log.v(TAG, "assertForegroundServiceState(): status for app2 (" + mUid + ") on attempt #" 264 + i + ": " + state); 265 if (state.state == PROCESS_STATE_FOREGROUND_SERVICE) { 266 return; 267 } 268 Log.d(TAG, "App not on foreground service state on attempt #" + i 269 + "; sleeping 1s before trying again"); 270 SystemClock.sleep(SECOND_IN_MS); 271 } 272 fail("App2 is not on foreground service state after " + maxTries + " attempts: " + state ); 273 } 274 275 /** 276 * Returns whether an app state should be considered "background" for restriction purposes. 277 */ isBackground(int state)278 protected boolean isBackground(int state) { 279 return state > PROCESS_STATE_FOREGROUND_SERVICE; 280 } 281 282 /** 283 * Asserts whether the active network is available or not. 284 */ assertNetworkAccess(boolean expectAvailable)285 private void assertNetworkAccess(boolean expectAvailable) throws Exception { 286 final Intent intent = new Intent(ACTION_CHECK_NETWORK); 287 288 final int maxTries = 5; 289 String resultData = null; 290 for (int i = 1; i <= maxTries; i++) { 291 resultData = sendOrderedBroadcast(intent); 292 assertNotNull("timeout waiting for ordered broadcast", resultData); 293 294 // Network status format is described on MyBroadcastReceiver.checkNetworkStatus() 295 final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR); 296 assertEquals("Wrong network status: " + resultData, 5, parts.length); // Sanity check 297 final State state = State.valueOf(parts[0]); 298 final DetailedState detailedState = DetailedState.valueOf(parts[1]); 299 final boolean connected = Boolean.valueOf(parts[2]); 300 final String connectionCheckDetails = parts[3]; 301 final String networkInfo = parts[4]; 302 303 if (expectAvailable) { 304 if (!connected) { 305 // Since it's establishing a connection to an external site, it could be flaky. 306 Log.w(TAG, "Failed to connect to an external site on attempt #" + i + 307 " (error: " + connectionCheckDetails + ", NetworkInfo: " + networkInfo 308 + "); sleeping " + NETWORK_TIMEOUT_MS + "ms before trying again"); 309 SystemClock.sleep(NETWORK_TIMEOUT_MS); 310 continue; 311 } 312 if (state != State.CONNECTED) { 313 Log.d(TAG, "State (" + state + ") not set to CONNECTED on attempt #" + i 314 + "; sleeping 1s before trying again"); 315 SystemClock.sleep(SECOND_IN_MS); 316 } else { 317 assertEquals("wrong detailed state for " + networkInfo, 318 DetailedState.CONNECTED, detailedState); 319 return; 320 } 321 return; 322 } else { 323 assertFalse("should not be connected: " + connectionCheckDetails 324 + " (network info: " + networkInfo + ")", connected); 325 if (state != State.DISCONNECTED) { 326 // When the network info state change, it's possible the app still get the 327 // previous value, so we need to retry a couple times. 328 Log.d(TAG, "State (" + state + ") not set to DISCONNECTED on attempt #" + i 329 + "; sleeping 1s before trying again"); 330 SystemClock.sleep(SECOND_IN_MS); 331 } else { 332 assertEquals("wrong detailed state for " + networkInfo, 333 DetailedState.BLOCKED, detailedState); 334 return; 335 } 336 } 337 } 338 fail("Invalid state for expectAvailable=" + expectAvailable + " after " + maxTries 339 + " attempts. Last data: " + resultData); 340 } 341 executeShellCommand(String command)342 protected String executeShellCommand(String command) throws Exception { 343 final String result = runShellCommand(mInstrumentation, command).trim(); 344 if (DEBUG) Log.d(TAG, "Command '" + command + "' returned '" + result + "'"); 345 return result; 346 } 347 348 /** 349 * Runs a Shell command which is not expected to generate output. 350 */ executeSilentShellCommand(String command)351 protected void executeSilentShellCommand(String command) throws Exception { 352 final String result = executeShellCommand(command); 353 assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty()); 354 } 355 356 /** 357 * Asserts the result of a command, wait and re-running it a couple times if necessary. 358 */ assertDelayedShellCommand(String command, final String expectedResult)359 protected void assertDelayedShellCommand(String command, final String expectedResult) 360 throws Exception { 361 assertDelayedShellCommand(command, 5, 1, expectedResult); 362 } 363 assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds, final String expectedResult)364 protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds, 365 final String expectedResult) throws Exception { 366 assertDelayedShellCommand(command, maxTries, napTimeSeconds, new ExpectResultChecker() { 367 368 @Override 369 public boolean isExpected(String result) { 370 return expectedResult.equals(result); 371 } 372 373 @Override 374 public String getExpected() { 375 return expectedResult; 376 } 377 }); 378 } 379 assertDelayedShellCommand(String command, ExpectResultChecker checker)380 protected void assertDelayedShellCommand(String command, ExpectResultChecker checker) 381 throws Exception { 382 assertDelayedShellCommand(command, 5, 1, checker); 383 } assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds, ExpectResultChecker checker)384 protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds, 385 ExpectResultChecker checker) throws Exception { 386 String result = ""; 387 for (int i = 1; i <= maxTries; i++) { 388 result = executeShellCommand(command).trim(); 389 if (checker.isExpected(result)) return; 390 Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '" 391 + checker.getExpected() + "' on attempt #" + i 392 + "; sleeping " + napTimeSeconds + "s before trying again"); 393 SystemClock.sleep(napTimeSeconds * SECOND_IN_MS); 394 } 395 fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after " 396 + maxTries 397 + " attempts. Last result: '" + result + "'"); 398 } 399 400 /** 401 * Puts the device in a state where the active network is metered, or fail if it can't achieve 402 * that state. 403 */ setMeteredNetwork()404 protected void setMeteredNetwork() throws Exception { 405 final NetworkInfo info = mCm.getActiveNetworkInfo(); 406 final boolean metered = mCm.isActiveNetworkMetered(); 407 if (metered) { 408 Log.d(TAG, "Active network already metered: " + info); 409 return; 410 } else { 411 Log.w(TAG, "Active network not metered: " + info); 412 } 413 final String netId = setWifiMeteredStatus(true); 414 415 // Set flag so status is reverted on resetMeteredNetwork(); 416 mMeteredWifi = netId; 417 // Sanity check. 418 assertWifiMeteredStatus(netId, true); 419 assertActiveNetworkMetered(true); 420 } 421 422 /** 423 * Puts the device in a state where the active network is not metered, or fail if it can't 424 * achieve that state. 425 * <p>It assumes the device has a valid WI-FI connection. 426 */ resetMeteredNetwork()427 protected void resetMeteredNetwork() throws Exception { 428 if (mMeteredWifi != null) { 429 Log.i(TAG, "resetMeteredNetwork(): SID '" + mMeteredWifi 430 + "' was set as metered by test case; resetting it"); 431 setWifiMeteredStatus(mMeteredWifi, false); 432 } else { 433 final NetworkInfo info = mCm.getActiveNetworkInfo(); 434 assertNotNull("Could not get active network", info); 435 if (!mCm.isActiveNetworkMetered()) { 436 Log.d(TAG, "Active network is not metered: " + info); 437 } else if (info.getType() == ConnectivityManager.TYPE_WIFI) { 438 Log.i(TAG, "Setting active WI-FI network as metered: " + info ); 439 setWifiMeteredStatus(false); 440 } else { 441 fail("Active network is not WI-FI hence cannot be set as non-metered: " + info); 442 } 443 } 444 assertActiveNetworkMetered(false); // Sanity check. 445 } 446 assertActiveNetworkMetered(boolean expected)447 private void assertActiveNetworkMetered(boolean expected) throws Exception { 448 final int maxTries = 5; 449 NetworkInfo info = null; 450 for (int i = 1; i <= maxTries; i++) { 451 info = mCm.getActiveNetworkInfo(); 452 if (info != null) { 453 break; 454 } 455 Log.v(TAG, "No active network info on attempt #" + i 456 + "; sleeping 1s before polling again"); 457 Thread.sleep(SECOND_IN_MS); 458 } 459 assertNotNull("No active network after " + maxTries + " attempts", info); 460 assertEquals("Wrong metered status for active network " + info, expected, 461 mCm.isActiveNetworkMetered()); 462 } 463 setWifiMeteredStatus(boolean metered)464 private String setWifiMeteredStatus(boolean metered) throws Exception { 465 // We could call setWifiEnabled() here, but it might take sometime to be in a consistent 466 // state (for example, if one of the saved network is not properly authenticated), so it's 467 // better to let the hostside test take care of that. 468 assertTrue("wi-fi is disabled", mWfm.isWifiEnabled()); 469 // TODO: if it's not guaranteed the device has wi-fi, we need to change the tests 470 // to make the actual verification of restrictions optional. 471 final String ssid = mWfm.getConnectionInfo().getSSID(); 472 return setWifiMeteredStatus(ssid, metered); 473 } 474 setWifiMeteredStatus(String ssid, boolean metered)475 private String setWifiMeteredStatus(String ssid, boolean metered) throws Exception { 476 assertNotNull("null SSID", ssid); 477 final String netId = ssid.trim().replaceAll("\"", ""); // remove quotes, if any. 478 assertFalse("empty SSID", ssid.isEmpty()); 479 480 Log.i(TAG, "Setting wi-fi network " + netId + " metered status to " + metered); 481 final String setCommand = "cmd netpolicy set metered-network " + netId + " " + metered; 482 assertDelayedShellCommand(setCommand, ""); 483 484 return netId; 485 } 486 assertWifiMeteredStatus(String netId, boolean status)487 private void assertWifiMeteredStatus(String netId, boolean status) throws Exception { 488 final String command = "cmd netpolicy list wifi-networks"; 489 final String expectedLine = netId + ";" + status; 490 assertDelayedShellCommand(command, new ExpectResultChecker() { 491 492 @Override 493 public boolean isExpected(String result) { 494 return result.contains(expectedLine); 495 } 496 497 @Override 498 public String getExpected() { 499 return "line containing " + expectedLine; 500 } 501 }); 502 } 503 setRestrictBackground(boolean enabled)504 protected void setRestrictBackground(boolean enabled) throws Exception { 505 executeShellCommand("cmd netpolicy set restrict-background " + enabled); 506 final String output = executeShellCommand("cmd netpolicy get restrict-background "); 507 final String expectedSuffix = enabled ? "enabled" : "disabled"; 508 // TODO: use MoreAsserts? 509 assertTrue("output '" + output + "' should end with '" + expectedSuffix + "'", 510 output.endsWith(expectedSuffix)); 511 } 512 addRestrictBackgroundWhitelist(int uid)513 protected void addRestrictBackgroundWhitelist(int uid) throws Exception { 514 executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid); 515 assertRestrictBackgroundWhitelist(uid, true); 516 } 517 removeRestrictBackgroundWhitelist(int uid)518 protected void removeRestrictBackgroundWhitelist(int uid) throws Exception { 519 executeShellCommand("cmd netpolicy remove restrict-background-whitelist " + uid); 520 assertRestrictBackgroundWhitelist(uid, false); 521 } 522 assertRestrictBackgroundWhitelist(int uid, boolean expected)523 protected void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception { 524 assertRestrictBackground("restrict-background-whitelist", uid, expected); 525 } 526 addRestrictBackgroundBlacklist(int uid)527 protected void addRestrictBackgroundBlacklist(int uid) throws Exception { 528 executeShellCommand("cmd netpolicy add restrict-background-blacklist " + uid); 529 assertRestrictBackgroundBlacklist(uid, true); 530 } 531 removeRestrictBackgroundBlacklist(int uid)532 protected void removeRestrictBackgroundBlacklist(int uid) throws Exception { 533 executeShellCommand("cmd netpolicy remove restrict-background-blacklist " + uid); 534 assertRestrictBackgroundBlacklist(uid, false); 535 } 536 assertRestrictBackgroundBlacklist(int uid, boolean expected)537 protected void assertRestrictBackgroundBlacklist(int uid, boolean expected) throws Exception { 538 assertRestrictBackground("restrict-background-blacklist", uid, expected); 539 } 540 assertRestrictBackground(String list, int uid, boolean expected)541 private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception { 542 final int maxTries = 5; 543 boolean actual = false; 544 final String expectedUid = Integer.toString(uid); 545 String uids = ""; 546 for (int i = 1; i <= maxTries; i++) { 547 final String output = 548 executeShellCommand("cmd netpolicy list " + list); 549 uids = output.split(":")[1]; 550 for (String candidate : uids.split(" ")) { 551 actual = candidate.trim().equals(expectedUid); 552 if (expected == actual) { 553 return; 554 } 555 } 556 Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected " 557 + expected + ", got " + actual + "); sleeping 1s before polling again"); 558 SystemClock.sleep(SECOND_IN_MS); 559 } 560 fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual 561 + ". Full list: " + uids); 562 } 563 assertPowerSaveModeWhitelist(String packageName, boolean expected)564 protected void assertPowerSaveModeWhitelist(String packageName, boolean expected) 565 throws Exception { 566 // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll 567 // need to use netpolicy for whitelisting 568 assertDelayedShellCommand("dumpsys deviceidle whitelist =" + packageName, 569 Boolean.toString(expected)); 570 } 571 addPowerSaveModeWhitelist(String packageName)572 protected void addPowerSaveModeWhitelist(String packageName) throws Exception { 573 Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist"); 574 // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll 575 // need to use netpolicy for whitelisting 576 executeShellCommand("dumpsys deviceidle whitelist +" + packageName); 577 assertPowerSaveModeWhitelist(packageName, true); // Sanity check 578 } 579 removePowerSaveModeWhitelist(String packageName)580 protected void removePowerSaveModeWhitelist(String packageName) throws Exception { 581 Log.i(TAG, "Removing package " + packageName + " from power-save-mode whitelist"); 582 // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll 583 // need to use netpolicy for whitelisting 584 executeShellCommand("dumpsys deviceidle whitelist -" + packageName); 585 assertPowerSaveModeWhitelist(packageName, false); // Sanity check 586 } 587 turnBatteryOff()588 protected void turnBatteryOff() throws Exception { 589 executeSilentShellCommand("cmd battery unplug"); 590 } 591 turnBatteryOn()592 protected void turnBatteryOn() throws Exception { 593 executeSilentShellCommand("cmd battery reset"); 594 } 595 turnScreenOff()596 protected void turnScreenOff() throws Exception { 597 executeSilentShellCommand("input keyevent KEYCODE_SLEEP"); 598 } 599 turnScreenOn()600 protected void turnScreenOn() throws Exception { 601 executeSilentShellCommand("input keyevent KEYCODE_WAKEUP"); 602 executeSilentShellCommand("wm dismiss-keyguard"); 603 } 604 setBatterySaverMode(boolean enabled)605 protected void setBatterySaverMode(boolean enabled) throws Exception { 606 Log.i(TAG, "Setting Battery Saver Mode to " + enabled); 607 if (enabled) { 608 turnBatteryOff(); 609 executeSilentShellCommand("cmd battery unplug"); 610 executeSilentShellCommand("settings put global low_power 1"); 611 } else { 612 turnBatteryOn(); 613 } 614 } 615 setDozeMode(boolean enabled)616 protected void setDozeMode(boolean enabled) throws Exception { 617 // Sanity check, since tests should check beforehand.... 618 assertTrue("Device does not support Doze Mode", isDozeModeEnabled()); 619 620 Log.i(TAG, "Setting Doze Mode to " + enabled); 621 if (enabled) { 622 turnBatteryOff(); 623 turnScreenOff(); 624 executeShellCommand("dumpsys deviceidle force-idle deep"); 625 } else { 626 turnScreenOn(); 627 turnBatteryOn(); 628 executeShellCommand("dumpsys deviceidle unforce"); 629 } 630 // Sanity check. 631 assertDozeMode(enabled); 632 } 633 assertDozeMode(boolean enabled)634 protected void assertDozeMode(boolean enabled) throws Exception { 635 assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE"); 636 } 637 isDozeModeEnabled()638 protected boolean isDozeModeEnabled() throws Exception { 639 final String result = executeShellCommand("cmd deviceidle enabled deep").trim(); 640 return result.equals("1"); 641 } 642 setAppIdle(boolean enabled)643 protected void setAppIdle(boolean enabled) throws Exception { 644 Log.i(TAG, "Setting app idle to " + enabled); 645 executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled ); 646 assertAppIdle(enabled); // Sanity check 647 } 648 assertAppIdle(boolean enabled)649 protected void assertAppIdle(boolean enabled) throws Exception { 650 assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG, 10, 2, "Idle=" + enabled); 651 } 652 653 /** 654 * Starts a service that will register a broadcast receiver to receive 655 * {@code RESTRICT_BACKGROUND_CHANGE} intents. 656 * <p> 657 * The service must run in a separate app because otherwise it would be killed every time 658 * {@link #runDeviceTests(String, String)} is executed. 659 */ registerBroadcastReceiver()660 protected void registerBroadcastReceiver() throws Exception { 661 executeShellCommand("am startservice com.android.cts.net.hostside.app2/.MyService"); 662 // Wait until receiver is ready. 663 final int maxTries = 5; 664 for (int i = 1; i <= maxTries; i++) { 665 final String message = 666 sendOrderedBroadcast(new Intent(ACTION_RECEIVER_READY), SECOND_IN_MS); 667 Log.d(TAG, "app2 receiver acked: " + message); 668 if (message != null) { 669 return; 670 } 671 Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again"); 672 SystemClock.sleep(SECOND_IN_MS); 673 } 674 fail("app2 receiver is not ready"); 675 } 676 677 /** 678 * Registers a {@link NotificationListenerService} implementation that will execute the 679 * notification actions right after the notification is sent. 680 */ registerNotificationListenerService()681 protected void registerNotificationListenerService() throws Exception { 682 final StringBuilder listeners = new StringBuilder(getNotificationListenerServices()); 683 if (listeners.length() > 0) { 684 listeners.append(":"); 685 } 686 listeners.append(MyNotificationListenerService.getId()); 687 executeShellCommand("settings put secure enabled_notification_listeners " + listeners); 688 final String newListeners = getNotificationListenerServices(); 689 assertEquals("Failed to set 'enabled_notification_listeners'", 690 listeners.toString(), newListeners); 691 } 692 getNotificationListenerServices()693 private String getNotificationListenerServices() throws Exception { 694 return executeShellCommand("settings get secure enabled_notification_listeners"); 695 } 696 setPendingIntentWhitelistDuration(int durationMs)697 protected void setPendingIntentWhitelistDuration(int durationMs) throws Exception { 698 final String command = String.format( 699 "settings put global device_idle_constants %s=%d", 700 "notification_whitelist_duration", durationMs); 701 executeSilentShellCommand(command); 702 } 703 resetDeviceIdleSettings()704 protected void resetDeviceIdleSettings() throws Exception { 705 executeShellCommand("settings delete global device_idle_constants"); 706 } 707 startForegroundService()708 protected void startForegroundService() throws Exception { 709 executeShellCommand( 710 "am startservice -f 1 com.android.cts.net.hostside.app2/.MyForegroundService"); 711 assertForegroundServiceState(); 712 } 713 stopForegroundService()714 protected void stopForegroundService() throws Exception { 715 executeShellCommand( 716 "am startservice -f 2 com.android.cts.net.hostside.app2/.MyForegroundService"); 717 // NOTE: cannot assert state because it depends on whether activity was on top before. 718 } 719 720 /** 721 * Launches an activity on app2 so its process is elevated to foreground status. 722 */ launchActivity()723 protected void launchActivity() throws Exception { 724 turnScreenOn(); 725 executeShellCommand("am start com.android.cts.net.hostside.app2/.MyActivity"); 726 assertForegroundState(); 727 } 728 729 /** 730 * Finishes an activity on app2 so its process is demoted fromforeground status. 731 */ finishActivity()732 protected void finishActivity() throws Exception { 733 executeShellCommand("am broadcast -a " 734 + " com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY " 735 + "--receiver-foreground --receiver-registered-only"); 736 } 737 sendNotification(int notificationId)738 protected void sendNotification(int notificationId) { 739 final Intent intent = new Intent(ACTION_SEND_NOTIFICATION); 740 intent.putExtra(EXTRA_NOTIFICATION_ID, notificationId); 741 Log.d(TAG, "Sending broadcast: " + intent); 742 mContext.sendBroadcast(intent); 743 } 744 toString(int status)745 private String toString(int status) { 746 switch (status) { 747 case RESTRICT_BACKGROUND_STATUS_DISABLED: 748 return "DISABLED"; 749 case RESTRICT_BACKGROUND_STATUS_WHITELISTED: 750 return "WHITELISTED"; 751 case RESTRICT_BACKGROUND_STATUS_ENABLED: 752 return "ENABLED"; 753 default: 754 return "UNKNOWN_STATUS_" + status; 755 } 756 } 757 getProcessStateByUid(int uid)758 private ProcessState getProcessStateByUid(int uid) throws Exception { 759 return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid)); 760 } 761 762 private static class ProcessState { 763 private final String fullState; 764 final int state; 765 ProcessState(String fullState)766 ProcessState(String fullState) { 767 this.fullState = fullState; 768 try { 769 this.state = Integer.parseInt(fullState.split(" ")[0]); 770 } catch (Exception e) { 771 throw new IllegalArgumentException("Could not parse " + fullState); 772 } 773 } 774 775 @Override toString()776 public String toString() { 777 return fullState; 778 } 779 } 780 781 /** 782 * Helper class used to assert the result of a Shell command. 783 */ 784 protected static interface ExpectResultChecker { 785 /** 786 * Checkes whether the result of the command matched the expectation. 787 */ isExpected(String result)788 boolean isExpected(String result); 789 /** 790 * Gets the expected result so it's displayed on log and failure messages. 791 */ getExpected()792 String getExpected(); 793 } 794 } 795