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