• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.job.JobScheduler.RESULT_SUCCESS;
20 import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
21 import static android.os.BatteryManager.BATTERY_PLUGGED_AC;
22 import static android.os.BatteryManager.BATTERY_PLUGGED_USB;
23 import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS;
24 
25 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.executeShellCommand;
26 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.forceRunJob;
27 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getConnectivityManager;
28 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getContext;
29 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.getInstrumentation;
30 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isAppStandbySupported;
31 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isBatterySaverSupported;
32 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.isDozeModeSupported;
33 import static com.android.cts.net.hostside.NetworkPolicyTestUtils.restrictBackgroundValueToString;
34 
35 import static org.junit.Assert.assertEquals;
36 import static org.junit.Assert.assertFalse;
37 import static org.junit.Assert.assertNotNull;
38 import static org.junit.Assert.assertTrue;
39 import static org.junit.Assert.fail;
40 
41 import android.app.ActivityManager;
42 import android.app.Instrumentation;
43 import android.app.NotificationManager;
44 import android.app.job.JobInfo;
45 import android.content.BroadcastReceiver;
46 import android.content.ComponentName;
47 import android.content.Context;
48 import android.content.Intent;
49 import android.content.IntentFilter;
50 import android.net.ConnectivityManager;
51 import android.net.NetworkInfo.DetailedState;
52 import android.net.NetworkInfo.State;
53 import android.net.NetworkRequest;
54 import android.os.BatteryManager;
55 import android.os.Binder;
56 import android.os.Bundle;
57 import android.os.PowerManager;
58 import android.os.RemoteCallback;
59 import android.os.SystemClock;
60 import android.provider.DeviceConfig;
61 import android.service.notification.NotificationListenerService;
62 import android.util.Log;
63 import android.util.Pair;
64 
65 import com.android.compatibility.common.util.AmUtils;
66 import com.android.compatibility.common.util.BatteryUtils;
67 import com.android.compatibility.common.util.DeviceConfigStateHelper;
68 
69 import org.junit.Rule;
70 import org.junit.rules.RuleChain;
71 import org.junit.runner.RunWith;
72 
73 import java.util.ArrayList;
74 import java.util.concurrent.CountDownLatch;
75 import java.util.concurrent.LinkedBlockingQueue;
76 import java.util.concurrent.TimeUnit;
77 
78 /**
79  * Superclass for tests related to background network restrictions.
80  */
81 @RunWith(NetworkPolicyTestRunner.class)
82 public abstract class AbstractRestrictBackgroundNetworkTestCase {
83     public static final String TAG = "RestrictBackgroundNetworkTests";
84 
85     protected static final String TEST_PKG = "com.android.cts.net.hostside";
86     protected static final String TEST_APP2_PKG = "com.android.cts.net.hostside.app2";
87 
88     private static final String TEST_APP2_ACTIVITY_CLASS = TEST_APP2_PKG + ".MyActivity";
89     private static final String TEST_APP2_SERVICE_CLASS = TEST_APP2_PKG + ".MyForegroundService";
90     private static final String TEST_APP2_JOB_SERVICE_CLASS = TEST_APP2_PKG + ".MyJobService";
91 
92     private static final ComponentName TEST_JOB_COMPONENT = new ComponentName(
93             TEST_APP2_PKG, TEST_APP2_JOB_SERVICE_CLASS);
94 
95     private static final int TEST_JOB_ID = 7357437;
96 
97     private static final int SLEEP_TIME_SEC = 1;
98 
99     // Constants below must match values defined on app2's Common.java
100     private static final String MANIFEST_RECEIVER = "ManifestReceiver";
101     private static final String DYNAMIC_RECEIVER = "DynamicReceiver";
102     private static final String ACTION_FINISH_ACTIVITY =
103             "com.android.cts.net.hostside.app2.action.FINISH_ACTIVITY";
104     private static final String ACTION_FINISH_JOB =
105             "com.android.cts.net.hostside.app2.action.FINISH_JOB";
106     // Copied from com.android.server.net.NetworkPolicyManagerService class
107     private static final String ACTION_SNOOZE_WARNING =
108             "com.android.server.net.action.SNOOZE_WARNING";
109 
110     private static final String ACTION_RECEIVER_READY =
111             "com.android.cts.net.hostside.app2.action.RECEIVER_READY";
112     static final String ACTION_SHOW_TOAST =
113             "com.android.cts.net.hostside.app2.action.SHOW_TOAST";
114 
115     protected static final String NOTIFICATION_TYPE_CONTENT = "CONTENT";
116     protected static final String NOTIFICATION_TYPE_DELETE = "DELETE";
117     protected static final String NOTIFICATION_TYPE_FULL_SCREEN = "FULL_SCREEN";
118     protected static final String NOTIFICATION_TYPE_BUNDLE = "BUNDLE";
119     protected static final String NOTIFICATION_TYPE_ACTION = "ACTION";
120     protected static final String NOTIFICATION_TYPE_ACTION_BUNDLE = "ACTION_BUNDLE";
121     protected static final String NOTIFICATION_TYPE_ACTION_REMOTE_INPUT = "ACTION_REMOTE_INPUT";
122 
123     // TODO: Update BatteryManager.BATTERY_PLUGGED_ANY as @TestApi
124     public static final int BATTERY_PLUGGED_ANY =
125             BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS;
126 
127     private static final String NETWORK_STATUS_SEPARATOR = "\\|";
128     private static final int SECOND_IN_MS = 1000;
129     static final int NETWORK_TIMEOUT_MS = 15 * SECOND_IN_MS;
130 
131     private static int PROCESS_STATE_FOREGROUND_SERVICE;
132 
133     private static final String KEY_NETWORK_STATE_OBSERVER = TEST_PKG + ".observer";
134     private static final String KEY_SKIP_VALIDATION_CHECKS = TEST_PKG + ".skip_validation_checks";
135 
136     private static final String EMPTY_STRING = "";
137 
138     protected static final int TYPE_COMPONENT_ACTIVTIY = 0;
139     protected static final int TYPE_COMPONENT_FOREGROUND_SERVICE = 1;
140     protected static final int TYPE_EXPEDITED_JOB = 2;
141 
142     private static final int BATTERY_STATE_TIMEOUT_MS = 5000;
143     private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 500;
144 
145     private static final int ACTIVITY_NETWORK_STATE_TIMEOUT_MS = 6_000;
146     private static final int JOB_NETWORK_STATE_TIMEOUT_MS = 10_000;
147     private static final int LAUNCH_ACTIVITY_TIMEOUT_MS = 10_000;
148 
149     // Must be higher than NETWORK_TIMEOUT_MS
150     private static final int ORDERED_BROADCAST_TIMEOUT_MS = NETWORK_TIMEOUT_MS * 4;
151 
152     private static final IntentFilter BATTERY_CHANGED_FILTER =
153             new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
154 
155     private static final String APP_NOT_FOREGROUND_ERROR = "app_not_fg";
156 
157     protected static final long TEMP_POWERSAVE_WHITELIST_DURATION_MS = 20_000; // 20 sec
158 
159     private static final long BROADCAST_TIMEOUT_MS = 5_000;
160 
161     protected Context mContext;
162     protected Instrumentation mInstrumentation;
163     protected ConnectivityManager mCm;
164     protected int mUid;
165     private int mMyUid;
166     private MyServiceClient mServiceClient;
167     private DeviceConfigStateHelper mDeviceIdleDeviceConfigStateHelper;
168     private PowerManager mPowerManager;
169     private PowerManager.WakeLock mLock;
170 
171     @Rule
172     public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
173             .around(new MeterednessConfigurationRule());
174 
setUp()175     protected void setUp() throws Exception {
176         // TODO: Annotate these constants with @TestApi instead of obtaining them using reflection
177         PROCESS_STATE_FOREGROUND_SERVICE = (Integer) ActivityManager.class
178                 .getDeclaredField("PROCESS_STATE_FOREGROUND_SERVICE").get(null);
179         mInstrumentation = getInstrumentation();
180         mContext = getContext();
181         mCm = getConnectivityManager();
182         mDeviceIdleDeviceConfigStateHelper =
183                 new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_DEVICE_IDLE);
184         mUid = getUid(TEST_APP2_PKG);
185         mMyUid = getUid(mContext.getPackageName());
186         mServiceClient = new MyServiceClient(mContext);
187         mServiceClient.bind();
188         mPowerManager = mContext.getSystemService(PowerManager.class);
189         executeShellCommand("cmd netpolicy start-watching " + mUid);
190         setAppIdle(false);
191         mLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
192 
193         Log.i(TAG, "Apps status:\n"
194                 + "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
195                 + "\tapp2: uid=" + mUid + ", state=" + getProcessStateByUid(mUid));
196     }
197 
tearDown()198     protected void tearDown() throws Exception {
199         executeShellCommand("cmd netpolicy stop-watching");
200         mServiceClient.unbind();
201         if (mLock.isHeld()) mLock.release();
202     }
203 
getUid(String packageName)204     protected int getUid(String packageName) throws Exception {
205         return mContext.getPackageManager().getPackageUid(packageName, 0);
206     }
207 
assertRestrictBackgroundChangedReceived(int expectedCount)208     protected void assertRestrictBackgroundChangedReceived(int expectedCount) throws Exception {
209         assertRestrictBackgroundChangedReceived(DYNAMIC_RECEIVER, expectedCount);
210         assertRestrictBackgroundChangedReceived(MANIFEST_RECEIVER, 0);
211     }
212 
assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)213     protected void assertRestrictBackgroundChangedReceived(String receiverName, int expectedCount)
214             throws Exception {
215         int attempts = 0;
216         int count = 0;
217         final int maxAttempts = 5;
218         do {
219             attempts++;
220             count = getNumberBroadcastsReceived(receiverName, ACTION_RESTRICT_BACKGROUND_CHANGED);
221             assertFalse("Expected count " + expectedCount + " but actual is " + count,
222                     count > expectedCount);
223             if (count == expectedCount) {
224                 break;
225             }
226             Log.d(TAG, "Expecting count " + expectedCount + " but actual is " + count + " after "
227                     + attempts + " attempts; sleeping "
228                     + SLEEP_TIME_SEC + " seconds before trying again");
229             // No sleep after the last turn
230             if (attempts <= maxAttempts) {
231                 SystemClock.sleep(SLEEP_TIME_SEC * SECOND_IN_MS);
232             }
233         } while (attempts <= maxAttempts);
234         assertEquals("Number of expected broadcasts for " + receiverName + " not reached after "
235                 + maxAttempts * SLEEP_TIME_SEC + " seconds", expectedCount, count);
236     }
237 
assertSnoozeWarningNotReceived()238     protected void assertSnoozeWarningNotReceived() throws Exception {
239         // Wait for a while to take broadcast queue delays into account
240         SystemClock.sleep(BROADCAST_TIMEOUT_MS);
241         assertEquals(0, getNumberBroadcastsReceived(DYNAMIC_RECEIVER, ACTION_SNOOZE_WARNING));
242     }
243 
sendOrderedBroadcast(Intent intent)244     protected String sendOrderedBroadcast(Intent intent) throws Exception {
245         return sendOrderedBroadcast(intent, ORDERED_BROADCAST_TIMEOUT_MS);
246     }
247 
sendOrderedBroadcast(Intent intent, int timeoutMs)248     protected String sendOrderedBroadcast(Intent intent, int timeoutMs) throws Exception {
249         final LinkedBlockingQueue<String> result = new LinkedBlockingQueue<>(1);
250         Log.d(TAG, "Sending ordered broadcast: " + intent);
251         mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
252 
253             @Override
254             public void onReceive(Context context, Intent intent) {
255                 final String resultData = getResultData();
256                 if (resultData == null) {
257                     Log.e(TAG, "Received null data from ordered intent");
258                     // Offer an empty string so that the code waiting for the result can return.
259                     result.offer(EMPTY_STRING);
260                     return;
261                 }
262                 result.offer(resultData);
263             }
264         }, null, 0, null, null);
265 
266         final String resultData = result.poll(timeoutMs, TimeUnit.MILLISECONDS);
267         Log.d(TAG, "Ordered broadcast response after " + timeoutMs + "ms: " + resultData );
268         return resultData;
269     }
270 
getNumberBroadcastsReceived(String receiverName, String action)271     protected int getNumberBroadcastsReceived(String receiverName, String action) throws Exception {
272         return mServiceClient.getCounters(receiverName, action);
273     }
274 
assertRestrictBackgroundStatus(int expectedStatus)275     protected void assertRestrictBackgroundStatus(int expectedStatus) throws Exception {
276         final String status = mServiceClient.getRestrictBackgroundStatus();
277         assertNotNull("didn't get API status from app2", status);
278         assertEquals(restrictBackgroundValueToString(expectedStatus),
279                 restrictBackgroundValueToString(Integer.parseInt(status)));
280     }
281 
assertBackgroundNetworkAccess(boolean expectAllowed)282     protected void assertBackgroundNetworkAccess(boolean expectAllowed) throws Exception {
283         assertBackgroundState();
284         assertNetworkAccess(expectAllowed /* expectAvailable */, false /* needScreenOn */);
285     }
286 
assertForegroundNetworkAccess()287     protected void assertForegroundNetworkAccess() throws Exception {
288         assertForegroundNetworkAccess(true);
289     }
290 
assertForegroundNetworkAccess(boolean expectAllowed)291     protected void assertForegroundNetworkAccess(boolean expectAllowed) throws Exception {
292         assertForegroundState();
293         // We verified that app is in foreground state but if the screen turns-off while
294         // verifying for network access, the app will go into background state (in case app's
295         // foreground status was due to top activity). So, turn the screen on when verifying
296         // network connectivity.
297         assertNetworkAccess(expectAllowed /* expectAvailable */, true /* needScreenOn */);
298     }
299 
assertForegroundServiceNetworkAccess()300     protected void assertForegroundServiceNetworkAccess() throws Exception {
301         assertForegroundServiceState();
302         assertNetworkAccess(true /* expectAvailable */, false /* needScreenOn */);
303     }
304 
305     /**
306      * Asserts that an app always have access while on foreground or running a foreground service.
307      *
308      * <p>This method will launch an activity, a foreground service to make
309      * the assertion, but will finish the activity / stop the service afterwards.
310      */
assertsForegroundAlwaysHasNetworkAccess()311     protected void assertsForegroundAlwaysHasNetworkAccess() throws Exception{
312         // Checks foreground first.
313         launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_ACTIVTIY);
314         finishActivity();
315 
316         // Then foreground service
317         launchComponentAndAssertNetworkAccess(TYPE_COMPONENT_FOREGROUND_SERVICE);
318         stopForegroundService();
319     }
320 
assertExpeditedJobHasNetworkAccess()321     protected void assertExpeditedJobHasNetworkAccess() throws Exception {
322         launchComponentAndAssertNetworkAccess(TYPE_EXPEDITED_JOB);
323         finishExpeditedJob();
324     }
325 
assertExpeditedJobHasNoNetworkAccess()326     protected void assertExpeditedJobHasNoNetworkAccess() throws Exception {
327         launchComponentAndAssertNetworkAccess(TYPE_EXPEDITED_JOB, false);
328         finishExpeditedJob();
329     }
330 
assertBackgroundState()331     protected final void assertBackgroundState() throws Exception {
332         final int maxTries = 30;
333         ProcessState state = null;
334         for (int i = 1; i <= maxTries; i++) {
335             state = getProcessStateByUid(mUid);
336             Log.v(TAG, "assertBackgroundState(): status for app2 (" + mUid + ") on attempt #" + i
337                     + ": " + state);
338             if (isBackground(state.state)) {
339                 return;
340             }
341             Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
342                     + "; sleeping 1s before trying again");
343             // No sleep after the last turn
344             if (i < maxTries) {
345                 SystemClock.sleep(SECOND_IN_MS);
346             }
347         }
348         fail("App2 (" + mUid + ") is not on background state after "
349                 + maxTries + " attempts: " + state);
350     }
351 
assertForegroundState()352     protected final void assertForegroundState() throws Exception {
353         final int maxTries = 30;
354         ProcessState state = null;
355         for (int i = 1; i <= maxTries; i++) {
356             state = getProcessStateByUid(mUid);
357             Log.v(TAG, "assertForegroundState(): status for app2 (" + mUid + ") on attempt #" + i
358                     + ": " + state);
359             if (!isBackground(state.state)) {
360                 return;
361             }
362             Log.d(TAG, "App not on foreground state on attempt #" + i
363                     + "; sleeping 1s before trying again");
364             turnScreenOn();
365             // No sleep after the last turn
366             if (i < maxTries) {
367                 SystemClock.sleep(SECOND_IN_MS);
368             }
369         }
370         fail("App2 (" + mUid + ") is not on foreground state after "
371                 + maxTries + " attempts: " + state);
372     }
373 
assertForegroundServiceState()374     protected final void assertForegroundServiceState() throws Exception {
375         final int maxTries = 30;
376         ProcessState state = null;
377         for (int i = 1; i <= maxTries; i++) {
378             state = getProcessStateByUid(mUid);
379             Log.v(TAG, "assertForegroundServiceState(): status for app2 (" + mUid + ") on attempt #"
380                     + i + ": " + state);
381             if (state.state == PROCESS_STATE_FOREGROUND_SERVICE) {
382                 return;
383             }
384             Log.d(TAG, "App not on foreground service state on attempt #" + i
385                     + "; sleeping 1s before trying again");
386             // No sleep after the last turn
387             if (i < maxTries) {
388                 SystemClock.sleep(SECOND_IN_MS);
389             }
390         }
391         fail("App2 (" + mUid + ") is not on foreground service state after "
392                 + maxTries + " attempts: " + state);
393     }
394 
395     /**
396      * Returns whether an app state should be considered "background" for restriction purposes.
397      */
isBackground(int state)398     protected boolean isBackground(int state) {
399         return state > PROCESS_STATE_FOREGROUND_SERVICE;
400     }
401 
402     /**
403      * Asserts whether the active network is available or not.
404      */
assertNetworkAccess(boolean expectAvailable, boolean needScreenOn)405     private void assertNetworkAccess(boolean expectAvailable, boolean needScreenOn)
406             throws Exception {
407         final int maxTries = 5;
408         String error = null;
409         int timeoutMs = 500;
410 
411         for (int i = 1; i <= maxTries; i++) {
412             error = checkNetworkAccess(expectAvailable);
413 
414             if (error == null) return;
415 
416             // TODO: ideally, it should retry only when it cannot connect to an external site,
417             // or no retry at all! But, currently, the initial change fails almost always on
418             // battery saver tests because the netd changes are made asynchronously.
419             // Once b/27803922 is fixed, this retry mechanism should be revisited.
420 
421             Log.w(TAG, "Network status didn't match for expectAvailable=" + expectAvailable
422                     + " on attempt #" + i + ": " + error + "\n"
423                     + "Sleeping " + timeoutMs + "ms before trying again");
424             if (needScreenOn) {
425                 turnScreenOn();
426             }
427             // No sleep after the last turn
428             if (i < maxTries) {
429                 SystemClock.sleep(timeoutMs);
430             }
431             // Exponential back-off.
432             timeoutMs = Math.min(timeoutMs*2, NETWORK_TIMEOUT_MS);
433         }
434         fail("Invalid state for " + mUid + "; expectAvailable=" + expectAvailable + " after "
435                 + maxTries + " attempts.\nLast error: " + error);
436     }
437 
438     /**
439      * Checks whether the network is available as expected.
440      *
441      * @return error message with the mismatch (or empty if assertion passed).
442      */
checkNetworkAccess(boolean expectAvailable)443     private String checkNetworkAccess(boolean expectAvailable) throws Exception {
444         final String resultData = mServiceClient.checkNetworkStatus();
445         return checkForAvailabilityInResultData(resultData, expectAvailable);
446     }
447 
checkForAvailabilityInResultData(String resultData, boolean expectAvailable)448     private String checkForAvailabilityInResultData(String resultData, boolean expectAvailable) {
449         if (resultData == null) {
450             assertNotNull("Network status from app2 is null", resultData);
451         }
452         // Network status format is described on MyBroadcastReceiver.checkNetworkStatus()
453         final String[] parts = resultData.split(NETWORK_STATUS_SEPARATOR);
454         assertEquals("Wrong network status: " + resultData, 5, parts.length);
455         final State state = parts[0].equals("null") ? null : State.valueOf(parts[0]);
456         final DetailedState detailedState = parts[1].equals("null")
457                 ? null : DetailedState.valueOf(parts[1]);
458         final boolean connected = Boolean.valueOf(parts[2]);
459         final String connectionCheckDetails = parts[3];
460         final String networkInfo = parts[4];
461 
462         final StringBuilder errors = new StringBuilder();
463         final State expectedState;
464         final DetailedState expectedDetailedState;
465         if (expectAvailable) {
466             expectedState = State.CONNECTED;
467             expectedDetailedState = DetailedState.CONNECTED;
468         } else {
469             expectedState = State.DISCONNECTED;
470             expectedDetailedState = DetailedState.BLOCKED;
471         }
472 
473         if (expectAvailable != connected) {
474             errors.append(String.format("External site connection failed: expected %s, got %s\n",
475                     expectAvailable, connected));
476         }
477         if (expectedState != state || expectedDetailedState != detailedState) {
478             errors.append(String.format("Connection state mismatch: expected %s/%s, got %s/%s\n",
479                     expectedState, expectedDetailedState, state, detailedState));
480         }
481 
482         if (errors.length() > 0) {
483             errors.append("\tnetworkInfo: " + networkInfo + "\n");
484             errors.append("\tconnectionCheckDetails: " + connectionCheckDetails + "\n");
485         }
486         return errors.length() == 0 ? null : errors.toString();
487     }
488 
489     /**
490      * Runs a Shell command which is not expected to generate output.
491      */
executeSilentShellCommand(String command)492     protected void executeSilentShellCommand(String command) {
493         final String result = executeShellCommand(command);
494         assertTrue("Command '" + command + "' failed: " + result, result.trim().isEmpty());
495     }
496 
497     /**
498      * Asserts the result of a command, wait and re-running it a couple times if necessary.
499      */
assertDelayedShellCommand(String command, final String expectedResult)500     protected void assertDelayedShellCommand(String command, final String expectedResult)
501             throws Exception {
502         assertDelayedShellCommand(command, 5, 1, expectedResult);
503     }
504 
assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds, final String expectedResult)505     protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
506             final String expectedResult) throws Exception {
507         assertDelayedShellCommand(command, maxTries, napTimeSeconds, new ExpectResultChecker() {
508 
509             @Override
510             public boolean isExpected(String result) {
511                 return expectedResult.equals(result);
512             }
513 
514             @Override
515             public String getExpected() {
516                 return expectedResult;
517             }
518         });
519     }
520 
assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds, ExpectResultChecker checker)521     protected void assertDelayedShellCommand(String command, int maxTries, int napTimeSeconds,
522             ExpectResultChecker checker) throws Exception {
523         String result = "";
524         for (int i = 1; i <= maxTries; i++) {
525             result = executeShellCommand(command).trim();
526             if (checker.isExpected(result)) return;
527             Log.v(TAG, "Command '" + command + "' returned '" + result + " instead of '"
528                     + checker.getExpected() + "' on attempt #" + i
529                     + "; sleeping " + napTimeSeconds + "s before trying again");
530             // No sleep after the last turn
531             if (i < maxTries) {
532                 SystemClock.sleep(napTimeSeconds * SECOND_IN_MS);
533             }
534         }
535         fail("Command '" + command + "' did not return '" + checker.getExpected() + "' after "
536                 + maxTries
537                 + " attempts. Last result: '" + result + "'");
538     }
539 
addRestrictBackgroundWhitelist(int uid)540     protected void addRestrictBackgroundWhitelist(int uid) throws Exception {
541         executeShellCommand("cmd netpolicy add restrict-background-whitelist " + uid);
542         assertRestrictBackgroundWhitelist(uid, true);
543         // UID policies live by the Highlander rule: "There can be only one".
544         // Hence, if app is whitelisted, it should not be blacklisted.
545         assertRestrictBackgroundBlacklist(uid, false);
546     }
547 
removeRestrictBackgroundWhitelist(int uid)548     protected void removeRestrictBackgroundWhitelist(int uid) throws Exception {
549         executeShellCommand("cmd netpolicy remove restrict-background-whitelist " + uid);
550         assertRestrictBackgroundWhitelist(uid, false);
551     }
552 
assertRestrictBackgroundWhitelist(int uid, boolean expected)553     protected void assertRestrictBackgroundWhitelist(int uid, boolean expected) throws Exception {
554         assertRestrictBackground("restrict-background-whitelist", uid, expected);
555     }
556 
addRestrictBackgroundBlacklist(int uid)557     protected void addRestrictBackgroundBlacklist(int uid) throws Exception {
558         executeShellCommand("cmd netpolicy add restrict-background-blacklist " + uid);
559         assertRestrictBackgroundBlacklist(uid, true);
560         // UID policies live by the Highlander rule: "There can be only one".
561         // Hence, if app is blacklisted, it should not be whitelisted.
562         assertRestrictBackgroundWhitelist(uid, false);
563     }
564 
removeRestrictBackgroundBlacklist(int uid)565     protected void removeRestrictBackgroundBlacklist(int uid) throws Exception {
566         executeShellCommand("cmd netpolicy remove restrict-background-blacklist " + uid);
567         assertRestrictBackgroundBlacklist(uid, false);
568     }
569 
assertRestrictBackgroundBlacklist(int uid, boolean expected)570     protected void assertRestrictBackgroundBlacklist(int uid, boolean expected) throws Exception {
571         assertRestrictBackground("restrict-background-blacklist", uid, expected);
572     }
573 
addAppIdleWhitelist(int uid)574     protected void addAppIdleWhitelist(int uid) throws Exception {
575         executeShellCommand("cmd netpolicy add app-idle-whitelist " + uid);
576         assertAppIdleWhitelist(uid, true);
577     }
578 
removeAppIdleWhitelist(int uid)579     protected void removeAppIdleWhitelist(int uid) throws Exception {
580         executeShellCommand("cmd netpolicy remove app-idle-whitelist " + uid);
581         assertAppIdleWhitelist(uid, false);
582     }
583 
assertAppIdleWhitelist(int uid, boolean expected)584     protected void assertAppIdleWhitelist(int uid, boolean expected) throws Exception {
585         assertRestrictBackground("app-idle-whitelist", uid, expected);
586     }
587 
assertRestrictBackground(String list, int uid, boolean expected)588     private void assertRestrictBackground(String list, int uid, boolean expected) throws Exception {
589         final int maxTries = 5;
590         boolean actual = false;
591         final String expectedUid = Integer.toString(uid);
592         String uids = "";
593         for (int i = 1; i <= maxTries; i++) {
594             final String output =
595                     executeShellCommand("cmd netpolicy list " + list);
596             uids = output.split(":")[1];
597             for (String candidate : uids.split(" ")) {
598                 actual = candidate.trim().equals(expectedUid);
599                 if (expected == actual) {
600                     return;
601                 }
602             }
603             Log.v(TAG, list + " check for uid " + uid + " doesn't match yet (expected "
604                     + expected + ", got " + actual + "); sleeping 1s before polling again");
605             // No sleep after the last turn
606             if (i < maxTries) {
607                 SystemClock.sleep(SECOND_IN_MS);
608             }
609         }
610         fail(list + " check for uid " + uid + " failed: expected " + expected + ", got " + actual
611                 + ". Full list: " + uids);
612     }
613 
addTempPowerSaveModeWhitelist(String packageName, long duration)614     protected void addTempPowerSaveModeWhitelist(String packageName, long duration)
615             throws Exception {
616         Log.i(TAG, "Adding pkg " + packageName + " to temp-power-save-mode whitelist");
617         executeShellCommand("dumpsys deviceidle tempwhitelist -d " + duration + " " + packageName);
618     }
619 
assertPowerSaveModeWhitelist(String packageName, boolean expected)620     protected void assertPowerSaveModeWhitelist(String packageName, boolean expected)
621             throws Exception {
622         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
623         // need to use netpolicy for whitelisting
624         assertDelayedShellCommand("dumpsys deviceidle whitelist =" + packageName,
625                 Boolean.toString(expected));
626     }
627 
addPowerSaveModeWhitelist(String packageName)628     protected void addPowerSaveModeWhitelist(String packageName) throws Exception {
629         Log.i(TAG, "Adding package " + packageName + " to power-save-mode whitelist");
630         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
631         // need to use netpolicy for whitelisting
632         executeShellCommand("dumpsys deviceidle whitelist +" + packageName);
633         assertPowerSaveModeWhitelist(packageName, true);
634     }
635 
removePowerSaveModeWhitelist(String packageName)636     protected void removePowerSaveModeWhitelist(String packageName) throws Exception {
637         Log.i(TAG, "Removing package " + packageName + " from power-save-mode whitelist");
638         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
639         // need to use netpolicy for whitelisting
640         executeShellCommand("dumpsys deviceidle whitelist -" + packageName);
641         assertPowerSaveModeWhitelist(packageName, false);
642     }
643 
assertPowerSaveModeExceptIdleWhitelist(String packageName, boolean expected)644     protected void assertPowerSaveModeExceptIdleWhitelist(String packageName, boolean expected)
645             throws Exception {
646         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
647         // need to use netpolicy for whitelisting
648         assertDelayedShellCommand("dumpsys deviceidle except-idle-whitelist =" + packageName,
649                 Boolean.toString(expected));
650     }
651 
addPowerSaveModeExceptIdleWhitelist(String packageName)652     protected void addPowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
653         Log.i(TAG, "Adding package " + packageName + " to power-save-mode-except-idle whitelist");
654         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
655         // need to use netpolicy for whitelisting
656         executeShellCommand("dumpsys deviceidle except-idle-whitelist +" + packageName);
657         assertPowerSaveModeExceptIdleWhitelist(packageName, true);
658     }
659 
removePowerSaveModeExceptIdleWhitelist(String packageName)660     protected void removePowerSaveModeExceptIdleWhitelist(String packageName) throws Exception {
661         Log.i(TAG, "Removing package " + packageName
662                 + " from power-save-mode-except-idle whitelist");
663         // TODO: currently the power-save mode is behaving like idle, but once it changes, we'll
664         // need to use netpolicy for whitelisting
665         executeShellCommand("dumpsys deviceidle except-idle-whitelist reset");
666         assertPowerSaveModeExceptIdleWhitelist(packageName, false);
667     }
668 
turnBatteryOn()669     protected void turnBatteryOn() throws Exception {
670         executeSilentShellCommand("cmd battery unplug");
671         executeSilentShellCommand("cmd battery set status "
672                 + BatteryManager.BATTERY_STATUS_DISCHARGING);
673         assertBatteryState(false);
674     }
675 
turnBatteryOff()676     protected void turnBatteryOff() throws Exception {
677         executeSilentShellCommand("cmd battery set ac " + BATTERY_PLUGGED_ANY);
678         executeSilentShellCommand("cmd battery set level 100");
679         executeSilentShellCommand("cmd battery set status "
680                 + BatteryManager.BATTERY_STATUS_CHARGING);
681         assertBatteryState(true);
682     }
683 
resetBatteryState()684     protected void resetBatteryState() {
685         BatteryUtils.runDumpsysBatteryReset();
686     }
687 
assertBatteryState(boolean pluggedIn)688     private void assertBatteryState(boolean pluggedIn) throws Exception {
689         final long endTime = SystemClock.elapsedRealtime() + BATTERY_STATE_TIMEOUT_MS;
690         while (isDevicePluggedIn() != pluggedIn && SystemClock.elapsedRealtime() <= endTime) {
691             Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
692         }
693         if (isDevicePluggedIn() != pluggedIn) {
694             fail("Timed out waiting for the plugged-in state to change,"
695                     + " expected pluggedIn: " + pluggedIn);
696         }
697     }
698 
isDevicePluggedIn()699     private boolean isDevicePluggedIn() {
700         final Intent batteryIntent = mContext.registerReceiver(null, BATTERY_CHANGED_FILTER);
701         return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
702     }
703 
turnScreenOff()704     protected void turnScreenOff() throws Exception {
705         if (!mLock.isHeld()) mLock.acquire();
706         executeSilentShellCommand("input keyevent KEYCODE_SLEEP");
707     }
708 
turnScreenOn()709     protected void turnScreenOn() throws Exception {
710         executeSilentShellCommand("input keyevent KEYCODE_WAKEUP");
711         if (mLock.isHeld()) mLock.release();
712         executeSilentShellCommand("wm dismiss-keyguard");
713     }
714 
setBatterySaverMode(boolean enabled)715     protected void setBatterySaverMode(boolean enabled) throws Exception {
716         if (!isBatterySaverSupported()) {
717             return;
718         }
719         Log.i(TAG, "Setting Battery Saver Mode to " + enabled);
720         if (enabled) {
721             turnBatteryOn();
722             AmUtils.waitForBroadcastBarrier();
723             executeSilentShellCommand("cmd power set-mode 1");
724         } else {
725             executeSilentShellCommand("cmd power set-mode 0");
726             turnBatteryOff();
727             AmUtils.waitForBroadcastBarrier();
728         }
729     }
730 
setDozeMode(boolean enabled)731     protected void setDozeMode(boolean enabled) throws Exception {
732         if (!isDozeModeSupported()) {
733             return;
734         }
735 
736         Log.i(TAG, "Setting Doze Mode to " + enabled);
737         if (enabled) {
738             turnBatteryOn();
739             turnScreenOff();
740             executeShellCommand("dumpsys deviceidle force-idle deep");
741         } else {
742             turnScreenOn();
743             turnBatteryOff();
744             executeShellCommand("dumpsys deviceidle unforce");
745         }
746         assertDozeMode(enabled);
747     }
748 
assertDozeMode(boolean enabled)749     protected void assertDozeMode(boolean enabled) throws Exception {
750         assertDelayedShellCommand("dumpsys deviceidle get deep", enabled ? "IDLE" : "ACTIVE");
751     }
752 
setAppIdle(boolean enabled)753     protected void setAppIdle(boolean enabled) throws Exception {
754         if (!isAppStandbySupported()) {
755             return;
756         }
757         Log.i(TAG, "Setting app idle to " + enabled);
758         executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
759         assertAppIdle(enabled);
760     }
761 
setAppIdleNoAssert(boolean enabled)762     protected void setAppIdleNoAssert(boolean enabled) throws Exception {
763         if (!isAppStandbySupported()) {
764             return;
765         }
766         Log.i(TAG, "Setting app idle to " + enabled);
767         executeSilentShellCommand("am set-inactive " + TEST_APP2_PKG + " " + enabled );
768     }
769 
assertAppIdle(boolean enabled)770     protected void assertAppIdle(boolean enabled) throws Exception {
771         try {
772             assertDelayedShellCommand("am get-inactive " + TEST_APP2_PKG,
773                     30 /* maxTries */, 1 /* napTimeSeconds */, "Idle=" + enabled);
774         } catch (Throwable e) {
775             throw e;
776         }
777     }
778 
779     /**
780      * Starts a service that will register a broadcast receiver to receive
781      * {@code RESTRICT_BACKGROUND_CHANGE} intents.
782      * <p>
783      * The service must run in a separate app because otherwise it would be killed every time
784      * {@link #runDeviceTests(String, String)} is executed.
785      */
registerBroadcastReceiver()786     protected void registerBroadcastReceiver() throws Exception {
787         mServiceClient.registerBroadcastReceiver();
788 
789         final Intent intent = new Intent(ACTION_RECEIVER_READY)
790                 .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
791         // Wait until receiver is ready.
792         final int maxTries = 10;
793         for (int i = 1; i <= maxTries; i++) {
794             final String message = sendOrderedBroadcast(intent, SECOND_IN_MS * 4);
795             Log.d(TAG, "app2 receiver acked: " + message);
796             if (message != null) {
797                 return;
798             }
799             Log.v(TAG, "app2 receiver is not ready yet; sleeping 1s before polling again");
800             // No sleep after the last turn
801             if (i < maxTries) {
802                 SystemClock.sleep(SECOND_IN_MS);
803             }
804         }
805         fail("app2 receiver is not ready in " + mUid);
806     }
807 
registerNetworkCallback(final NetworkRequest request, INetworkCallback cb)808     protected void registerNetworkCallback(final NetworkRequest request, INetworkCallback cb)
809             throws Exception {
810         Log.i(TAG, "Registering network callback for request: " + request);
811         mServiceClient.registerNetworkCallback(request, cb);
812     }
813 
unregisterNetworkCallback()814     protected void unregisterNetworkCallback() throws Exception {
815         mServiceClient.unregisterNetworkCallback();
816     }
817 
818     /**
819      * Registers a {@link NotificationListenerService} implementation that will execute the
820      * notification actions right after the notification is sent.
821      */
registerNotificationListenerService()822     protected void registerNotificationListenerService() throws Exception {
823         executeShellCommand("cmd notification allow_listener "
824                 + MyNotificationListenerService.getId());
825         final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
826         final ComponentName listenerComponent = MyNotificationListenerService.getComponentName();
827         assertTrue(listenerComponent + " has not been granted access",
828                 nm.isNotificationListenerAccessGranted(listenerComponent));
829     }
830 
setPendingIntentAllowlistDuration(long durationMs)831     protected void setPendingIntentAllowlistDuration(long durationMs) {
832         mDeviceIdleDeviceConfigStateHelper.set("notification_allowlist_duration_ms",
833                 String.valueOf(durationMs));
834     }
835 
resetDeviceIdleSettings()836     protected void resetDeviceIdleSettings() {
837         mDeviceIdleDeviceConfigStateHelper.restoreOriginalValues();
838     }
839 
launchActivity()840     protected void launchActivity() throws Exception {
841         turnScreenOn();
842         final CountDownLatch latch = new CountDownLatch(1);
843         final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_ACTIVTIY);
844         final RemoteCallback callback = new RemoteCallback(result -> latch.countDown());
845         launchIntent.putExtra(Intent.EXTRA_REMOTE_CALLBACK, callback);
846         mContext.startActivity(launchIntent);
847         // There might be a race when app2 is launched but ACTION_FINISH_ACTIVITY has not registered
848         // before test calls finishActivity(). When the issue is happened, there is no way to fix
849         // it, so have a callback design to make sure that the app is launched completely and
850         // ACTION_FINISH_ACTIVITY will be registered before leaving this method.
851         if (!latch.await(LAUNCH_ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
852             fail("Timed out waiting for launching activity");
853         }
854     }
855 
launchComponentAndAssertNetworkAccess(int type)856     protected void launchComponentAndAssertNetworkAccess(int type) throws Exception {
857         launchComponentAndAssertNetworkAccess(type, true);
858     }
859 
launchComponentAndAssertNetworkAccess(int type, boolean expectAvailable)860     protected void launchComponentAndAssertNetworkAccess(int type, boolean expectAvailable)
861             throws Exception {
862         if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
863             startForegroundService();
864             assertForegroundServiceNetworkAccess();
865             return;
866         } else if (type == TYPE_COMPONENT_ACTIVTIY) {
867             turnScreenOn();
868             final CountDownLatch latch = new CountDownLatch(1);
869             final Intent launchIntent = getIntentForComponent(type);
870             final Bundle extras = new Bundle();
871             final ArrayList<Pair<Integer, String>> result = new ArrayList<>(1);
872             extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
873             extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
874             launchIntent.putExtras(extras);
875             mContext.startActivity(launchIntent);
876             if (latch.await(ACTIVITY_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
877                 final int resultCode = result.get(0).first;
878                 final String resultData = result.get(0).second;
879                 if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
880                     final String error = checkForAvailabilityInResultData(
881                             resultData, expectAvailable);
882                     if (error != null) {
883                         fail("Network is not available for activity in app2 (" + mUid + "): "
884                                 + error);
885                     }
886                 } else if (resultCode == INetworkStateObserver.RESULT_ERROR_UNEXPECTED_PROC_STATE) {
887                     Log.d(TAG, resultData);
888                     // App didn't come to foreground when the activity is started, so try again.
889                     assertForegroundNetworkAccess();
890                 } else {
891                     fail("Unexpected resultCode=" + resultCode + "; received=[" + resultData + "]");
892                 }
893             } else {
894                 fail("Timed out waiting for network availability status from app2's activity ("
895                         + mUid + ")");
896             }
897         } else if (type == TYPE_EXPEDITED_JOB) {
898             final Bundle extras = new Bundle();
899             final ArrayList<Pair<Integer, String>> result = new ArrayList<>(1);
900             final CountDownLatch latch = new CountDownLatch(1);
901             extras.putBinder(KEY_NETWORK_STATE_OBSERVER, getNewNetworkStateObserver(latch, result));
902             extras.putBoolean(KEY_SKIP_VALIDATION_CHECKS, !expectAvailable);
903             final JobInfo jobInfo = new JobInfo.Builder(TEST_JOB_ID, TEST_JOB_COMPONENT)
904                     .setExpedited(true)
905                     .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
906                     .setTransientExtras(extras)
907                     .build();
908             assertEquals("Error scheduling " + jobInfo,
909                     RESULT_SUCCESS, mServiceClient.scheduleJob(jobInfo));
910             forceRunJob(TEST_APP2_PKG, TEST_JOB_ID);
911             if (latch.await(JOB_NETWORK_STATE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
912                 final int resultCode = result.get(0).first;
913                 final String resultData = result.get(0).second;
914                 if (resultCode == INetworkStateObserver.RESULT_SUCCESS_NETWORK_STATE_CHECKED) {
915                     final String error = checkForAvailabilityInResultData(
916                             resultData, expectAvailable);
917                     if (error != null) {
918                         Log.d(TAG, "Network state is unexpected, checking again. " + error);
919                         // Right now we could end up in an unexpected state if expedited job
920                         // doesn't have network access immediately after starting, so check again.
921                         assertNetworkAccess(expectAvailable, false /* needScreenOn */);
922                     }
923                 } else {
924                     fail("Unexpected resultCode=" + resultCode + "; received=[" + resultData + "]");
925                 }
926             } else {
927                 fail("Timed out waiting for network availability status from app2's expedited job ("
928                         + mUid + ")");
929             }
930         } else {
931             throw new IllegalArgumentException("Unknown type: " + type);
932         }
933     }
934 
startActivity()935     protected void startActivity() throws Exception {
936         final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_ACTIVTIY);
937         mContext.startActivity(launchIntent);
938     }
939 
startForegroundService()940     private void startForegroundService() throws Exception {
941         final Intent launchIntent = getIntentForComponent(TYPE_COMPONENT_FOREGROUND_SERVICE);
942         mContext.startForegroundService(launchIntent);
943         assertForegroundServiceState();
944     }
945 
getIntentForComponent(int type)946     private Intent getIntentForComponent(int type) {
947         final Intent intent = new Intent();
948         if (type == TYPE_COMPONENT_ACTIVTIY) {
949             intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_ACTIVITY_CLASS))
950                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
951         } else if (type == TYPE_COMPONENT_FOREGROUND_SERVICE) {
952             intent.setComponent(new ComponentName(TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS))
953                     .setFlags(1);
954         } else {
955             fail("Unknown type: " + type);
956         }
957         return intent;
958     }
959 
stopForegroundService()960     protected void stopForegroundService() throws Exception {
961         executeShellCommand(String.format("am startservice -f 2 %s/%s",
962                 TEST_APP2_PKG, TEST_APP2_SERVICE_CLASS));
963         // NOTE: cannot assert state because it depends on whether activity was on top before.
964     }
965 
getNewNetworkStateObserver(final CountDownLatch latch, final ArrayList<Pair<Integer, String>> result)966     private Binder getNewNetworkStateObserver(final CountDownLatch latch,
967             final ArrayList<Pair<Integer, String>> result) {
968         return new INetworkStateObserver.Stub() {
969             @Override
970             public void onNetworkStateChecked(int resultCode, String resultData) {
971                 result.add(Pair.create(resultCode, resultData));
972                 latch.countDown();
973             }
974         };
975     }
976 
977     /**
978      * Finishes an activity on app2 so its process is demoted from foreground status.
979      */
980     protected void finishActivity() throws Exception {
981         final Intent intent = new Intent(ACTION_FINISH_ACTIVITY)
982                 .setPackage(TEST_APP2_PKG)
983                 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
984         sendOrderedBroadcast(intent);
985     }
986 
987     /**
988      * Finishes the expedited job on app2 so its process is demoted from foreground status.
989      */
990     private void finishExpeditedJob() throws Exception {
991         final Intent intent = new Intent(ACTION_FINISH_JOB)
992                 .setPackage(TEST_APP2_PKG)
993                 .setFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
994         sendOrderedBroadcast(intent);
995     }
996 
997     protected void sendNotification(int notificationId, String notificationType) throws Exception {
998         Log.d(TAG, "Sending notification broadcast (id=" + notificationId
999                 + ", type=" + notificationType);
1000         mServiceClient.sendNotification(notificationId, notificationType);
1001     }
1002 
1003     protected String showToast() {
1004         final Intent intent = new Intent(ACTION_SHOW_TOAST);
1005         intent.setPackage(TEST_APP2_PKG);
1006         Log.d(TAG, "Sending request to show toast");
1007         try {
1008             return sendOrderedBroadcast(intent, 3 * SECOND_IN_MS);
1009         } catch (Exception e) {
1010             return "";
1011         }
1012     }
1013 
1014     private ProcessState getProcessStateByUid(int uid) throws Exception {
1015         return new ProcessState(executeShellCommand("cmd activity get-uid-state " + uid));
1016     }
1017 
1018     private static class ProcessState {
1019         private final String fullState;
1020         final int state;
1021 
1022         ProcessState(String fullState) {
1023             this.fullState = fullState;
1024             try {
1025                 this.state = Integer.parseInt(fullState.split(" ")[0]);
1026             } catch (Exception e) {
1027                 throw new IllegalArgumentException("Could not parse " + fullState);
1028             }
1029         }
1030 
1031         @Override
1032         public String toString() {
1033             return fullState;
1034         }
1035     }
1036 
1037     /**
1038      * Helper class used to assert the result of a Shell command.
1039      */
1040     protected static interface ExpectResultChecker {
1041         /**
1042          * Checkes whether the result of the command matched the expectation.
1043          */
1044         boolean isExpected(String result);
1045         /**
1046          * Gets the expected result so it's displayed on log and failure messages.
1047          */
1048         String getExpected();
1049     }
1050 
1051     protected void setRestrictedNetworkingMode(boolean enabled) throws Exception {
1052         executeSilentShellCommand(
1053                 "settings put global restricted_networking_mode " + (enabled ? 1 : 0));
1054         assertRestrictedNetworkingModeState(enabled);
1055     }
1056 
1057     protected void assertRestrictedNetworkingModeState(boolean enabled) throws Exception {
1058         assertDelayedShellCommand("cmd netpolicy get restricted-mode",
1059                 "Restricted mode status: " + (enabled ? "enabled" : "disabled"));
1060     }
1061 }
1062