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