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