• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.TestAppInterface.TEST_APP_PACKAGE;
22 
23 import static com.android.compatibility.common.util.TestUtils.waitUntil;
24 
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertTrue;
28 import static org.junit.Assume.assumeFalse;
29 import static org.junit.Assume.assumeTrue;
30 
31 import android.app.AppOpsManager;
32 import android.app.job.JobInfo;
33 import android.app.job.JobParameters;
34 import android.content.Context;
35 import android.content.pm.PackageManager;
36 import android.jobscheduler.cts.jobtestapp.TestJobSchedulerReceiver;
37 import android.os.PowerManager;
38 import android.os.SystemClock;
39 import android.os.Temperature;
40 import android.os.UserHandle;
41 import android.platform.test.annotations.RequiresDevice;
42 import android.provider.DeviceConfig;
43 import android.provider.Settings;
44 import android.util.Log;
45 
46 import androidx.test.InstrumentationRegistry;
47 import androidx.test.filters.LargeTest;
48 import androidx.test.runner.AndroidJUnit4;
49 import androidx.test.uiautomator.UiDevice;
50 
51 import com.android.compatibility.common.util.AppOpsUtils;
52 import com.android.compatibility.common.util.AppStandbyUtils;
53 import com.android.compatibility.common.util.BatteryUtils;
54 import com.android.compatibility.common.util.DeviceConfigStateHelper;
55 import com.android.compatibility.common.util.ThermalUtils;
56 
57 import org.junit.After;
58 import org.junit.Before;
59 import org.junit.Test;
60 import org.junit.runner.RunWith;
61 
62 import java.util.Map;
63 
64 /**
65  * Tests related to job throttling -- device idle, app standby and battery saver.
66  */
67 @RunWith(AndroidJUnit4.class)
68 @LargeTest
69 public class JobThrottlingTest {
70     private static final String TAG = JobThrottlingTest.class.getSimpleName();
71     private static final long BACKGROUND_JOBS_EXPECTED_DELAY = 3_000;
72     private static final long POLL_INTERVAL = 500;
73     private static final long DEFAULT_WAIT_TIMEOUT = 5000;
74     private static final long SHELL_TIMEOUT = 3_000;
75     // TODO: mark Settings.System.SCREEN_OFF_TIMEOUT as @TestApi
76     private static final String SCREEN_OFF_TIMEOUT = "screen_off_timeout";
77 
78     enum Bucket {
79         ACTIVE,
80         WORKING_SET,
81         FREQUENT,
82         RARE,
83         RESTRICTED,
84         NEVER
85     }
86 
87     private Context mContext;
88     private UiDevice mUiDevice;
89     private NetworkingHelper mNetworkingHelper;
90     private PowerManager mPowerManager;
91     private int mTestJobId;
92     private int mTestPackageUid;
93     private boolean mDeviceIdleEnabled;
94     private boolean mDeviceLightIdleEnabled;
95     private boolean mAppStandbyEnabled;
96     private String mInitialActivityManagerConstants;
97     private String mInitialDisplayTimeout;
98     private String mInitialBatteryStatsConstants;
99     private boolean mAutomotiveDevice;
100     private boolean mLeanbackOnly;
101 
102     private TestAppInterface mTestAppInterface;
103     private DeviceConfigStateHelper mDeviceConfigStateHelper;
104     private DeviceConfigStateHelper mActivityManagerDeviceConfigStateHelper;
105     private DeviceConfigStateHelper mTareDeviceConfigStateHelper;
106 
isDeviceIdleEnabled(UiDevice uiDevice)107     private static boolean isDeviceIdleEnabled(UiDevice uiDevice) throws Exception {
108         final String output = uiDevice.executeShellCommand("cmd deviceidle enabled deep").trim();
109         return Integer.parseInt(output) != 0;
110     }
111 
isDeviceLightIdleEnabled(UiDevice uiDevice)112     private static boolean isDeviceLightIdleEnabled(UiDevice uiDevice) throws Exception {
113         final String output = uiDevice.executeShellCommand("cmd deviceidle enabled light").trim();
114         return Integer.parseInt(output) != 0;
115     }
116 
117     @Before
setUp()118     public void setUp() throws Exception {
119         mContext = InstrumentationRegistry.getTargetContext();
120         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
121         mNetworkingHelper =
122                 new NetworkingHelper(InstrumentationRegistry.getInstrumentation(), mContext);
123         mPowerManager = mContext.getSystemService(PowerManager.class);
124         mTestPackageUid = mContext.getPackageManager().getPackageUid(TEST_APP_PACKAGE, 0);
125         mTestJobId = (int) (SystemClock.uptimeMillis() / 1000);
126         mTestAppInterface = new TestAppInterface(mContext, mTestJobId);
127         assertFalse("Test package already in temp whitelist", isTestAppTempWhitelisted());
128         makeTestPackageIdle();
129         mDeviceIdleEnabled = isDeviceIdleEnabled(mUiDevice);
130         mDeviceLightIdleEnabled = isDeviceLightIdleEnabled(mUiDevice);
131         if (mDeviceIdleEnabled || mDeviceLightIdleEnabled) {
132             // Make sure the device isn't dozing since it will affect execution of regular jobs
133             toggleDozeState(false);
134         }
135         mAppStandbyEnabled = AppStandbyUtils.isAppStandbyEnabled();
136         if (mAppStandbyEnabled) {
137             setTestPackageStandbyBucket(Bucket.ACTIVE);
138         } else {
139             Log.w(TAG, "App standby not enabled on test device");
140         }
141         mInitialBatteryStatsConstants = Settings.Global.getString(mContext.getContentResolver(),
142                 Settings.Global.BATTERY_STATS_CONSTANTS);
143         // Make sure ACTION_CHARGING is sent immediately.
144         Settings.Global.putString(mContext.getContentResolver(),
145                 Settings.Global.BATTERY_STATS_CONSTANTS, "battery_charged_delay_ms=0");
146         // Make sure test jobs can run regardless of bucket.
147         mDeviceConfigStateHelper =
148                 new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
149         mDeviceConfigStateHelper.set(
150                 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER)
151                         .setInt("min_ready_non_active_jobs_count", 0)
152                         .setBoolean("fc_enable_flexibility", false).build());
153         mActivityManagerDeviceConfigStateHelper =
154                 new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER);
155         mTareDeviceConfigStateHelper = new DeviceConfigStateHelper(DeviceConfig.NAMESPACE_TARE);
156         toggleAutoRestrictedBucketOnBgRestricted(false);
157         // Make sure the screen doesn't turn off when the test turns it on.
158         mInitialDisplayTimeout =
159                 Settings.System.getString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT);
160         Settings.System.putString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT, "300000");
161 
162         mInitialActivityManagerConstants = Settings.Global.getString(mContext.getContentResolver(),
163                 Settings.Global.ACTIVITY_MANAGER_CONSTANTS);
164         // Delete any activity manager constants overrides so that the default transition time
165         // of 60 seconds for UID active to UID idle is used.
166         Settings.Global.putString(mContext.getContentResolver(),
167                 Settings.Global.ACTIVITY_MANAGER_CONSTANTS, null);
168 
169         // In automotive device, always-on screen and endless battery charging are assumed.
170         mAutomotiveDevice =
171                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
172         // In leanback devices, it is assumed that there is no battery.
173         mLeanbackOnly =
174                 mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY);
175         if (mAutomotiveDevice || mLeanbackOnly) {
176             setScreenState(true);
177             // TODO(b/159176758): make sure that initial power supply is on.
178             setChargingState(true);
179         }
180 
181         // Kill as many things in the background as possible so we avoid LMK interfering with the
182         // test.
183         mUiDevice.executeShellCommand("am kill-all");
184     }
185 
186     @Test
testAllowWhileIdleJobInTempwhitelist()187     public void testAllowWhileIdleJobInTempwhitelist() throws Exception {
188         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
189 
190         toggleDozeState(true);
191         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
192         sendScheduleJobBroadcast(true);
193         assertFalse("Job started without being tempwhitelisted",
194                 mTestAppInterface.awaitJobStart(5_000));
195         tempWhitelistTestApp(5_000);
196         assertTrue("Job with allow_while_idle flag did not start when the app was tempwhitelisted",
197                 mTestAppInterface.awaitJobStart(5_000));
198     }
199 
200     @Test
testForegroundJobsStartImmediately()201     public void testForegroundJobsStartImmediately() throws Exception {
202         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
203 
204         sendScheduleJobBroadcast(false);
205         runJob();
206         assertTrue("Job did not start after scheduling",
207                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
208         toggleDozeState(true);
209         assertTrue("Job did not stop on entering doze",
210                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
211         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
212         // The adb command will force idle even with the screen on, so we need to turn Doze off
213         // explicitly.
214         toggleDozeState(false);
215         // Turn the screen on to ensure the test app ends up in TOP.
216         setScreenState(true);
217         mTestAppInterface.startAndKeepTestActivity();
218         assertTrue("Job for foreground app did not start immediately when device exited doze",
219                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
220     }
221 
222     @Test
testBackgroundJobsDelayed()223     public void testBackgroundJobsDelayed() 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         toggleDozeState(false);
235         assertFalse("Job for background app started immediately when device exited doze",
236                 mTestAppInterface.awaitJobStart(2000));
237         Thread.sleep(BACKGROUND_JOBS_EXPECTED_DELAY - 2000);
238         assertTrue("Job for background app did not start after the expected delay of "
239                         + BACKGROUND_JOBS_EXPECTED_DELAY + "ms",
240                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
241     }
242 
243     @Test
testJobStoppedWhenRestricted()244     public void testJobStoppedWhenRestricted() throws Exception {
245         sendScheduleJobBroadcast(false);
246         runJob();
247         assertTrue("Job did not start after scheduling",
248                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
249         toggleAutoRestrictedBucketOnBgRestricted(true);
250         setTestPackageRestricted(true);
251         assertFalse("Job stopped after test app was restricted with auto-restricted-bucket on",
252                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
253         toggleAutoRestrictedBucketOnBgRestricted(false);
254         assertTrue("Job did not stop after test app was restricted",
255                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
256         assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
257                 mTestAppInterface.getLastParams().getStopReason());
258     }
259 
260     @Test
testRestrictedJobStartedWhenUnrestricted()261     public void testRestrictedJobStartedWhenUnrestricted() throws Exception {
262         setTestPackageRestricted(true);
263         sendScheduleJobBroadcast(false);
264         assertFalse("Job started for restricted app",
265                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
266         setTestPackageRestricted(false);
267         assertTrue("Job did not start when app was unrestricted",
268                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
269     }
270 
271     @Test
testRestrictedJobAllowedWhenUidActive()272     public void testRestrictedJobAllowedWhenUidActive() throws Exception {
273         setTestPackageRestricted(true);
274         sendScheduleJobBroadcast(false);
275         assertFalse("Job started for restricted app",
276                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
277         // Turn the screen on to ensure the app gets into the TOP state.
278         setScreenState(true);
279         mTestAppInterface.startAndKeepTestActivity(true);
280         assertTrue("Job did not start when app had an activity",
281                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
282 
283         mTestAppInterface.closeActivity();
284         // Don't put full minute as the timeout to give some leeway with test timing/processing.
285         assertFalse("Job stopped within grace period after activity closed",
286                 mTestAppInterface.awaitJobStop(55_000L));
287         assertTrue("Job did not stop after grace period ended",
288                 mTestAppInterface.awaitJobStop(15_000L));
289         assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
290                 mTestAppInterface.getLastParams().getStopReason());
291     }
292 
293     @Test
testRestrictedJobAllowedWhenAutoRestrictedBucketFeatureOn()294     public void testRestrictedJobAllowedWhenAutoRestrictedBucketFeatureOn() throws Exception {
295         setTestPackageRestricted(true);
296         sendScheduleJobBroadcast(false);
297         assertFalse("Job started for restricted app",
298                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
299         toggleAutoRestrictedBucketOnBgRestricted(true);
300         assertTrue("Job did not start after scheduling",
301                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
302     }
303 
304     @Test
testEJStoppedWhenRestricted()305     public void testEJStoppedWhenRestricted() throws Exception {
306         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
307         runJob();
308         assertTrue("Job did not start after scheduling",
309                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
310         toggleAutoRestrictedBucketOnBgRestricted(true);
311         setTestPackageRestricted(true);
312         assertFalse("Job stopped after test app was restricted with auto-restricted-bucket on",
313                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
314         toggleAutoRestrictedBucketOnBgRestricted(false);
315         assertTrue("Job did not stop after test app was restricted",
316                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
317         assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
318                 mTestAppInterface.getLastParams().getStopReason());
319     }
320 
321     @Test
testRestrictedEJStartedWhenUnrestricted()322     public void testRestrictedEJStartedWhenUnrestricted() throws Exception {
323         setTestPackageRestricted(true);
324         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
325         assertFalse("Job started for restricted app",
326                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
327         setTestPackageRestricted(false);
328         assertTrue("Job did not start when app was unrestricted",
329                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
330     }
331 
332     @Test
testRestrictedEJAllowedWhenUidActive()333     public void testRestrictedEJAllowedWhenUidActive() throws Exception {
334         setTestPackageRestricted(true);
335         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
336         assertFalse("Job started for restricted app",
337                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
338         // Turn the screen on to ensure the app gets into the TOP state.
339         setScreenState(true);
340         mTestAppInterface.startAndKeepTestActivity(true);
341         assertTrue("Job did not start when app had an activity",
342                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
343 
344         mTestAppInterface.closeActivity();
345         // Don't put full minute as the timeout to give some leeway with test timing/processing.
346         assertFalse("Job stopped within grace period after activity closed",
347                 mTestAppInterface.awaitJobStop(55_000L));
348         assertTrue("Job did not stop after grace period ended",
349                 mTestAppInterface.awaitJobStop(15_000L));
350         assertEquals(JobParameters.STOP_REASON_BACKGROUND_RESTRICTION,
351                 mTestAppInterface.getLastParams().getStopReason());
352     }
353 
354     @Test
testRestrictedEJAllowedWhenAutoRestrictedBucketFeatureOn()355     public void testRestrictedEJAllowedWhenAutoRestrictedBucketFeatureOn() throws Exception {
356         setTestPackageRestricted(true);
357         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
358         assertFalse("Job started for restricted app",
359                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
360 
361         toggleAutoRestrictedBucketOnBgRestricted(true);
362         assertTrue("Job did not start when app was background unrestricted",
363                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
364     }
365 
366     @Test
testBackgroundRegJobsThermal()367     public void testBackgroundRegJobsThermal() throws Exception {
368         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
369         runJob();
370         assertTrue("Job did not start after scheduling",
371                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
372 
373         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_LIGHT);
374         assertFalse("Job stopped below thermal throttling threshold",
375                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
376 
377         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
378         assertTrue("Job did not stop on thermal throttling",
379                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
380         final long jobStopTime = System.currentTimeMillis();
381 
382         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
383         runJob();
384         assertFalse("Job started above thermal throttling threshold",
385                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
386 
387         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_EMERGENCY);
388         runJob();
389         assertFalse("Job started above thermal throttling threshold",
390                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
391 
392         Thread.sleep(Math.max(0, TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF
393                 - (System.currentTimeMillis() - jobStopTime)));
394         ThermalUtils.overrideThermalNotThrottling();
395         runJob();
396         assertTrue("Job did not start back from throttling",
397                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
398     }
399 
400     @Test
testBackgroundEJsThermal()401     public void testBackgroundEJsThermal() throws Exception {
402         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, true);
403         runJob();
404         assertTrue("Job did not start after scheduling",
405                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
406 
407         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_MODERATE);
408         assertFalse("Job stopped below thermal throttling threshold",
409                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
410 
411         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
412         assertTrue("Job did not stop on thermal throttling",
413                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
414         final long jobStopTime = System.currentTimeMillis();
415 
416         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
417         runJob();
418         assertFalse("Job started above thermal throttling threshold",
419                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
420 
421         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_EMERGENCY);
422         runJob();
423         assertFalse("Job started above thermal throttling threshold",
424                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
425 
426         Thread.sleep(Math.max(0, TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF
427                 - (System.currentTimeMillis() - jobStopTime)));
428         ThermalUtils.overrideThermalNotThrottling();
429         runJob();
430         assertTrue("Job did not start back from throttling",
431                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
432     }
433 
434     @Test
testBackgroundUIJsThermal()435     public void testBackgroundUIJsThermal() throws Exception {
436         try (TestNotificationListener.NotificationHelper notificationHelper =
437                      new TestNotificationListener.NotificationHelper(
438                              mContext, TestAppInterface.TEST_APP_PACKAGE)) {
439             mTestAppInterface.postUiInitiatingNotification(
440                     Map.of(TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true),
441                     Map.of(TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY));
442             notificationHelper.clickNotification();
443 
444             assertTrue("Job did not start after scheduling",
445                     mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
446 
447             ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_MODERATE);
448             assertFalse("Job stopped below thermal throttling threshold",
449                     mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
450 
451             ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
452             assertTrue("Job did not stop on thermal throttling",
453                     mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
454             final long jobStopTime = System.currentTimeMillis();
455 
456             ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
457             runJob();
458             assertFalse("Job started above thermal throttling threshold",
459                     mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
460 
461             ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_EMERGENCY);
462             runJob();
463             assertFalse("Job started above thermal throttling threshold",
464                     mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
465 
466             Thread.sleep(Math.max(0, TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF
467                     - (System.currentTimeMillis() - jobStopTime)));
468             ThermalUtils.overrideThermalNotThrottling();
469             runJob();
470             assertTrue("Job did not start back from throttling",
471                     mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
472         }
473     }
474 
475     @Test
testForegroundJobsThermal()476     public void testForegroundJobsThermal() throws Exception {
477         // Turn the screen on to ensure the app gets into the TOP state.
478         setScreenState(true);
479         mTestAppInterface.startAndKeepTestActivity(true);
480         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
481         runJob();
482         assertTrue("Job did not start after scheduling",
483                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
484 
485         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_MODERATE);
486         assertFalse("Job stopped below thermal throttling threshold",
487                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
488 
489         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_SEVERE);
490         assertFalse("Job stopped despite being TOP app",
491                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
492 
493         ThermalUtils.overrideThermalStatus(Temperature.THROTTLING_CRITICAL);
494         assertFalse("Job stopped despite being TOP app",
495                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
496     }
497 
498     /** Tests that apps in the RESTRICTED bucket still get their one parole session per day. */
499     @Test
testJobsInRestrictedBucket_ParoleSession()500     public void testJobsInRestrictedBucket_ParoleSession() throws Exception {
501         assumeTrue("app standby not enabled", mAppStandbyEnabled);
502         assumeTrue("device doesn't have battery", BatteryUtils.hasBattery());
503 
504         // This test is designed for the old quota system.
505         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
506 
507         // Disable coalescing
508         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
509 
510         setScreenState(true);
511 
512         setChargingState(false);
513         setTestPackageStandbyBucket(Bucket.RESTRICTED);
514         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
515         sendScheduleJobBroadcast(false);
516         runJob();
517         assertTrue("Parole job didn't start in RESTRICTED bucket",
518                 mTestAppInterface.awaitJobStart(3_000));
519 
520         sendScheduleJobBroadcast(false);
521         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
522     }
523 
524     /**
525      * Tests that apps in the RESTRICTED bucket have their parole sessions properly counted even
526      * when charging (but not idle).
527      */
528     @Test
testJobsInRestrictedBucket_CorrectParoleWhileCharging()529     public void testJobsInRestrictedBucket_CorrectParoleWhileCharging() throws Exception {
530         assumeTrue("app standby not enabled", mAppStandbyEnabled);
531         assumeFalse("not testable in automotive device", mAutomotiveDevice);
532         assumeFalse("not testable in leanback device", mLeanbackOnly);
533 
534         // This test is designed for the old quota system.
535         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
536 
537         // Disable coalescing
538         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
539         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "1");
540 
541         setScreenState(true);
542         setChargingState(true);
543         BatteryUtils.runDumpsysBatterySetLevel(100);
544 
545         setTestPackageStandbyBucket(Bucket.RESTRICTED);
546         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
547         sendScheduleJobBroadcast(false);
548         runJob();
549         assertTrue("Parole job didn't start in RESTRICTED bucket",
550                 mTestAppInterface.awaitJobStart(3_000));
551 
552         sendScheduleJobBroadcast(false);
553         assertFalse("New job started in RESTRICTED bucket after parole used",
554                 mTestAppInterface.awaitJobStart(3_000));
555     }
556 
557     /**
558      * Tests that apps in the RESTRICTED bucket that have used their one parole session per day
559      * don't get to run again until the device is charging + idle.
560      */
561     @Test
testJobsInRestrictedBucket_DeferredUntilFreeResources()562     public void testJobsInRestrictedBucket_DeferredUntilFreeResources() throws Exception {
563         assumeTrue("app standby not enabled", mAppStandbyEnabled);
564         assumeTrue("device doesn't have battery", BatteryUtils.hasBattery());
565 
566         // This test is designed for the old quota system.
567         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
568 
569         // Disable coalescing
570         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
571 
572         setScreenState(true);
573 
574         setChargingState(false);
575         setTestPackageStandbyBucket(Bucket.RESTRICTED);
576         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
577         sendScheduleJobBroadcast(false);
578         runJob();
579         assertTrue("Parole job didn't start in RESTRICTED bucket",
580                 mTestAppInterface.awaitJobStart(3_000));
581 
582         sendScheduleJobBroadcast(false);
583         assertFalse("New job started in RESTRICTED bucket after parole used",
584                 mTestAppInterface.awaitJobStart(3_000));
585 
586         setChargingState(true);
587         BatteryUtils.runDumpsysBatterySetLevel(100);
588         assertFalse("New job started in RESTRICTED bucket after parole when charging but not idle",
589                 mTestAppInterface.awaitJobStart(3_000));
590 
591         setScreenState(false);
592         triggerJobIdle();
593         assertTrue("Job didn't start in RESTRICTED bucket when charging + idle",
594                 mTestAppInterface.awaitJobStart(3_000));
595 
596         // Make sure job can be stopped and started again when charging + idle
597         sendScheduleJobBroadcast(false);
598         runJob();
599         assertTrue("Job didn't restart in RESTRICTED bucket when charging + idle",
600                 mTestAppInterface.awaitJobStart(3_000));
601     }
602 
603     @Test
testJobsInRestrictedBucket_NoRequiredNetwork()604     public void testJobsInRestrictedBucket_NoRequiredNetwork() throws Exception {
605         assumeTrue("app standby not enabled", mAppStandbyEnabled);
606         assumeTrue("device doesn't have battery", BatteryUtils.hasBattery());
607         assumeFalse("not testable, since ethernet is connected", hasEthernetConnection());
608 
609         // This test is designed for the old quota system.
610         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
611 
612         // Disable coalescing and the parole session
613         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
614         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
615 
616         mNetworkingHelper.setAllNetworksEnabled(false);
617         setScreenState(true);
618 
619         setChargingState(false);
620         setTestPackageStandbyBucket(Bucket.RESTRICTED);
621         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
622         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
623         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
624 
625         // Slowly add back required bucket constraints.
626 
627         // Battery charging and high.
628         setChargingState(true);
629         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
630         BatteryUtils.runDumpsysBatterySetLevel(100);
631         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
632 
633         // Device is idle.
634         setScreenState(false);
635         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
636         triggerJobIdle();
637         assertTrue("New job didn't start in RESTRICTED bucket",
638                 mTestAppInterface.awaitJobStart(3_000));
639     }
640 
641     @RequiresDevice // Emulators don't always have access to wifi/network
642     @Test
testJobsInRestrictedBucket_WithRequiredNetwork()643     public void testJobsInRestrictedBucket_WithRequiredNetwork() throws Exception {
644         assumeTrue("app standby not enabled", mAppStandbyEnabled);
645         assumeTrue("device doesn't have battery", BatteryUtils.hasBattery());
646         assumeFalse("not testable, since ethernet is connected", hasEthernetConnection());
647         assumeTrue(mNetworkingHelper.hasWifiFeature());
648         mNetworkingHelper.ensureSavedWifiNetwork();
649 
650         // This test is designed for the old quota system.
651         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
652 
653         // Disable coalescing and the parole session
654         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
655         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
656 
657         mNetworkingHelper.setAllNetworksEnabled(false);
658         setScreenState(true);
659 
660         setChargingState(false);
661         setTestPackageStandbyBucket(Bucket.RESTRICTED);
662         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
663         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
664         runJob();
665         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
666 
667         // Slowly add back required bucket constraints.
668 
669         // Battery charging and high.
670         setChargingState(true);
671         runJob();
672         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
673         BatteryUtils.runDumpsysBatterySetLevel(100);
674         runJob();
675         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
676 
677         // Device is idle.
678         setScreenState(false);
679         runJob();
680         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
681         triggerJobIdle();
682         runJob();
683         assertFalse("New job started in RESTRICTED bucket", mTestAppInterface.awaitJobStart(3_000));
684 
685         // Add network
686         mNetworkingHelper.setAllNetworksEnabled(true);
687         mNetworkingHelper.setWifiMeteredState(false);
688         runJob();
689         assertTrue("New job didn't start in RESTRICTED bucket",
690                 mTestAppInterface.awaitJobStart(5_000));
691     }
692 
693     @Test
testJobsInNeverApp()694     public void testJobsInNeverApp() throws Exception {
695         assumeTrue("app standby not enabled", mAppStandbyEnabled);
696         assumeTrue("device doesn't have battery", BatteryUtils.hasBattery());
697 
698         // This test is designed for the old quota system.
699         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
700 
701         setChargingState(false);
702         setTestPackageStandbyBucket(Bucket.NEVER);
703         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
704         sendScheduleJobBroadcast(false);
705         assertFalse("New job started in NEVER bucket", mTestAppInterface.awaitJobStart(3_000));
706     }
707 
708     @Test
testUidActiveBypassesStandby()709     public void testUidActiveBypassesStandby() throws Exception {
710         assumeTrue("app standby not enabled", mAppStandbyEnabled);
711         assumeTrue("device doesn't have battery", BatteryUtils.hasBattery());
712 
713         // This test is designed for the old quota system.
714         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
715 
716         setChargingState(false);
717         setTestPackageStandbyBucket(Bucket.NEVER);
718         tempWhitelistTestApp(6_000);
719         Thread.sleep(DEFAULT_WAIT_TIMEOUT);
720         sendScheduleJobBroadcast(false);
721         assertTrue("New job in uid-active app failed to start in NEVER standby",
722                 mTestAppInterface.awaitJobStart(4_000));
723     }
724 
725     @Test
testBatterySaverOff()726     public void testBatterySaverOff() throws Exception {
727         BatteryUtils.assumeBatterySaverFeature();
728 
729         setChargingState(false);
730         BatteryUtils.enableBatterySaver(false);
731         sendScheduleJobBroadcast(false);
732         assertTrue("New job failed to start with battery saver OFF",
733                 mTestAppInterface.awaitJobStart(3_000));
734     }
735 
736     @Test
testBatterySaverOn()737     public void testBatterySaverOn() throws Exception {
738         BatteryUtils.assumeBatterySaverFeature();
739 
740         setChargingState(false);
741         BatteryUtils.enableBatterySaver(true);
742         sendScheduleJobBroadcast(false);
743         assertFalse("New job started with battery saver ON",
744                 mTestAppInterface.awaitJobStart(3_000));
745     }
746 
747     @Test
testUidActiveBypassesBatterySaverOn()748     public void testUidActiveBypassesBatterySaverOn() throws Exception {
749         BatteryUtils.assumeBatterySaverFeature();
750 
751         setChargingState(false);
752         BatteryUtils.enableBatterySaver(true);
753         tempWhitelistTestApp(6_000);
754         sendScheduleJobBroadcast(false);
755         assertTrue("New job in uid-active app failed to start with battery saver ON",
756                 mTestAppInterface.awaitJobStart(3_000));
757     }
758 
759     @Test
testBatterySaverOnThenUidActive()760     public void testBatterySaverOnThenUidActive() throws Exception {
761         BatteryUtils.assumeBatterySaverFeature();
762 
763         // Enable battery saver, and schedule a job. It shouldn't run.
764         setChargingState(false);
765         BatteryUtils.enableBatterySaver(true);
766         sendScheduleJobBroadcast(false);
767         assertFalse("New job started with battery saver ON",
768                 mTestAppInterface.awaitJobStart(3_000));
769 
770         // Then make the UID active. Now the job should run.
771         tempWhitelistTestApp(120_000);
772         assertTrue("New job in uid-active app failed to start with battery saver OFF",
773                 mTestAppInterface.awaitJobStart(120_000));
774     }
775 
776     @Test
testExpeditedJobBypassesBatterySaverOn()777     public void testExpeditedJobBypassesBatterySaverOn() throws Exception {
778         BatteryUtils.assumeBatterySaverFeature();
779 
780         setChargingState(false);
781         BatteryUtils.enableBatterySaver(true);
782         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
783         assertTrue("New expedited job failed to start with battery saver ON",
784                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
785     }
786 
787     @Test
testExpeditedJobBypassesBatterySaver_toggling()788     public void testExpeditedJobBypassesBatterySaver_toggling() throws Exception {
789         BatteryUtils.assumeBatterySaverFeature();
790 
791         setChargingState(false);
792         BatteryUtils.enableBatterySaver(false);
793         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
794         assertTrue("New expedited job failed to start with battery saver ON",
795                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
796         BatteryUtils.enableBatterySaver(true);
797         assertFalse("Job stopped when battery saver turned on",
798                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
799     }
800 
801     @Test
testExpeditedJobBypassesDeviceIdle()802     public void testExpeditedJobBypassesDeviceIdle() throws Exception {
803         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
804 
805         toggleDozeState(true);
806         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
807         runJob();
808         assertTrue("Job did not start after scheduling",
809                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
810     }
811 
812     @Test
testExpeditedJobBypassesDeviceIdle_toggling()813     public void testExpeditedJobBypassesDeviceIdle_toggling() throws Exception {
814         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
815 
816         toggleDozeState(false);
817         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
818         runJob();
819         assertTrue("Job did not start after scheduling",
820                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
821         toggleDozeState(true);
822         assertFalse("Job stopped when device enabled turned on",
823                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
824     }
825 
826     @Test
testExpeditedJobDeferredAfterTimeoutInDoze()827     public void testExpeditedJobDeferredAfterTimeoutInDoze() throws Exception {
828         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
829         // Intentionally set a value below 1 minute to ensure the range checks work.
830         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(30_000L));
831 
832         toggleDozeState(true);
833         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
834         runJob();
835         assertTrue("Job did not start after scheduling",
836                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
837         // Don't put full minute as the timeout to give some leeway with test timing/processing.
838         assertFalse("Job stopped before min runtime limit",
839                 mTestAppInterface.awaitJobStop(55_000L));
840         assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(15_000L));
841         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
842                 mTestAppInterface.getLastParams().getStopReason());
843         // Should be rescheduled.
844         assertJobNotReady();
845         assertJobWaiting();
846         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
847         runJob();
848         assertFalse("Job started after timing out in Doze",
849                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
850 
851         // Should start when Doze is turned off.
852         toggleDozeState(false);
853         assertTrue("Job did not start after Doze turned off",
854                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
855     }
856 
857     @Test
testExpeditedJobDeferredAfterTimeoutInBatterySaver()858     public void testExpeditedJobDeferredAfterTimeoutInBatterySaver() throws Exception {
859         BatteryUtils.assumeBatterySaverFeature();
860 
861         // Intentionally set a value below 1 minute to ensure the range checks work.
862         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(47_000L));
863 
864         setChargingState(false);
865         BatteryUtils.enableBatterySaver(true);
866         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
867         runJob();
868         assertTrue("Job did not start after scheduling",
869                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
870         // Don't put full minute as the timeout to give some leeway with test timing/processing.
871         assertFalse("Job stopped before min runtime limit",
872                 mTestAppInterface.awaitJobStop(55_000L));
873         assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(15_000L));
874         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
875                 mTestAppInterface.getLastParams().getStopReason());
876         // Should be rescheduled.
877         assertJobNotReady();
878         assertJobWaiting();
879         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
880         runJob();
881         assertFalse("Job started after timing out in battery saver",
882                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
883 
884         // Should start when battery saver is turned off.
885         BatteryUtils.enableBatterySaver(false);
886         assertTrue("Job did not start after battery saver turned off",
887                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
888     }
889 
890     @Test
testExpeditedJobDeferredAfterTimeout_DozeAndBatterySaver()891     public void testExpeditedJobDeferredAfterTimeout_DozeAndBatterySaver() throws Exception {
892         BatteryUtils.assumeBatterySaverFeature();
893         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
894         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(60_000L));
895 
896         setChargingState(false);
897         toggleDozeState(true);
898         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
899         runJob();
900         assertTrue("Job did not start after scheduling",
901                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
902         // Don't put full minute as the timeout to give some leeway with test timing/processing.
903         assertFalse("Job stopped before min runtime limit",
904                 mTestAppInterface.awaitJobStop(55_000L));
905         assertTrue("Job did not stop after timeout", mTestAppInterface.awaitJobStop(15_000L));
906         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
907                 mTestAppInterface.getLastParams().getStopReason());
908         // Should be rescheduled.
909         assertJobNotReady();
910         assertJobWaiting();
911         // Battery saver kicks in before Doze ends. Job shouldn't start while BS is on.
912         BatteryUtils.enableBatterySaver(true);
913         toggleDozeState(false);
914         Thread.sleep(TestJobSchedulerReceiver.JOB_INITIAL_BACKOFF);
915         runJob();
916         assertFalse("Job started while power restrictions active after timing out",
917                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
918 
919         // Should start when battery saver is turned off.
920         BatteryUtils.enableBatterySaver(false);
921         assertTrue("Job did not start after power restrictions turned off",
922                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
923     }
924 
925     @Test
testLongExpeditedJobStoppedByDoze()926     public void testLongExpeditedJobStoppedByDoze() throws Exception {
927         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
928         // Intentionally set a value below 1 minute to ensure the range checks work.
929         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(59_000L));
930 
931         toggleDozeState(false);
932         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
933         runJob();
934         assertTrue("Job did not start after scheduling",
935                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
936         // Should get to run past min runtime.
937         assertFalse("Job stopped after min runtime", mTestAppInterface.awaitJobStop(90_000L));
938 
939         // Should stop when Doze is turned on.
940         toggleDozeState(true);
941         assertTrue("Job did not stop after Doze turned on",
942                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
943         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
944                 mTestAppInterface.getLastParams().getStopReason());
945     }
946 
947     @Test
testLongExpeditedJobStoppedByBatterySaver()948     public void testLongExpeditedJobStoppedByBatterySaver() throws Exception {
949         BatteryUtils.assumeBatterySaverFeature();
950 
951         // Intentionally set a value below 1 minute to ensure the range checks work.
952         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", Long.toString(0L));
953 
954         setChargingState(false);
955         BatteryUtils.enableBatterySaver(false);
956         mTestAppInterface.scheduleJob(false, JobInfo.NETWORK_TYPE_NONE, true);
957         runJob();
958         assertTrue("Job did not start after scheduling",
959                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
960         // Should get to run past min runtime.
961         assertFalse("Job stopped after runtime", mTestAppInterface.awaitJobStop(90_000L));
962 
963         // Should stop when battery saver is turned on.
964         BatteryUtils.enableBatterySaver(true);
965         assertTrue("Job did not stop after battery saver turned on",
966                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
967         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
968                 mTestAppInterface.getLastParams().getStopReason());
969     }
970 
971     @Test
testUserInitiatedJobBypassesBatterySaverOn()972     public void testUserInitiatedJobBypassesBatterySaverOn() throws Exception {
973         BatteryUtils.assumeBatterySaverFeature();
974         mNetworkingHelper.setAllNetworksEnabled(true);
975 
976         try (TestNotificationListener.NotificationHelper notificationHelper =
977                      new TestNotificationListener.NotificationHelper(
978                              mContext, TestAppInterface.TEST_APP_PACKAGE)) {
979             setChargingState(false);
980             BatteryUtils.enableBatterySaver(true);
981 
982             mTestAppInterface.postUiInitiatingNotification(
983                     Map.of(
984                             TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true
985                     ),
986                     Map.of(TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY));
987             notificationHelper.clickNotification();
988 
989             assertTrue("New user-initiated job failed to start with battery saver ON",
990                     mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
991         }
992     }
993 
994     @Test
testUserInitiatedJobBypassesBatterySaver_toggling()995     public void testUserInitiatedJobBypassesBatterySaver_toggling() throws Exception {
996         BatteryUtils.assumeBatterySaverFeature();
997         mNetworkingHelper.setAllNetworksEnabled(true);
998 
999         try (TestNotificationListener.NotificationHelper notificationHelper =
1000                      new TestNotificationListener.NotificationHelper(
1001                              mContext, TestAppInterface.TEST_APP_PACKAGE)) {
1002             setChargingState(false);
1003             BatteryUtils.enableBatterySaver(false);
1004 
1005             mTestAppInterface.postUiInitiatingNotification(
1006                     Map.of(
1007                             TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true
1008                     ),
1009                     Map.of(TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY));
1010             notificationHelper.clickNotification();
1011 
1012             assertTrue("New user-initiated job failed to start with battery saver ON",
1013                     mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1014 
1015             BatteryUtils.enableBatterySaver(true);
1016             assertFalse("Job stopped when battery saver turned on",
1017                     mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1018         }
1019     }
1020 
1021     @Test
testUserInitiatedJobBypassesDeviceIdle()1022     public void testUserInitiatedJobBypassesDeviceIdle() throws Exception {
1023         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
1024         mNetworkingHelper.setAllNetworksEnabled(true);
1025 
1026         try (TestNotificationListener.NotificationHelper notificationHelper =
1027                      new TestNotificationListener.NotificationHelper(
1028                              mContext, TestAppInterface.TEST_APP_PACKAGE)) {
1029             toggleDozeState(true);
1030 
1031             mTestAppInterface.postUiInitiatingNotification(
1032                     Map.of(
1033                             TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true
1034                     ),
1035                     Map.of(TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY));
1036             notificationHelper.clickNotification();
1037 
1038             assertTrue("Job did not start after scheduling",
1039                     mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1040         }
1041     }
1042 
1043     @Test
testUserInitiatedJobBypassesDeviceIdle_toggling()1044     public void testUserInitiatedJobBypassesDeviceIdle_toggling() throws Exception {
1045         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
1046         mNetworkingHelper.setAllNetworksEnabled(true);
1047 
1048         try (TestNotificationListener.NotificationHelper notificationHelper =
1049                      new TestNotificationListener.NotificationHelper(
1050                              mContext, TestAppInterface.TEST_APP_PACKAGE)) {
1051             toggleDozeState(false);
1052 
1053             mTestAppInterface.postUiInitiatingNotification(
1054                     Map.of(
1055                             TestJobSchedulerReceiver.EXTRA_AS_USER_INITIATED, true
1056                     ),
1057                     Map.of(TestJobSchedulerReceiver.EXTRA_REQUIRED_NETWORK_TYPE, NETWORK_TYPE_ANY));
1058             notificationHelper.clickNotification();
1059 
1060             assertTrue("Job did not start after scheduling",
1061                     mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1062 
1063             toggleDozeState(true);
1064             assertFalse("Job stopped when device enabled turned on",
1065                     mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1066         }
1067     }
1068 
1069     @Test
testRestrictingStopReason_RestrictedBucket_connectivity()1070     public void testRestrictingStopReason_RestrictedBucket_connectivity() throws Exception {
1071         assumeTrue("app standby not enabled", mAppStandbyEnabled);
1072         // Tests cannot disable ethernet network.
1073         assumeFalse("not testable, since ethernet is connected", hasEthernetConnection());
1074 
1075         assumeTrue(BatteryUtils.hasBattery());
1076         assumeTrue(mNetworkingHelper.hasWifiFeature());
1077         mNetworkingHelper.ensureSavedWifiNetwork();
1078 
1079         // This test is designed for the old quota system.
1080         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
1081 
1082         setTestPackageStandbyBucket(Bucket.RESTRICTED);
1083 
1084         // Disable coalescing and the parole session
1085         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
1086         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
1087 
1088         // Satisfy all additional constraints.
1089         mNetworkingHelper.setAllNetworksEnabled(true);
1090         mNetworkingHelper.setWifiMeteredState(false);
1091         setChargingState(true);
1092         BatteryUtils.runDumpsysBatterySetLevel(100);
1093         setScreenState(false);
1094         triggerJobIdle();
1095 
1096         // Connectivity
1097         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_ANY, false);
1098         runJob();
1099         assertTrue("New job didn't start in RESTRICTED bucket",
1100                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1101         mNetworkingHelper.setAllNetworksEnabled(false);
1102         assertTrue("New job didn't stop when connectivity dropped",
1103                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1104         assertEquals(JobParameters.STOP_REASON_CONSTRAINT_CONNECTIVITY,
1105                 mTestAppInterface.getLastParams().getStopReason());
1106     }
1107 
1108     @Test
testRestrictingStopReason_RestrictedBucket_idle()1109     public void testRestrictingStopReason_RestrictedBucket_idle() throws Exception {
1110         assumeTrue("app standby not enabled", mAppStandbyEnabled);
1111 
1112         // This test is designed for the old quota system.
1113         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
1114 
1115         setTestPackageStandbyBucket(Bucket.RESTRICTED);
1116 
1117         // Disable coalescing and the parole session
1118         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
1119         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
1120 
1121         // Satisfy all additional constraints.
1122         setChargingState(true);
1123         BatteryUtils.runDumpsysBatterySetLevel(100);
1124         setScreenState(false);
1125         triggerJobIdle();
1126 
1127         // Idle
1128         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
1129         runJob();
1130         assertTrue("New job didn't start in RESTRICTED bucket",
1131                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1132         setScreenState(true);
1133         assertTrue("New job didn't stop when device no longer idle",
1134                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1135         assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
1136                 mTestAppInterface.getLastParams().getStopReason());
1137     }
1138 
1139     @Test
testRestrictingStopReason_RestrictedBucket_charging()1140     public void testRestrictingStopReason_RestrictedBucket_charging() throws Exception {
1141         assumeTrue("app standby not enabled", mAppStandbyEnabled);
1142         // Can't toggle charging state if there's no battery.
1143         assumeTrue("device doesn't have battery", BatteryUtils.hasBattery());
1144 
1145         // This test is designed for the old quota system.
1146         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
1147 
1148         setTestPackageStandbyBucket(Bucket.RESTRICTED);
1149 
1150         // Disable coalescing and the parole session
1151         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
1152         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
1153 
1154         // Satisfy all additional constraints.
1155         setChargingState(true);
1156         BatteryUtils.runDumpsysBatterySetLevel(100);
1157         setScreenState(false);
1158         triggerJobIdle();
1159 
1160         // Charging
1161         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
1162         runJob();
1163         assertTrue("New job didn't start in RESTRICTED bucket",
1164                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1165         setChargingState(false);
1166         assertTrue("New job didn't stop when device no longer charging",
1167                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1168         assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
1169                 mTestAppInterface.getLastParams().getStopReason());
1170     }
1171 
1172     @Test
testRestrictingStopReason_RestrictedBucket_batteryNotLow()1173     public void testRestrictingStopReason_RestrictedBucket_batteryNotLow() throws Exception {
1174         assumeTrue("app standby not enabled", mAppStandbyEnabled);
1175         assumeTrue("device doesn't have battery", BatteryUtils.hasBattery());
1176 
1177         // This test is designed for the old quota system.
1178         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
1179 
1180         setTestPackageStandbyBucket(Bucket.RESTRICTED);
1181 
1182         // Disable coalescing and the parole session
1183         mDeviceConfigStateHelper.set("qc_timing_session_coalescing_duration_ms", "0");
1184         mDeviceConfigStateHelper.set("qc_max_session_count_restricted", "0");
1185 
1186         // Satisfy all additional constraints.
1187         setChargingState(true);
1188         BatteryUtils.runDumpsysBatterySetLevel(100);
1189         setScreenState(false);
1190         triggerJobIdle();
1191 
1192         // Battery not low
1193         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
1194         runJob();
1195         assertTrue("New job didn't start in RESTRICTED bucket",
1196                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1197         BatteryUtils.runDumpsysBatterySetLevel(1);
1198         assertTrue("New job didn't stop when battery too low",
1199                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1200         assertEquals(JobParameters.STOP_REASON_APP_STANDBY,
1201                 mTestAppInterface.getLastParams().getStopReason());
1202     }
1203 
1204     @Test
testRestrictingStopReason_Quota()1205     public void testRestrictingStopReason_Quota() throws Exception {
1206         assumeTrue("app standby not enabled", mAppStandbyEnabled);
1207         assumeTrue("device doesn't have battery", BatteryUtils.hasBattery());
1208 
1209         // This test is designed for the old quota system.
1210         mTareDeviceConfigStateHelper.set("enable_tare_mode", "0");
1211 
1212         // Reduce allowed time for testing.
1213         mDeviceConfigStateHelper.set("qc_allowed_time_per_period_rare_ms", "60000");
1214         setChargingState(false);
1215         setTestPackageStandbyBucket(Bucket.RARE);
1216 
1217         sendScheduleJobBroadcast(false);
1218         runJob();
1219         assertTrue("New job didn't start",
1220                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1221 
1222         Thread.sleep(60000);
1223 
1224         assertTrue("New job didn't stop after using up quota",
1225                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1226         assertEquals(JobParameters.STOP_REASON_QUOTA,
1227                 mTestAppInterface.getLastParams().getStopReason());
1228     }
1229 
1230     /*
1231     Tests currently disabled because they require changes inside the framework to lower the minimum
1232     EJ quota to one minute (from 5 minutes).
1233     TODO(224533485): make JS testable enough to enable these tests
1234 
1235     @Test
1236     public void testRestrictingStopReason_ExpeditedQuota_startOnCharging() throws Exception {
1237         assumeTrue("app standby not enabled", mAppStandbyEnabled);
1238         assumeFalse("not testable in automotive device", mAutomotiveDevice); // Test needs battery
1239         assumeFalse("not testable in leanback device", mLeanbackOnly); // Test needs battery
1240 
1241         // Reduce allowed time for testing. System to cap the time above 30 seconds.
1242         mDeviceConfigStateHelper.set("qc_ej_limit_rare_ms", "30000");
1243         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", "30000");
1244         // Start with charging so JobScheduler thinks the job can run for the maximum amount of
1245         // time. We turn off charging later so quota clearly comes into effect.
1246         setChargingState(true);
1247         setTestPackageStandbyBucket(Bucket.RARE);
1248 
1249         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, true);
1250         runJob();
1251         assertTrue("New job didn't start",
1252                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1253         assertTrue(mTestAppInterface.getLastParams().isExpeditedJob());
1254         setChargingState(false);
1255 
1256         assertFalse("Job stopped before using up quota",
1257                 mTestAppInterface.awaitJobStop(45_000));
1258         Thread.sleep(15_000);
1259 
1260         assertTrue("Job didn't stop after using up quota",
1261                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1262         assertEquals(JobParameters.STOP_REASON_QUOTA,
1263                 mTestAppInterface.getLastParams().getStopReason());
1264     }
1265 
1266     @Test
1267     public void testRestrictingStopReason_ExpeditedQuota_noCharging() throws Exception {
1268         assumeTrue("app standby not enabled", mAppStandbyEnabled);
1269         assumeFalse("not testable in automotive device", mAutomotiveDevice); // Test needs battery
1270         assumeFalse("not testable in leanback device", mLeanbackOnly); // Test needs battery
1271 
1272         // Reduce allowed time for testing.
1273         mDeviceConfigStateHelper.set("qc_ej_limit_rare_ms", "30000");
1274         mDeviceConfigStateHelper.set("runtime_min_ej_guarantee_ms", "30000");
1275         setChargingState(false);
1276         setTestPackageStandbyBucket(Bucket.RARE);
1277 
1278         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, true);
1279         runJob();
1280         assertTrue("New job didn't start",
1281                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1282         assertTrue(mTestAppInterface.getLastParams().isExpeditedJob());
1283 
1284         assertFalse("Job stopped before using up quota",
1285                 mTestAppInterface.awaitJobStop(45_000));
1286         Thread.sleep(15_000);
1287 
1288         assertTrue("Job didn't stop after using up quota",
1289                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1290         // Charging state was false when the job started, so the trigger the timeout before
1291         // QuotaController officially marks the quota finished.
1292         final int stopReason = mTestAppInterface.getLastParams().getStopReason();
1293         assertTrue(stopReason == JobParameters.STOP_REASON_TIMEOUT
1294                 || stopReason == JobParameters.STOP_REASON_QUOTA);
1295     }
1296      */
1297 
1298     @Test
testRestrictingStopReason_BatterySaver()1299     public void testRestrictingStopReason_BatterySaver() throws Exception {
1300         BatteryUtils.assumeBatterySaverFeature();
1301 
1302         setChargingState(false);
1303         BatteryUtils.enableBatterySaver(false);
1304         sendScheduleJobBroadcast(false);
1305         runJob();
1306         assertTrue("Job did not start after scheduling",
1307                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1308 
1309         BatteryUtils.enableBatterySaver(true);
1310         assertTrue("Job did not stop on entering battery saver",
1311                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1312         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
1313                 mTestAppInterface.getLastParams().getStopReason());
1314     }
1315 
1316     @Test
testRestrictingStopReason_Doze()1317     public void testRestrictingStopReason_Doze() throws Exception {
1318         assumeTrue("device idle not enabled", mDeviceIdleEnabled);
1319 
1320         toggleDozeState(false);
1321         mTestAppInterface.scheduleJob(false, NETWORK_TYPE_NONE, false);
1322         runJob();
1323         assertTrue("Job did not start after scheduling",
1324                 mTestAppInterface.awaitJobStart(DEFAULT_WAIT_TIMEOUT));
1325 
1326         toggleDozeState(true);
1327         assertTrue("Job did not stop on entering doze",
1328                 mTestAppInterface.awaitJobStop(DEFAULT_WAIT_TIMEOUT));
1329         assertEquals(JobParameters.STOP_REASON_DEVICE_STATE,
1330                 mTestAppInterface.getLastParams().getStopReason());
1331     }
1332 
1333     @After
tearDown()1334     public void tearDown() throws Exception {
1335         AppOpsUtils.reset(TEST_APP_PACKAGE);
1336         // Lock thermal service to not throttling
1337         ThermalUtils.overrideThermalNotThrottling();
1338         if (mDeviceIdleEnabled || mDeviceLightIdleEnabled) {
1339             resetDozeState();
1340         }
1341         mTestAppInterface.cleanup();
1342         mUiDevice.executeShellCommand("cmd jobscheduler monitor-battery off");
1343         BatteryUtils.runDumpsysBatteryReset();
1344         BatteryUtils.resetBatterySaver();
1345         Settings.Global.putString(mContext.getContentResolver(),
1346                 Settings.Global.BATTERY_STATS_CONSTANTS, mInitialBatteryStatsConstants);
1347         removeTestAppFromTempWhitelist();
1348 
1349         mNetworkingHelper.tearDown();
1350         mDeviceConfigStateHelper.restoreOriginalValues();
1351         mActivityManagerDeviceConfigStateHelper.restoreOriginalValues();
1352         mTareDeviceConfigStateHelper.restoreOriginalValues();
1353 
1354         mUiDevice.executeShellCommand(
1355                 "cmd jobscheduler reset-execution-quota -u " + UserHandle.myUserId()
1356                         + " " + TEST_APP_PACKAGE);
1357 
1358         Settings.System.putString(
1359                 mContext.getContentResolver(), SCREEN_OFF_TIMEOUT, mInitialDisplayTimeout);
1360 
1361         Settings.Global.putString(mContext.getContentResolver(),
1362                 Settings.Global.ACTIVITY_MANAGER_CONSTANTS, mInitialActivityManagerConstants);
1363     }
1364 
setTestPackageRestricted(boolean restricted)1365     private void setTestPackageRestricted(boolean restricted) throws Exception {
1366         AppOpsUtils.setOpMode(TEST_APP_PACKAGE, "RUN_ANY_IN_BACKGROUND",
1367                 restricted ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED);
1368     }
1369 
toggleAutoRestrictedBucketOnBgRestricted(boolean enable)1370     private void toggleAutoRestrictedBucketOnBgRestricted(boolean enable) {
1371         mActivityManagerDeviceConfigStateHelper.set("bg_auto_restricted_bucket_on_bg_restricted",
1372                 Boolean.toString(enable));
1373     }
1374 
isTestAppTempWhitelisted()1375     private boolean isTestAppTempWhitelisted() throws Exception {
1376         final String output = mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist").trim();
1377         for (String line : output.split("\n")) {
1378             if (line.contains("UID=" + UserHandle.getAppId(mTestPackageUid))) {
1379                 return true;
1380             }
1381         }
1382         return false;
1383     }
1384 
sendScheduleJobBroadcast(boolean allowWhileIdle)1385     private void sendScheduleJobBroadcast(boolean allowWhileIdle) throws Exception {
1386         mTestAppInterface.scheduleJob(allowWhileIdle, NETWORK_TYPE_NONE, false);
1387     }
1388 
resetDozeState()1389     private void resetDozeState() throws Exception {
1390         mUiDevice.executeShellCommand("cmd deviceidle unforce");
1391     }
1392 
toggleDozeState(final boolean idle)1393     private void toggleDozeState(final boolean idle) throws Exception {
1394         final String changeCommand;
1395         if (idle) {
1396             changeCommand = "force-idle " + (mDeviceIdleEnabled ? "deep" : "light");
1397         } else {
1398             changeCommand = "force-active";
1399         }
1400         mUiDevice.executeShellCommand("cmd deviceidle " + changeCommand);
1401         assertTrue("Could not change device idle state to " + idle,
1402                 waitUntilTrue(SHELL_TIMEOUT, () -> {
1403                     if (idle) {
1404                         return mDeviceIdleEnabled
1405                                 ? mPowerManager.isDeviceIdleMode()
1406                                 : mPowerManager.isDeviceLightIdleMode();
1407                     } else {
1408                         return !mPowerManager.isDeviceIdleMode()
1409                                 && !mPowerManager.isDeviceLightIdleMode();
1410                     }
1411                 }));
1412     }
1413 
tempWhitelistTestApp(long duration)1414     private void tempWhitelistTestApp(long duration) throws Exception {
1415         mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist -d " + duration
1416                 + " -u " + UserHandle.myUserId()
1417                 + " " + TEST_APP_PACKAGE);
1418     }
1419 
makeTestPackageIdle()1420     private void makeTestPackageIdle() throws Exception {
1421         mUiDevice.executeShellCommand("am make-uid-idle --user current " + TEST_APP_PACKAGE);
1422     }
1423 
setTestPackageStandbyBucket(Bucket bucket)1424     void setTestPackageStandbyBucket(Bucket bucket) throws Exception {
1425         setTestPackageStandbyBucket(mUiDevice, bucket);
1426     }
1427 
setTestPackageStandbyBucket(UiDevice uiDevice, Bucket bucket)1428     static void setTestPackageStandbyBucket(UiDevice uiDevice, Bucket bucket) throws Exception {
1429         final String bucketName;
1430         switch (bucket) {
1431             case ACTIVE:
1432                 bucketName = "active";
1433                 break;
1434             case WORKING_SET:
1435                 bucketName = "working";
1436                 break;
1437             case FREQUENT:
1438                 bucketName = "frequent";
1439                 break;
1440             case RARE:
1441                 bucketName = "rare";
1442                 break;
1443             case RESTRICTED:
1444                 bucketName = "restricted";
1445                 break;
1446             case NEVER:
1447                 bucketName = "never";
1448                 break;
1449             default:
1450                 throw new IllegalArgumentException("Requested unknown bucket " + bucket);
1451         }
1452         uiDevice.executeShellCommand("am set-standby-bucket " + TEST_APP_PACKAGE
1453                 + " " + bucketName);
1454     }
1455 
removeTestAppFromTempWhitelist()1456     private boolean removeTestAppFromTempWhitelist() throws Exception {
1457         mUiDevice.executeShellCommand("cmd deviceidle tempwhitelist"
1458                 + " -u " + UserHandle.myUserId()
1459                 + " -r " + TEST_APP_PACKAGE);
1460         return waitUntilTrue(SHELL_TIMEOUT, () -> !isTestAppTempWhitelisted());
1461     }
1462 
1463     /**
1464      * Set the screen state.
1465      */
setScreenState(boolean on)1466     private void setScreenState(boolean on) throws Exception {
1467         if (on) {
1468             mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
1469             mUiDevice.executeShellCommand("wm dismiss-keyguard");
1470         } else {
1471             mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
1472         }
1473         // Wait a little bit to make sure the screen state has changed.
1474         Thread.sleep(4_000);
1475     }
1476 
setChargingState(boolean isCharging)1477     private void setChargingState(boolean isCharging) throws Exception {
1478         mUiDevice.executeShellCommand("cmd jobscheduler monitor-battery on");
1479 
1480         final String command;
1481         if (isCharging) {
1482             mUiDevice.executeShellCommand("cmd battery set ac 1");
1483             final int curLevel = Integer.parseInt(
1484                     mUiDevice.executeShellCommand("dumpsys battery get level").trim());
1485             command = "cmd battery set -f level " + Math.min(100, curLevel + 1);
1486         } else {
1487             command = "cmd battery unplug -f";
1488         }
1489         int seq = Integer.parseInt(mUiDevice.executeShellCommand(command).trim());
1490 
1491         // Wait for the battery update to be processed by job scheduler before proceeding.
1492         waitUntil("JobScheduler didn't update charging status to " + isCharging, 15 /* seconds */,
1493                 () -> {
1494                     int curSeq;
1495                     boolean curCharging;
1496                     curSeq = Integer.parseInt(mUiDevice.executeShellCommand(
1497                             "cmd jobscheduler get-battery-seq").trim());
1498                     curCharging = Boolean.parseBoolean(mUiDevice.executeShellCommand(
1499                             "cmd jobscheduler get-battery-charging").trim());
1500                     return curSeq >= seq && curCharging == isCharging;
1501                 });
1502     }
1503 
1504     /**
1505      * Trigger job idle (not device idle);
1506      */
triggerJobIdle()1507     private void triggerJobIdle() throws Exception {
1508         mUiDevice.executeShellCommand("cmd activity idle-maintenance");
1509         // Wait a moment to let that happen before proceeding.
1510         Thread.sleep(2_000);
1511     }
1512 
1513     /** Asks (not forces) JobScheduler to run the job if constraints are met. */
runJob()1514     private void runJob() throws Exception {
1515         // Since connectivity is a functional constraint, calling the "run" command without force
1516         // will only get the job to run if the constraint is satisfied.
1517         mUiDevice.executeShellCommand("cmd jobscheduler run -s"
1518                 + " -u " + UserHandle.myUserId() + " " + TEST_APP_PACKAGE + " " + mTestJobId);
1519     }
1520 
hasEthernetConnection()1521     private boolean hasEthernetConnection() {
1522         return mNetworkingHelper.hasEthernetConnection();
1523     }
1524 
getJobState()1525     private String getJobState() throws Exception {
1526         return mUiDevice.executeShellCommand("cmd jobscheduler get-job-state --user cur "
1527                 + TEST_APP_PACKAGE + " " + mTestJobId).trim();
1528     }
1529 
assertJobWaiting()1530     private void assertJobWaiting() throws Exception {
1531         String state = getJobState();
1532         assertTrue("Job unexpectedly not waiting, in state: " + state, state.contains("waiting"));
1533     }
1534 
assertJobNotReady()1535     private void assertJobNotReady() throws Exception {
1536         String state = getJobState();
1537         assertFalse("Job unexpectedly ready, in state: " + state, state.contains("ready"));
1538     }
1539 
assertJobReady()1540     private void assertJobReady() throws Exception {
1541         String state = getJobState();
1542         assertTrue("Job unexpectedly not ready, in state: " + state, state.contains("ready"));
1543     }
1544 
waitUntilTrue(long maxWait, Condition condition)1545     private boolean waitUntilTrue(long maxWait, Condition condition) throws Exception {
1546         final long deadLine = SystemClock.uptimeMillis() + maxWait;
1547         do {
1548             Thread.sleep(POLL_INTERVAL);
1549         } while (!condition.isTrue() && SystemClock.uptimeMillis() < deadLine);
1550         return condition.isTrue();
1551     }
1552 
1553     private interface Condition {
isTrue()1554         boolean isTrue() throws Exception;
1555     }
1556 }
1557