1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License
15  */
16 
17 package android.jobscheduler.cts;
18 
19 import static android.app.job.JobInfo.NETWORK_TYPE_ANY;
20 import static android.app.job.JobInfo.NETWORK_TYPE_NONE;
21 import static android.jobscheduler.cts.ConnectivityConstraintTest.ensureSavedWifiNetwork;
22 import static android.jobscheduler.cts.ConnectivityConstraintTest.isWiFiConnected;
23 import static android.jobscheduler.cts.ConnectivityConstraintTest.setWifiState;
24 import static android.jobscheduler.cts.TestAppInterface.TEST_APP_PACKAGE;
25 import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED;
26 import static android.os.PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED;
27 
28 import static com.android.compatibility.common.util.TestUtils.waitUntil;
29 
30 import static junit.framework.Assert.fail;
31 
32 import static org.junit.Assert.assertEquals;
33 import static org.junit.Assert.assertFalse;
34 import static org.junit.Assert.assertTrue;
35 import static org.junit.Assume.assumeFalse;
36 import static org.junit.Assume.assumeTrue;
37 
38 import android.Manifest;
39 import android.app.AppOpsManager;
40 import android.app.job.JobInfo;
41 import android.app.job.JobParameters;
42 import android.content.BroadcastReceiver;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.IntentFilter;
46 import android.content.pm.PackageManager;
47 import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver;
48 import android.net.ConnectivityManager;
49 import android.net.wifi.WifiManager;
50 import android.os.PowerManager;
51 import android.os.SystemClock;
52 import android.os.Temperature;
53 import android.os.UserHandle;
54 import android.platform.test.annotations.RequiresDevice;
55 import android.provider.DeviceConfig;
56 import android.provider.Settings;
57 import android.support.test.uiautomator.UiDevice;
58 import android.util.Log;
59 
60 import androidx.test.InstrumentationRegistry;
61 import androidx.test.filters.LargeTest;
62 import androidx.test.runner.AndroidJUnit4;
63 
64 import com.android.compatibility.common.util.AppOpsUtils;
65 import com.android.compatibility.common.util.AppStandbyUtils;
66 import com.android.compatibility.common.util.BatteryUtils;
67 import com.android.compatibility.common.util.CallbackAsserter;
68 import com.android.compatibility.common.util.DeviceConfigStateHelper;
69 import com.android.compatibility.common.util.SystemUtil;
70 import com.android.compatibility.common.util.ThermalUtils;
71 
72 import junit.framework.AssertionFailedError;
73 
74 import org.junit.After;
75 import org.junit.Before;
76 import org.junit.Test;
77 import org.junit.runner.RunWith;
78 
79 import java.io.IOException;
80 import java.util.concurrent.atomic.AtomicReference;
81 import java.util.regex.Matcher;
82 import java.util.regex.Pattern;
83 
84 /**
85  * Tests related to job throttling -- device idle, app standby and battery saver.
86  */
87 @RunWith(AndroidJUnit4.class)
88 @LargeTest
89 public class JobThrottlingTest {
90     private static final String TAG = JobThrottlingTest.class.getSimpleName();
91     private static final long BACKGROUND_JOBS_EXPECTED_DELAY = 3_000;
92     private static final long POLL_INTERVAL = 500;
93     private static final long DEFAULT_WAIT_TIMEOUT = 2000;
94     private static final long SHELL_TIMEOUT = 3_000;
95     // TODO: mark Settings.System.SCREEN_OFF_TIMEOUT as @TestApi
96     private static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
97 
98     enum Bucket {
99         ACTIVE,
100         WORKING_SET,
101         FREQUENT,
102         RARE,
103         RESTRICTED,
104         NEVER
105     }
106 
107     private Context mContext;
108     private UiDevice mUiDevice;
109     private PowerManager mPowerManager;
110     private int mTestJobId;
111     private int mTestPackageUid;
112     private boolean mDeviceInDoze;
113     private boolean mDeviceIdleEnabled;
114     private boolean mAppStandbyEnabled;
115     private WifiManager mWifiManager;
116     private ConnectivityManager mCm;
117     /** Whether the device running these tests supports WiFi. */
118     private boolean mHasWifi;
119     /** Track whether WiFi was enabled in case we turn it off. */
120     private boolean mInitialWiFiState;
121     private boolean mInitialAirplaneModeState;
122     private String mInitialDisplayTimeout;
123     private String mInitialRestrictedBucketEnabled;
124     private boolean mAutomotiveDevice;
125     private boolean mLeanbackOnly;
126 
127     private TestAppInterface mTestAppInterface;
128     private DeviceConfigStateHelper mDeviceConfigStateHelper;
129 
130     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
131         @Override
132         public void onReceive(Context context, Intent intent) {
133             Log.d(TAG, "Received action " + intent.getAction());
134             switch (intent.getAction()) {
135                 case ACTION_DEVICE_IDLE_MODE_CHANGED:
136                 case ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
137                     synchronized (JobThrottlingTest.this) {
138                         mDeviceInDoze = mPowerManager.isDeviceIdleMode();
139                         Log.d(TAG, "mDeviceInDoze: " + mDeviceInDoze);
140                     }
141                     break;
142             }
143         }
144     };
145 
isDeviceIdleEnabled(UiDevice uiDevice)146     private static boolean isDeviceIdleEnabled(UiDevice uiDevice) throws Exception {
147         final String output = uiDevice.executeShellCommand("cmd deviceidle enabled deep").trim();
148         return Integer.parseInt(output) != 0;
149     }
150 
151     @Before
setUp()152     public void setUp() throws Exception {
153         mContext = InstrumentationRegistry.getTargetContext();
154         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
155         mPowerManager = mContext.getSystemService(PowerManager.class);
156         mDeviceInDoze = mPowerManager.isDeviceIdleMode();
157         mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
158         mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
159         mTestAppInterface = new TestAppInterface(mContext, mTestJobId);
160         final IntentFilter intentFilter = new IntentFilter();
161         intentFilter.addAction(ACTION_DEVICE_IDLE_MODE_CHANGED);
162         intentFilter.addAction(ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
163         mContext.registerReceiver(mReceiver, intentFilter);
164         assertFalse("Test package already in temp whitelist", isTestAppTempWhitelisted());
165         makeTestPackageIdle();
166         mDeviceIdleEnabled = isDeviceIdleEnabled(mUiDevice);
167         mAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabled();
168         if (mAppStandbyEnabled) {
169             setTestPackageStandbyBucket(Bucket.ACTIVE);
170         } else {
171             Log.w(TAG, "App standby not enabled on test device");
172         }
173         mWifiManager = mContext.getSystemService(WifiManager.class);
174         mCm = mContext.getSystemService(ConnectivityManager.class);
175         mHasWifi = mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI);
176         mInitialWiFiState = mWifiManager.isWifiEnabled();
177         mInitialAirplaneModeState = isAirplaneModeOn();
178         mInitialRestrictedBucketEnabled = Settings.Global.getString(mContext.getContentResolver(),
179                 Settings.Global.ENABLE_RESTRICTED_BUCKET);
180         // Make sure test jobs can run regardless of bucket.
181         mDeviceConfigStateHelper =
182                 new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
183         mDeviceConfigStateHelper.set(
184                 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
185                         .setInt("min_ready_non_active_jobs_count", 0).build());
186         // Make sure the screen doesn't turn off when the test turns it on.
187         mInitialDisplayTimeout =
188                 Settings.System.getString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT);
189         Settings.System.putString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT, "300000");
190 
191         // In automotive device, always-on screen and endless battery charging are assumed.
192         mAutomotiveDevice =
193                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
194         // In leanback devices, it is assumed that there is no battery.
195         mLeanbackOnly =
196                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
197         if (mAutomotiveDevice || mLeanbackOnly) {
198             setScreenState(true);
199             // TODO(b/159176758): make sure that initial power supply is on.
200             BatteryUtils.runDumpsysBatterySetPluggedIn(true);
201         }
202 
203         // Kill as many things in the background as possible so we avoid LMK interfering with the
204         // test.
205         mUiDevice.executeShellCommand("am kill-all");
206     }
207 
208     @Test
testAllowWhileIdleJobInTempwhitelist()209     public void testAllowWhileIdleJobInTempwhitelist() throws Exception {
210         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
211 
212         toggleDozeState(true);
213         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
214         sendScheduleJobBroadcast(true);
215         assertFalse("Job started without being tempwhitelisted",
216                 mTestAppInterface.awaitJobStart(5_000));
217         tempWhitelistTestApp(5_000);
218         assertTrue("Job with allow_while_idle flag did not start when the app was tempwhitelisted",
219                 mTestAppInterface.awaitJobStart(5_000));
220     }
221 
222     @Test
testForegroundJobsStartImmediately()223     public void testForegroundJobsStartImmediately() throws Exception {
224         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
225 
226         sendScheduleJobBroadcast(false);
227         runJob();
228         assertTrue("Job did not start after scheduling",
229                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
230         toggleDozeState(true);
231         assertTrue("Job did not stop on entering doze",
232                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
233         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
234         // The adb command will force idle even with the screen on, so we need to turn Doze off
235         // explicitly.
236         toggleDozeState(false);
237         // Turn the screen on to ensure the test app ends up in TOP.
238         setScreenState(true);
239         mTestAppInterface.startAndKeepTestActivity();
240         assertTrue("Job for foreground app did not start immediately when device exited doze",
241                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
242     }
243 
244     @Test
testBackgroundJobsDelayed()245     public void testBackgroundJobsDelayed() throws Exception {
246         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
247 
248         sendScheduleJobBroadcast(false);
249         runJob();
250         assertTrue("Job did not start after scheduling",
251                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
252         toggleDozeState(true);
253         assertTrue("Job did not stop on entering doze",
254                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
255         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
256         toggleDozeState(false);
257         assertFalse("Job for background app started immediately when device exited doze",
258                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
259         Thread.sleep(BACKGROUND_JOBS_EXPECTED_DELAY - DEFAULT_WAIT_TIMEOUT);
260         assertTrue("Job for background app did not start after the expected delay of "
261                         + BACKGROUND_JOBS_EXPECTED_DELAY + "ms",
262                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
263     }
264 
265     @Test
testJobStoppedWhenRestricted()266     public void testJobStoppedWhenRestricted() throws Exception {
267         sendScheduleJobBroadcast(false);
268         runJob();
269         assertTrue("Job did not start after scheduling",
270                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
271         setTestPackageRestricted(true);
272         assertTrue("Job did not stop after test app was restricted",
273                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
274         assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
275                 mTestAppInterface.getLastParams().getStopReason());
276     }
277 
278     @Test
testRestrictedJobStartedWhenUnrestricted()279     public void testRestrictedJobStartedWhenUnrestricted() throws Exception {
280         setTestPackageRestricted(true);
281         sendScheduleJobBroadcast(false);
282         assertFalse("Job started for restricted app",
283                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
284         setTestPackageRestricted(false);
285         assertTrue("Job did not start when app was unrestricted",
286                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
287     }
288 
289     @Test
testRestrictedJobAllowedWhenUidActive()290     public void testRestrictedJobAllowedWhenUidActive() throws Exception {
291         setTestPackageRestricted(true);
292         sendScheduleJobBroadcast(false);
293         assertFalse("Job started for restricted app",
294                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
295         // Turn the screen on to ensure the app gets into the TOP state.
296         setScreenState(true);
297         mTestAppInterface.startAndKeepTestActivity(true);
298         assertTrue("Job did not start when app had an activity",
299                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
300 
301         mTestAppInterface.closeActivity();
302         // Don't put full minute as the timeout to give some leeway with test timing/processing.
303         assertFalse("Job stopped within grace period after activity closed",
304                 mTestAppInterface.awaitJobStop(55_000L));
305         assertTrue("Job did not stop after grace period ended",
306                 mTestAppInterface.awaitJobStop(15_000L));
307         assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
308                 mTestAppInterface.getLastParams().getStopReason());
309     }
310 
311     @Test
testEJStoppedWhenRestricted()312     public void testEJStoppedWhenRestricted() throws Exception {
313         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
314         runJob();
315         assertTrue("Job did not start after scheduling",
316                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
317         setTestPackageRestricted(true);
318         assertTrue("Job did not stop after test app was restricted",
319                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
320         assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
321                 mTestAppInterface.getLastParams().getStopReason());
322     }
323 
324     @Test
testRestrictedEJStartedWhenUnrestricted()325     public void testRestrictedEJStartedWhenUnrestricted() throws Exception {
326         setTestPackageRestricted(true);
327         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
328         assertFalse("Job started for restricted app",
329                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
330         setTestPackageRestricted(false);
331         assertTrue("Job did not start when app was unrestricted",
332                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
333     }
334 
335     @Test
testRestrictedEJAllowedWhenUidActive()336     public void testRestrictedEJAllowedWhenUidActive() throws Exception {
337         setTestPackageRestricted(true);
338         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
339         assertFalse("Job started for restricted app",
340                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
341         // Turn the screen on to ensure the app gets into the TOP state.
342         setScreenState(true);
343         mTestAppInterface.startAndKeepTestActivity(true);
344         assertTrue("Job did not start when app had an activity",
345                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
346 
347         mTestAppInterface.closeActivity();
348         // Don't put full minute as the timeout to give some leeway with test timing/processing.
349         assertFalse("Job stopped within grace period after activity closed",
350                 mTestAppInterface.awaitJobStop(55_000L));
351         assertTrue("Job did not stop after grace period ended",
352                 mTestAppInterface.awaitJobStop(15_000L));
353         assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
354                 mTestAppInterface.getLastParams().getStopReason());
355     }
356 
357     @RequiresDevice // Emulators don't always have access to wifi/network
358     @Test
testBackgroundConnectivityJobsThrottled()359     public void testBackgroundConnectivityJobsThrottled() throws Exception {
360         if (!mHasWifi) {
361             Log.d(TAG, "Skipping test that requires the device be WiFi enabled.");
362             return;
363         }
364         ensureSavedWifiNetwork(mWifiManager);
365         setAirplaneMode(false);
366         setWifiState(true, mCm, mWifiManager);
367         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
368         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
369         runJob();
370         assertTrue("Job did not start after scheduling",
371                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
372         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
373         assertTrue("Job did not stop on thermal throttling",
374                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
375         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
376         ThermalUtils.overrideThermalNotThrottling();
377         runJob();
378         assertTrue("Job did not start back from throttling",
379                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
380     }
381 
382     /** Tests that apps in the RESTRICTED bucket still get their one parole session per day. */
383     @Test
testJobsInRestrictedBucket_ParoleSession()384     public void testJobsInRestrictedBucket_ParoleSession() throws Exception {
385         assumeTrue("app standby not enabled", mAppStandbyEnabled);
386         assumeFalse("not testable in automotive device", mAutomotiveDevice);
387         assumeFalse("not testable in leanback device", mLeanbackOnly);
388 
389         setRestrictedBucketEnabled(true);
390 
391         // Disable coalescing
392         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
393 
394         setScreenState(true);
395 
396         BatteryUtils.runDumpsysBatteryUnplug();
397         setTestPackageStandbyBucket(Bucket.RESTRICTED);
398         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
399         sendScheduleJobBroadcast(false);
400         runJob();
401         assertTrue("Parole job didn't start in RESTRICTED bucket",
402                 mTestAppInterface.awaitJobStart(3_000));
403 
404         sendScheduleJobBroadcast(false);
405         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
406     }
407 
408     /**
409      * Tests that apps in the RESTRICTED bucket have their parole sessions properly counted even
410      * when charging (but not idle).
411      */
412     @Test
testJobsInRestrictedBucket_CorrectParoleWhileCharging()413     public void testJobsInRestrictedBucket_CorrectParoleWhileCharging() throws Exception {
414         assumeTrue("app standby not enabled", mAppStandbyEnabled);
415         assumeFalse("not testable in automotive device", mAutomotiveDevice);
416         assumeFalse("not testable in leanback device", mLeanbackOnly);
417 
418         setRestrictedBucketEnabled(true);
419 
420         // Disable coalescing
421         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
422         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "1");
423 
424         setScreenState(true);
425         BatteryUtils.runDumpsysBatterySetPluggedIn(true);
426         BatteryUtils.runDumpsysBatterySetLevel(100);
427 
428         setTestPackageStandbyBucket(Bucket.RESTRICTED);
429         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
430         sendScheduleJobBroadcast(false);
431         runJob();
432         assertTrue("Parole job didn't start in RESTRICTED bucket",
433                 mTestAppInterface.awaitJobStart(3_000));
434 
435         sendScheduleJobBroadcast(false);
436         assertFalse("New job started in RESTRICTED bucket after parole used",
437                 mTestAppInterface.awaitJobStart(3_000));
438     }
439 
440     /**
441      * Tests that apps in the RESTRICTED bucket that have used their one parole session per day
442      * don't get to run again until the device is charging + idle.
443      */
444     @Test
testJobsInRestrictedBucket_DeferredUntilFreeResources()445     public void testJobsInRestrictedBucket_DeferredUntilFreeResources() throws Exception {
446         assumeTrue("app standby not enabled", mAppStandbyEnabled);
447         assumeFalse("not testable in automotive device", mAutomotiveDevice);
448         assumeFalse("not testable in leanback device", mLeanbackOnly);
449 
450         setRestrictedBucketEnabled(true);
451 
452         // Disable coalescing
453         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
454 
455         setScreenState(true);
456 
457         BatteryUtils.runDumpsysBatteryUnplug();
458         setTestPackageStandbyBucket(Bucket.RESTRICTED);
459         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
460         sendScheduleJobBroadcast(false);
461         runJob();
462         assertTrue("Parole job didn't start in RESTRICTED bucket",
463                 mTestAppInterface.awaitJobStart(3_000));
464 
465         sendScheduleJobBroadcast(false);
466         assertFalse("New job started in RESTRICTED bucket after parole used",
467                 mTestAppInterface.awaitJobStart(3_000));
468 
469         BatteryUtils.runDumpsysBatterySetPluggedIn(true);
470         BatteryUtils.runDumpsysBatterySetLevel(100);
471         assertFalse("New job started in RESTRICTED bucket after parole when charging but not idle",
472                 mTestAppInterface.awaitJobStart(3_000));
473 
474         setScreenState(false);
475         triggerJobIdle();
476         assertTrue("Job didn't start in RESTRICTED bucket when charging + idle",
477                 mTestAppInterface.awaitJobStart(3_000));
478 
479         // Make sure job can be stopped and started again when charging + idle
480         sendScheduleJobBroadcast(false);
481         assertTrue("Job didn't restart in RESTRICTED bucket when charging + idle",
482                 mTestAppInterface.awaitJobStart(3_000));
483     }
484 
485     @Test
testJobsInRestrictedBucket_NoRequiredNetwork()486     public void testJobsInRestrictedBucket_NoRequiredNetwork() throws Exception {
487         assumeTrue("app standby not enabled", mAppStandbyEnabled);
488         assumeFalse("not testable in automotive device", mAutomotiveDevice);
489         assumeFalse("not testable in leanback device", mLeanbackOnly);
490 
491         setRestrictedBucketEnabled(true);
492 
493         // Disable coalescing and the parole session
494         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
495         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
496 
497         setAirplaneMode(true);
498         setScreenState(true);
499 
500         BatteryUtils.runDumpsysBatteryUnplug();
501         setTestPackageStandbyBucket(Bucket.RESTRICTED);
502         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
503         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
504         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
505 
506         // Slowly add back required bucket constraints.
507 
508         // Battery charging and high.
509         BatteryUtils.runDumpsysBatterySetPluggedIn(true);
510         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
511         BatteryUtils.runDumpsysBatterySetLevel(100);
512         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
513 
514         // Device is idle.
515         setScreenState(false);
516         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
517         triggerJobIdle();
518         assertTrue("New job didn't start in RESTRICTED bucket",
519                 mTestAppInterface.awaitJobStart(3_000));
520     }
521 
522     @RequiresDevice // Emulators don't always have access to wifi/network
523     @Test
testJobsInRestrictedBucket_WithRequiredNetwork()524     public void testJobsInRestrictedBucket_WithRequiredNetwork() throws Exception {
525         assumeTrue("app standby not enabled", mAppStandbyEnabled);
526         assumeFalse("not testable in automotive device", mAutomotiveDevice);
527         assumeFalse("not testable in leanback device", mLeanbackOnly);
528 
529         assumeTrue(mHasWifi);
530         ensureSavedWifiNetwork(mWifiManager);
531 
532         setRestrictedBucketEnabled(true);
533 
534         // Disable coalescing and the parole session
535         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
536         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
537 
538         setAirplaneMode(true);
539         setScreenState(true);
540 
541         BatteryUtils.runDumpsysBatteryUnplug();
542         setTestPackageStandbyBucket(Bucket.RESTRICTED);
543         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
544         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
545         runJob();
546         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
547 
548         // Slowly add back required bucket constraints.
549 
550         // Battery charging and high.
551         BatteryUtils.runDumpsysBatterySetPluggedIn(true);
552         runJob();
553         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
554         BatteryUtils.runDumpsysBatterySetLevel(100);
555         runJob();
556         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
557 
558         // Device is idle.
559         setScreenState(false);
560         runJob();
561         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
562         triggerJobIdle();
563         runJob();
564         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
565 
566         // Add network
567         setAirplaneMode(false);
568         setWifiState(true, mCm, mWifiManager);
569         setWifiMeteredState(false);
570         runJob();
571         assertTrue("New job didn't start in RESTRICTED bucket",
572                 mTestAppInterface.awaitJobStart(5_000));
573     }
574 
575     @Test
testJobsInNeverApp()576     public void testJobsInNeverApp() throws Exception {
577         assumeTrue("app standby not enabled", mAppStandbyEnabled);
578         assumeFalse("not testable in automotive device", mAutomotiveDevice);
579         assumeFalse("not testable in leanback device", mLeanbackOnly);
580 
581         BatteryUtils.runDumpsysBatteryUnplug();
582         setTestPackageStandbyBucket(Bucket.NEVER);
583         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
584         sendScheduleJobBroadcast(false);
585         assertFalse("New job started in NEVER bucket", mTestAppInterface.awaitJobStart(3_000));
586     }
587 
588     @Test
testUidActiveBypassesStandby()589     public void testUidActiveBypassesStandby() throws Exception {
590         assumeFalse("not testable in automotive device", mAutomotiveDevice);
591         assumeFalse("not testable in leanback device", mLeanbackOnly);
592 
593         BatteryUtils.runDumpsysBatteryUnplug();
594         setTestPackageStandbyBucket(Bucket.NEVER);
595         tempWhitelistTestApp(6_000);
596         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
597         sendScheduleJobBroadcast(false);
598         assertTrue("New job in uid-active app failed to start in NEVER standby",
599                 mTestAppInterface.awaitJobStart(4_000));
600     }
601 
602     @Test
testBatterySaverOff()603     public void testBatterySaverOff() throws Exception {
604         assumeFalse("not testable in automotive device", mAutomotiveDevice);
605         assumeFalse("not testable in leanback device", mLeanbackOnly);
606 
607         BatteryUtils.assumeBatterySaverFeature();
608 
609         BatteryUtils.runDumpsysBatteryUnplug();
610         BatteryUtils.enableBatterySaver(false);
611         sendScheduleJobBroadcast(false);
612         assertTrue("New job failed to start with battery saver OFF",
613                 mTestAppInterface.awaitJobStart(3_000));
614     }
615 
616     @Test
testBatterySaverOn()617     public void testBatterySaverOn() throws Exception {
618         assumeFalse("not testable in automotive device", mAutomotiveDevice);
619         assumeFalse("not testable in leanback device", mLeanbackOnly);
620 
621         BatteryUtils.assumeBatterySaverFeature();
622 
623         BatteryUtils.runDumpsysBatteryUnplug();
624         BatteryUtils.enableBatterySaver(true);
625         sendScheduleJobBroadcast(false);
626         assertFalse("New job started with battery saver ON",
627                 mTestAppInterface.awaitJobStart(3_000));
628     }
629 
630     @Test
testUidActiveBypassesBatterySaverOn()631     public void testUidActiveBypassesBatterySaverOn() throws Exception {
632         assumeFalse("not testable in automotive device", mAutomotiveDevice);
633         assumeFalse("not testable in leanback device", mLeanbackOnly);
634 
635         BatteryUtils.assumeBatterySaverFeature();
636 
637         BatteryUtils.runDumpsysBatteryUnplug();
638         BatteryUtils.enableBatterySaver(true);
639         tempWhitelistTestApp(6_000);
640         sendScheduleJobBroadcast(false);
641         assertTrue("New job in uid-active app failed to start with battery saver ON",
642                 mTestAppInterface.awaitJobStart(3_000));
643     }
644 
645     @Test
testBatterySaverOnThenUidActive()646     public void testBatterySaverOnThenUidActive() throws Exception {
647         assumeFalse("not testable in automotive device", mAutomotiveDevice);
648         assumeFalse("not testable in leanback device", mLeanbackOnly);
649 
650         BatteryUtils.assumeBatterySaverFeature();
651 
652         // Enable battery saver, and schedule a job. It shouldn't run.
653         BatteryUtils.runDumpsysBatteryUnplug();
654         BatteryUtils.enableBatterySaver(true);
655         sendScheduleJobBroadcast(false);
656         assertFalse("New job started with battery saver ON",
657                 mTestAppInterface.awaitJobStart(3_000));
658 
659         // Then make the UID active. Now the job should run.
660         tempWhitelistTestApp(120_000);
661         assertTrue("New job in uid-active app failed to start with battery saver OFF",
662                 mTestAppInterface.awaitJobStart(120_000));
663     }
664 
665     @Test
testExpeditedJobBypassesBatterySaverOn()666     public void testExpeditedJobBypassesBatterySaverOn() throws Exception {
667         assumeFalse("not testable in automotive device", mAutomotiveDevice);
668         assumeFalse("not testable in leanback device", mLeanbackOnly);
669 
670         BatteryUtils.assumeBatterySaverFeature();
671 
672         BatteryUtils.runDumpsysBatteryUnplug();
673         BatteryUtils.enableBatterySaver(true);
674         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
675         assertTrue("New expedited job failed to start with battery saver ON",
676                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
677     }
678 
679     @Test
testExpeditedJobBypassesBatterySaver_toggling()680     public void testExpeditedJobBypassesBatterySaver_toggling() throws Exception {
681         assumeFalse("not testable in automotive device", mAutomotiveDevice);
682         assumeFalse("not testable in leanback device", mLeanbackOnly);
683 
684         BatteryUtils.assumeBatterySaverFeature();
685 
686         BatteryUtils.runDumpsysBatteryUnplug();
687         BatteryUtils.enableBatterySaver(false);
688         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
689         assertTrue("New expedited job failed to start with battery saver ON",
690                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
691         BatteryUtils.enableBatterySaver(true);
692         assertFalse("Job stopped when battery saver turned on",
693                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
694     }
695 
696     @Test
testExpeditedJobBypassesDeviceIdle()697     public void testExpeditedJobBypassesDeviceIdle() throws Exception {
698         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
699 
700         toggleDozeState(true);
701         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
702         runJob();
703         assertTrue("Job did not start after scheduling",
704                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
705     }
706 
707     @Test
testExpeditedJobBypassesDeviceIdle_toggling()708     public void testExpeditedJobBypassesDeviceIdle_toggling() throws Exception {
709         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
710 
711         toggleDozeState(false);
712         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
713         runJob();
714         assertTrue("Job did not start after scheduling",
715                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
716         toggleDozeState(true);
717         assertFalse("Job stopped when device enabled turned on",
718                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
719     }
720 
721     @Test
testExpeditedJobDeferredAfterTimeoutInDoze()722     public void testExpeditedJobDeferredAfterTimeoutInDoze() throws Exception {
723         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
724         // Intentionally set a value below 1 minute to ensure the range checks work.
725         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(30_000L));
726 
727         toggleDozeState(true);
728         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
729         runJob();
730         assertTrue("Job did not start after scheduling",
731                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
732         // Don't put full minute as the timeout to give some leeway with test timing/processing.
733         assertFalse("Job stopped before min runtime limit",
734                 mTestAppInterface.awaitJobStop(55_000L));
735         assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(15_000L));
736         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
737                 mTestAppInterface.getLastParams().getStopReason());
738         // Should be rescheduled.
739         assertJobNotReady();
740         assertJobWaiting();
741         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
742         runJob();
743         assertFalse("Job started after timing out in Doze",
744                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
745 
746         // Should start when Doze is turned off.
747         toggleDozeState(false);
748         assertTrue("Job did not start after Doze turned off",
749                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
750     }
751 
752     @Test
testExpeditedJobDeferredAfterTimeoutInBatterySaver()753     public void testExpeditedJobDeferredAfterTimeoutInBatterySaver() throws Exception {
754         BatteryUtils.assumeBatterySaverFeature();
755 
756         // Intentionally set a value below 1 minute to ensure the range checks work.
757         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(47_000L));
758 
759         BatteryUtils.runDumpsysBatteryUnplug();
760         BatteryUtils.enableBatterySaver(true);
761         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
762         runJob();
763         assertTrue("Job did not start after scheduling",
764                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
765         // Don't put full minute as the timeout to give some leeway with test timing/processing.
766         assertFalse("Job stopped before min runtime limit",
767                 mTestAppInterface.awaitJobStop(55_000L));
768         assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(15_000L));
769         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
770                 mTestAppInterface.getLastParams().getStopReason());
771         // Should be rescheduled.
772         assertJobNotReady();
773         assertJobWaiting();
774         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
775         runJob();
776         assertFalse("Job started after timing out in battery saver",
777                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
778 
779         // Should start when battery saver is turned off.
780         BatteryUtils.enableBatterySaver(false);
781         assertTrue("Job did not start after battery saver turned off",
782                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
783     }
784 
785     @Test
testExpeditedJobDeferredAfterTimeout_DozeAndBatterySaver()786     public void testExpeditedJobDeferredAfterTimeout_DozeAndBatterySaver() throws Exception {
787         BatteryUtils.assumeBatterySaverFeature();
788         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
789         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
790 
791         BatteryUtils.runDumpsysBatteryUnplug();
792         toggleDozeState(true);
793         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
794         runJob();
795         assertTrue("Job did not start after scheduling",
796                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
797         // Don't put full minute as the timeout to give some leeway with test timing/processing.
798         assertFalse("Job stopped before min runtime limit",
799                 mTestAppInterface.awaitJobStop(55_000L));
800         assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(15_000L));
801         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
802                 mTestAppInterface.getLastParams().getStopReason());
803         // Should be rescheduled.
804         assertJobNotReady();
805         assertJobWaiting();
806         // Battery saver kicks in before Doze ends. Job shouldn't start while BS is on.
807         BatteryUtils.enableBatterySaver(true);
808         toggleDozeState(false);
809         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
810         runJob();
811         assertFalse("Job started while power restrictions active after timing out",
812                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
813 
814         // Should start when battery saver is turned off.
815         BatteryUtils.enableBatterySaver(false);
816         assertTrue("Job did not start after power restrictions turned off",
817                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
818     }
819 
820     @Test
testLongExpeditedJobStoppedByDoze()821     public void testLongExpeditedJobStoppedByDoze() throws Exception {
822         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
823         // Intentionally set a value below 1 minute to ensure the range checks work.
824         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(59_000L));
825 
826         toggleDozeState(false);
827         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
828         runJob();
829         assertTrue("Job did not start after scheduling",
830                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
831         // Should get to run past min runtime.
832         assertFalse("Job stopped after min runtime", mTestAppInterface.awaitJobStop(90_000L));
833 
834         // Should stop when Doze is turned on.
835         toggleDozeState(true);
836         assertTrue("Job did not stop after Doze turned on",
837                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
838         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
839                 mTestAppInterface.getLastParams().getStopReason());
840     }
841 
842     @Test
testLongExpeditedJobStoppedByBatterySaver()843     public void testLongExpeditedJobStoppedByBatterySaver() throws Exception {
844         BatteryUtils.assumeBatterySaverFeature();
845 
846         // Intentionally set a value below 1 minute to ensure the range checks work.
847         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(0L));
848 
849         BatteryUtils.runDumpsysBatteryUnplug();
850         BatteryUtils.enableBatterySaver(false);
851         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
852         runJob();
853         assertTrue("Job did not start after scheduling",
854                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
855         // Should get to run past min runtime.
856         assertFalse("Job stopped after runtime", mTestAppInterface.awaitJobStop(90_000L));
857 
858         // Should stop when battery saver is turned on.
859         BatteryUtils.enableBatterySaver(true);
860         assertTrue("Job did not stop after battery saver turned on",
861                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
862         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
863                 mTestAppInterface.getLastParams().getStopReason());
864     }
865 
866     @Test
testRestrictingStopReason_RestrictedBucket()867     public void testRestrictingStopReason_RestrictedBucket() throws Exception {
868         assumeTrue("app standby not enabled", mAppStandbyEnabled);
869         assumeFalse("not testable in automotive device", mAutomotiveDevice);
870         assumeFalse("not testable in leanback device", mLeanbackOnly);
871 
872         assumeTrue(mHasWifi);
873         ensureSavedWifiNetwork(mWifiManager);
874 
875         setRestrictedBucketEnabled(true);
876         setTestPackageStandbyBucket(Bucket.RESTRICTED);
877 
878         // Disable coalescing and the parole session
879         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
880         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
881 
882         // Satisfy all additional constraints.
883         setAirplaneMode(false);
884         setWifiState(true, mCm, mWifiManager);
885         setWifiMeteredState(false);
886         BatteryUtils.runDumpsysBatterySetPluggedIn(true);
887         BatteryUtils.runDumpsysBatterySetLevel(100);
888         setScreenState(false);
889         triggerJobIdle();
890 
891         // Toggle individual constraints
892 
893         // Connectivity
894         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
895         runJob();
896         assertTrue("New job didn't start in RESTRICTED bucket",
897                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
898         setAirplaneMode(true);
899         assertTrue("New job didn't stop when connectivity dropped",
900                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
901         assertEquals(JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY,
902                 mTestAppInterface.getLastParams().getStopReason());
903         setAirplaneMode(false);
904         setWifiState(true, mCm, mWifiManager);
905 
906         // Idle
907         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
908         runJob();
909         assertTrue("New job didn't start in RESTRICTED bucket",
910                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
911         setScreenState(true);
912         assertTrue("New job didn't stop when device no longer idle",
913                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
914         assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
915                 mTestAppInterface.getLastParams().getStopReason());
916         setScreenState(false);
917         triggerJobIdle();
918 
919         // Charging
920         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
921         runJob();
922         assertTrue("New job didn't start in RESTRICTED bucket",
923                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
924         BatteryUtils.runDumpsysBatteryUnplug();
925         assertTrue("New job didn't stop when device no longer charging",
926                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
927         assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
928                 mTestAppInterface.getLastParams().getStopReason());
929         BatteryUtils.runDumpsysBatterySetPluggedIn(true);
930         BatteryUtils.runDumpsysBatterySetLevel(100);
931 
932         // Battery not low
933         setScreenState(false);
934         triggerJobIdle();
935         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
936         runJob();
937         assertTrue("New job didn't start in RESTRICTED bucket",
938                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
939         BatteryUtils.runDumpsysBatterySetLevel(1);
940         assertTrue("New job didn't stop when battery too low",
941                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
942         assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
943                 mTestAppInterface.getLastParams().getStopReason());
944     }
945 
946     @Test
testRestrictingStopReason_Quota()947     public void testRestrictingStopReason_Quota() throws Exception {
948         // Reduce allowed time for testing.
949         mDeviceConfigStateHelper.set("qc_allowed_time_per_period_ms", "60000");
950         BatteryUtils.runDumpsysBatteryUnplug();
951         setTestPackageStandbyBucket(Bucket.RARE);
952 
953         sendScheduleJobBroadcast(false);
954         runJob();
955         assertTrue("New job didn't start",
956                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
957 
958         Thread.sleep(60000);
959 
960         assertTrue("New job didn't stop after using up quota",
961                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
962         assertEquals(JobParameters.STOP_REASON_QUOTA,
963                 mTestAppInterface.getLastParams().getStopReason());
964     }
965 
966     @Test
testRestrictingStopReason_BatterySaver()967     public void testRestrictingStopReason_BatterySaver() throws Exception {
968         BatteryUtils.assumeBatterySaverFeature();
969 
970         BatteryUtils.runDumpsysBatteryUnplug();
971         BatteryUtils.enableBatterySaver(false);
972         sendScheduleJobBroadcast(false);
973         runJob();
974         assertTrue("Job did not start after scheduling",
975                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
976 
977         BatteryUtils.enableBatterySaver(true);
978         assertTrue("Job did not stop on entering battery saver",
979                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
980         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
981                 mTestAppInterface.getLastParams().getStopReason());
982     }
983 
984     @Test
testRestrictingStopReason_Doze()985     public void testRestrictingStopReason_Doze() throws Exception {
986         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
987 
988         toggleDozeState(false);
989         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
990         runJob();
991         assertTrue("Job did not start after scheduling",
992                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
993 
994         toggleDozeState(true);
995         assertTrue("Job did not stop on entering doze",
996                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
997         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
998                 mTestAppInterface.getLastParams().getStopReason());
999     }
1000 
1001     @After
tearDown()1002     public void tearDown() throws Exception {
1003         AppOpsUtils.reset(TEST_APP_PACKAGE);
1004         // Lock thermal service to not throttling
1005         ThermalUtils.overrideThermalNotThrottling();
1006         if (mDeviceIdleEnabled) {
1007             toggleDozeState(false);
1008         }
1009         mTestAppInterface.cleanup();
1010         BatteryUtils.runDumpsysBatteryReset();
1011         BatteryUtils.enableBatterySaver(false);
1012         removeTestAppFromTempWhitelist();
1013 
1014         // Ensure that we leave WiFi in its previous state.
1015         if (mHasWifi && mWifiManager.isWifiEnabled() != mInitialWiFiState) {
1016             try {
1017                 setWifiState(mInitialWiFiState, mCm, mWifiManager);
1018             } catch (AssertionFailedError e) {
1019                 // Don't fail the test just because wifi state wasn't set in tearDown.
1020                 Log.e(TAG, "Failed to return wifi state to " + mInitialWiFiState, e);
1021             }
1022         }
1023         mDeviceConfigStateHelper.restoreOriginalValues();
1024         Settings.Global.putString(mContext.getContentResolver(),
1025                 Settings.Global.ENABLE_RESTRICTED_BUCKET, mInitialRestrictedBucketEnabled);
1026         if (isAirplaneModeOn() != mInitialAirplaneModeState) {
1027             setAirplaneMode(mInitialAirplaneModeState);
1028         }
1029         mUiDevice.executeShellCommand(
1030                 "cmd jobscheduler reset-execution-quota -u " + UserHandle.myUserId()
1031                         + " " + TEST_APP_PACKAGE);
1032 
1033         Settings.System.putString(
1034                 mContext.getContentResolver(), SCREEN_OFF_TIMEOUT, mInitialDisplayTimeout);
1035     }
1036 
setTestPackageRestricted(boolean restricted)1037     private void setTestPackageRestricted(boolean restricted) throws Exception {
1038         AppOpsUtils.setOpMode(TEST_APP_PACKAGE, "RUN_ANY_IN_BACKGROUND",
1039                 restricted ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED);
1040     }
1041 
setRestrictedBucketEnabled(boolean enabled)1042     private void setRestrictedBucketEnabled(boolean enabled) {
1043         Settings.Global.putString(mContext.getContentResolver(),
1044                 Settings.Global.ENABLE_RESTRICTED_BUCKET, enabled ? "1" : "0");
1045     }
1046 
isTestAppTempWhitelisted()1047     private boolean isTestAppTempWhitelisted() throws Exception {
1048         final String output = mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist").trim();
1049         for (String line : output.split("\n")) {
1050             if (line.contains("UID=" + mTestPackageUid)) {
1051                 return true;
1052             }
1053         }
1054         return false;
1055     }
1056 
sendScheduleJobBroadcast(boolean allowWhileIdle)1057     private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
1058         mTestAppInterface.scheduleJob(allowWhileIdle, NETWORK_TYPE_NONE, false);
1059     }
1060 
toggleDozeState(final boolean idle)1061     private void toggleDozeState(final boolean idle) throws Exception {
1062         mUiDevice.executeShellCommand("cmd deviceidle " + (idle ? "force-idle" : "unforce"));
1063         if (!idle) {
1064             // Make sure the device doesn't stay idle, even after unforcing.
1065             mUiDevice.executeShellCommand("cmd deviceidle motion");
1066         }
1067         assertTrue("Could not change device idle state to " + idle,
1068                 waitUntilTrue(SHELL_TIMEOUT, () -> {
1069                     synchronized (JobThrottlingTest.this) {
1070                         return mDeviceInDoze == idle;
1071                     }
1072                 }));
1073     }
1074 
tempWhitelistTestApp(long duration)1075     private void tempWhitelistTestApp(long duration) throws Exception {
1076         mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -d " + duration
1077                 + " " + TEST_APP_PACKAGE);
1078     }
1079 
makeTestPackageIdle()1080     private void makeTestPackageIdle() throws Exception {
1081         mUiDevice.executeShellCommand("am make-uid-idle --user current " + TEST_APP_PACKAGE);
1082     }
1083 
setTestPackageStandbyBucket(Bucket bucket)1084     void setTestPackageStandbyBucket(Bucket bucket) throws Exception {
1085         setTestPackageStandbyBucket(mUiDevice, bucket);
1086     }
1087 
setTestPackageStandbyBucket(UiDevice uiDevice, Bucket bucket)1088     static void setTestPackageStandbyBucket(UiDevice uiDevice, Bucket bucket) throws Exception {
1089         final String bucketName;
1090         switch (bucket) {
1091             case ACTIVE:
1092                 bucketName = "active";
1093                 break;
1094             case WORKING_SET:
1095                 bucketName = "working";
1096                 break;
1097             case FREQUENT:
1098                 bucketName = "frequent";
1099                 break;
1100             case RARE:
1101                 bucketName = "rare";
1102                 break;
1103             case RESTRICTED:
1104                 bucketName = "restricted";
1105                 break;
1106             case NEVER:
1107                 bucketName = "never";
1108                 break;
1109             default:
1110                 throw new IllegalArgumentException("Requested unknown bucket " + bucket);
1111         }
1112         uiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE
1113                 + " " + bucketName);
1114     }
1115 
removeTestAppFromTempWhitelist()1116     private boolean removeTestAppFromTempWhitelist() throws Exception {
1117         mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -r " + TEST_APP_PACKAGE);
1118         return waitUntilTrue(SHELL_TIMEOUT, () -> !isTestAppTempWhitelisted());
1119     }
1120 
1121     /**
1122      * Set the screen state.
1123      */
setScreenState(boolean on)1124     private void setScreenState(boolean on) throws Exception {
1125         if (on) {
1126             mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
1127             mUiDevice.executeShellCommand("wm dismiss-keyguard");
1128         } else {
1129             mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
1130         }
1131         // Wait a little bit to make sure the screen state has changed.
1132         Thread.sleep(2_000);
1133     }
1134 
1135     /**
1136      * Trigger job idle (not device idle);
1137      */
triggerJobIdle()1138     private void triggerJobIdle() throws Exception {
1139         mUiDevice.executeShellCommand("cmd activity idle-maintenance");
1140         // Wait a moment to let that happen before proceeding.
1141         Thread.sleep(2_000);
1142     }
1143 
1144     /** Asks (not forces) JobScheduler to run the job if constraints are met. */
runJob()1145     private void runJob() throws Exception {
1146         // Since connectivity is a functional constraint, calling the "run" command without force
1147         // will only get the job to run if the constraint is satisfied.
1148         mUiDevice.executeShellCommand("cmd jobscheduler run -s"
1149                 + " -u " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE + " " + mTestJobId);
1150     }
1151 
isAirplaneModeOn()1152     private boolean isAirplaneModeOn() throws IOException {
1153         final String output =
1154                 mUiDevice.executeShellCommand("cmd connectivity airplane-mode").trim();
1155         return "enabled".equals(output);
1156     }
1157 
setAirplaneMode(boolean on)1158     private void setAirplaneMode(boolean on) throws Exception {
1159         final CallbackAsserter airplaneModeBroadcastAsserter = CallbackAsserter.forBroadcast(
1160                 new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED));
1161         mUiDevice.executeShellCommand(
1162                 "cmd connectivity airplane-mode " + (on ? "enable" : "disable"));
1163         airplaneModeBroadcastAsserter.assertCalled("Didn't get airplane mode changed broadcast",
1164                 15 /* 15 seconds */);
1165         if (!on && mHasWifi) {
1166             // Force wifi to connect ASAP.
1167             mUiDevice.executeShellCommand("svc wifi enable");
1168             //noinspection deprecation
1169             SystemUtil.runWithShellPermissionIdentity(mWifiManager::reconnect,
1170                     android.Manifest.permission.NETWORK_SETTINGS);
1171         }
1172         waitUntil("Networks didn't change to " + (!on ? "on" : "off"), 60 /* seconds */,
1173                 () -> {
1174                     if (on) {
1175                         return mCm.getActiveNetwork() == null
1176                                 && (!mHasWifi || !isWiFiConnected(mCm, mWifiManager));
1177                     } else {
1178                         return mCm.getActiveNetwork() != null;
1179                     }
1180                 });
1181         // Wait some time for the network changes to propagate. Can't use
1182         // waitUntil(isAirplaneModeOn() == on) because the response quickly gives the new
1183         // airplane mode status even though the network changes haven't propagated all the way to
1184         // JobScheduler.
1185         Thread.sleep(5000);
1186     }
1187 
unquoteSSID(String ssid)1188     private static String unquoteSSID(String ssid) {
1189         // SSID is returned surrounded by quotes if it can be decoded as UTF-8.
1190         // Otherwise it's guaranteed not to start with a quote.
1191         if (ssid.charAt(0) == '"') {
1192             return ssid.substring(1, ssid.length() - 1);
1193         } else {
1194             return ssid;
1195         }
1196     }
1197 
getWifiSSID()1198     private String getWifiSSID() {
1199         final AtomicReference<String> ssid = new AtomicReference<>();
1200         SystemUtil.runWithShellPermissionIdentity(() -> {
1201             ssid.set(mWifiManager.getConnectionInfo().getSSID());
1202         }, Manifest.permission.ACCESS_FINE_LOCATION);
1203         return unquoteSSID(ssid.get());
1204     }
1205 
1206     // Returns "true", "false" or "none"
getWifiMeteredStatus(String ssid)1207     private String getWifiMeteredStatus(String ssid) {
1208         // Interestingly giving the SSID as an argument to list wifi-networks
1209         // only works iff the network in question has the "false" policy.
1210         // Also unfortunately runShellCommand does not pass the command to the interpreter
1211         // so it's not possible to | grep the ssid.
1212         final String command = "cmd netpolicy list wifi-networks";
1213         final String policyString = SystemUtil.runShellCommand(command);
1214 
1215         final Matcher m = Pattern.compile("^" + ssid + ";(true|false|none)$",
1216                 Pattern.MULTILINE | Pattern.UNIX_LINES).matcher(policyString);
1217         if (!m.find()) {
1218             fail("Unexpected format from cmd netpolicy (when looking for " + ssid + "): "
1219                     + policyString);
1220         }
1221         return m.group(1);
1222     }
1223 
setWifiMeteredState(boolean metered)1224     private void setWifiMeteredState(boolean metered) throws Exception {
1225         if (metered) {
1226             // Make sure unmetered cellular networks don't interfere.
1227             setAirplaneMode(true);
1228             setWifiState(true, mCm, mWifiManager);
1229         }
1230         final String ssid = getWifiSSID();
1231         setWifiMeteredState(ssid, metered ? "true" : "false");
1232     }
1233 
1234     // metered should be "true", "false" or "none"
setWifiMeteredState(String ssid, String metered)1235     private void setWifiMeteredState(String ssid, String metered) {
1236         if (metered.equals(getWifiMeteredStatus(ssid))) {
1237             return;
1238         }
1239         SystemUtil.runShellCommand("cmd netpolicy set metered-network " + ssid + " " + metered);
1240         assertEquals(getWifiMeteredStatus(ssid), metered);
1241     }
1242 
getJobState()1243     private String getJobState() throws Exception {
1244         return mUiDevice.executeShellCommand("cmd jobscheduler get-job-state --user cur "
1245                 + TEST_APP_PACKAGE + " " + mTestJobId).trim();
1246     }
1247 
assertJobWaiting()1248     private void assertJobWaiting() throws Exception {
1249         String state = getJobState();
1250         assertTrue("Job unexpectedly not waiting, in state: " + state, state.contains("waiting"));
1251     }
1252 
assertJobNotReady()1253     private void assertJobNotReady() throws Exception {
1254         String state = getJobState();
1255         assertFalse("Job unexpectedly ready, in state: " + state, state.contains("ready"));
1256     }
1257 
assertJobReady()1258     private void assertJobReady() throws Exception {
1259         String state = getJobState();
1260         assertTrue("Job unexpectedly not ready, in state: " + state, state.contains("ready"));
1261     }
1262 
waitUntilTrue(long maxWait, Condition condition)1263     private boolean waitUntilTrue(long maxWait, Condition condition) throws Exception {
1264         final long deadLine = SystemClock.uptimeMillis() + maxWait;
1265         do {
1266             Thread.sleep(POLL_INTERVAL);
1267         } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
1268         return condition.isTrue();
1269     }
1270 
1271     private interface Condition {
isTrue()1272         boolean isTrue() throws Exception;
1273     }
1274 }
1275