1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.job;
18 
19 import static android.app.job.Flags.FLAG_HANDLE_ABANDONED_JOBS;
20 import static android.text.format.DateUtils.DAY_IN_MILLIS;
21 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
22 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
23 
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
28 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
29 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
30 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
31 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
32 import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
33 import static com.android.server.job.Flags.FLAG_BATCH_ACTIVE_BUCKET_JOBS;
34 import static com.android.server.job.Flags.FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK;
35 import static com.android.server.job.Flags.FLAG_CREATE_WORK_CHAIN_BY_DEFAULT;
36 import static com.android.server.job.Flags.FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS;
37 
38 import static org.junit.Assert.assertArrayEquals;
39 import static org.junit.Assert.assertEquals;
40 import static org.junit.Assert.assertFalse;
41 import static org.junit.Assert.assertNotEquals;
42 import static org.junit.Assert.assertNotNull;
43 import static org.junit.Assert.assertNull;
44 import static org.junit.Assert.assertTrue;
45 import static org.junit.Assert.fail;
46 import static org.mockito.ArgumentMatchers.any;
47 import static org.mockito.ArgumentMatchers.anyBoolean;
48 import static org.mockito.ArgumentMatchers.anyInt;
49 import static org.mockito.ArgumentMatchers.anyString;
50 import static org.mockito.ArgumentMatchers.eq;
51 import static org.mockito.Mockito.verify;
52 import static org.mockito.Mockito.when;
53 
54 import android.app.ActivityManager;
55 import android.app.ActivityManagerInternal;
56 import android.app.IActivityManager;
57 import android.app.UiModeManager;
58 import android.app.job.JobInfo;
59 import android.app.job.JobParameters;
60 import android.app.job.JobScheduler;
61 import android.app.job.JobWorkItem;
62 import android.app.usage.UsageStatsManagerInternal;
63 import android.content.ComponentName;
64 import android.content.ContentResolver;
65 import android.content.Context;
66 import android.content.IContentProvider;
67 import android.content.Intent;
68 import android.content.PermissionChecker;
69 import android.content.pm.PackageManager;
70 import android.content.pm.PackageManagerInternal;
71 import android.content.res.Resources;
72 import android.net.ConnectivityManager;
73 import android.net.Network;
74 import android.net.NetworkCapabilities;
75 import android.net.NetworkPolicyManager;
76 import android.os.BatteryManager;
77 import android.os.BatteryManagerInternal;
78 import android.os.BatteryManagerInternal.ChargingPolicyChangeListener;
79 import android.os.Looper;
80 import android.os.Process;
81 import android.os.RemoteException;
82 import android.os.ServiceManager;
83 import android.os.SystemClock;
84 import android.os.WorkSource;
85 import android.os.WorkSource.WorkChain;
86 import android.platform.test.annotations.EnableFlags;
87 import android.platform.test.annotations.RequiresFlagsDisabled;
88 import android.platform.test.annotations.RequiresFlagsEnabled;
89 import android.platform.test.flag.junit.CheckFlagsRule;
90 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
91 import android.platform.test.flag.junit.SetFlagsRule;
92 
93 import com.android.server.AppStateTracker;
94 import com.android.server.AppStateTrackerImpl;
95 import com.android.server.DeviceIdleInternal;
96 import com.android.server.LocalServices;
97 import com.android.server.PowerAllowlistInternal;
98 import com.android.server.SystemServiceManager;
99 import com.android.server.job.controllers.ConnectivityController;
100 import com.android.server.job.controllers.JobStatus;
101 import com.android.server.job.controllers.QuotaController;
102 import com.android.server.job.restrictions.JobRestriction;
103 import com.android.server.job.restrictions.ThermalStatusRestriction;
104 import com.android.server.pm.UserManagerInternal;
105 import com.android.server.usage.AppStandbyInternal;
106 
107 import org.junit.After;
108 import org.junit.Before;
109 import org.junit.Rule;
110 import org.junit.Test;
111 import org.mockito.ArgumentCaptor;
112 import org.mockito.ArgumentMatchers;
113 import org.mockito.Mock;
114 import org.mockito.MockitoSession;
115 import org.mockito.quality.Strictness;
116 
117 import java.time.Clock;
118 import java.time.Duration;
119 import java.time.ZoneOffset;
120 
121 public class JobSchedulerServiceTest {
122     private static final String TAG = JobSchedulerServiceTest.class.getSimpleName();
123     private static final int TEST_UID = 10123;
124 
125     private JobSchedulerService mService;
126 
127     private MockitoSession mMockingSession;
128     @Mock
129     private ActivityManagerInternal mActivityMangerInternal;
130     @Mock
131     private BatteryManagerInternal mBatteryManagerInternal;
132     @Mock
133     private Context mContext;
134     @Mock
135     private PackageManagerInternal mPackageManagerInternal;
136 
137     @Rule
138     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
139 
140     @Rule
141     public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
142 
143     private ChargingPolicyChangeListener mChargingPolicyChangeListener;
144 
145     private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context)146         TestJobSchedulerService(Context context) {
147             super(context);
148             mAppStateTracker = mock(AppStateTrackerImpl.class);
149         }
150     }
151 
152     @Before
setUp()153     public void setUp() throws Exception {
154         mMockingSession = mockitoSession()
155                 .initMocks(this)
156                 .strictness(Strictness.LENIENT)
157                 .mockStatic(LocalServices.class)
158                 .mockStatic(PermissionChecker.class)
159                 .mockStatic(ServiceManager.class)
160                 .startMocking();
161 
162         // Called in JobSchedulerService constructor.
163         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
164         doReturn(mActivityMangerInternal)
165                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
166         doReturn(mock(AppStandbyInternal.class))
167                 .when(() -> LocalServices.getService(AppStandbyInternal.class));
168         doReturn(mBatteryManagerInternal)
169                 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
170         doReturn(mPackageManagerInternal)
171                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
172         doReturn(mock(UsageStatsManagerInternal.class))
173                 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
174         when(mContext.getString(anyInt())).thenReturn("some_test_string");
175         // Called in BackgroundJobsController constructor.
176         doReturn(mock(AppStateTrackerImpl.class))
177                 .when(() -> LocalServices.getService(AppStateTracker.class));
178         // Called in ConnectivityController constructor.
179         when(mContext.getSystemService(ConnectivityManager.class))
180                 .thenReturn(mock(ConnectivityManager.class));
181         when(mContext.getSystemService(NetworkPolicyManager.class))
182                 .thenReturn(mock(NetworkPolicyManager.class));
183         // Called in DeviceIdleJobsController constructor.
184         doReturn(mock(DeviceIdleInternal.class))
185                 .when(() -> LocalServices.getService(DeviceIdleInternal.class));
186         // Used in JobConcurrencyManager.
187         doReturn(mock(UserManagerInternal.class))
188                 .when(() -> LocalServices.getService(UserManagerInternal.class));
189         // Used in JobStatus.
190         doReturn(mock(JobSchedulerInternal.class))
191                 .when(() -> LocalServices.getService(JobSchedulerInternal.class));
192         // Called via IdleController constructor.
193         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
194         when(mContext.getResources()).thenReturn(mock(Resources.class));
195         // Called in QuotaController constructor.
196         doReturn(mock(PowerAllowlistInternal.class))
197                 .when(() -> LocalServices.getService(PowerAllowlistInternal.class));
198         IActivityManager activityManager = ActivityManager.getService();
199         spyOn(activityManager);
200         try {
201             doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
202         } catch (RemoteException e) {
203             fail("registerUidObserver threw exception: " + e.getMessage());
204         }
205         // Called by QuotaTracker
206         doReturn(mock(SystemServiceManager.class))
207                 .when(() -> LocalServices.getService(SystemServiceManager.class));
208 
209         JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
210         JobSchedulerService.sElapsedRealtimeClock =
211                 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
212         // Make sure the uptime is at least 24 hours so that tests that rely on high uptime work.
213         sUptimeMillisClock = getAdvancedClock(sUptimeMillisClock, 24 * HOUR_IN_MILLIS);
214         // Called by DeviceIdlenessTracker
215         when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
216 
217         setChargingPolicy(Integer.MIN_VALUE);
218 
219         ArgumentCaptor<ChargingPolicyChangeListener> chargingPolicyChangeListenerCaptor =
220                 ArgumentCaptor.forClass(ChargingPolicyChangeListener.class);
221 
222         mService = new TestJobSchedulerService(mContext);
223         mService.waitOnAsyncLoadingForTesting();
224 
225         verify(mBatteryManagerInternal).registerChargingPolicyChangeListener(
226                 chargingPolicyChangeListenerCaptor.capture());
227         mChargingPolicyChangeListener = chargingPolicyChangeListenerCaptor.getValue();
228     }
229 
230     @After
tearDown()231     public void tearDown() {
232         if (mMockingSession != null) {
233             mMockingSession.finishMocking();
234         }
235         mService.cancelJobsForUid(TEST_UID, true,
236                 JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN,
237                 "test cleanup");
238     }
239 
getAdvancedClock(Clock clock, long incrementMs)240     private Clock getAdvancedClock(Clock clock, long incrementMs) {
241         return Clock.offset(clock, Duration.ofMillis(incrementMs));
242     }
243 
advanceElapsedClock(long incrementMs)244     private void advanceElapsedClock(long incrementMs) {
245         JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
246                 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
247     }
248 
createJobInfo()249     private static JobInfo.Builder createJobInfo() {
250         return createJobInfo(351);
251     }
252 
createJobInfo(int jobId)253     private static JobInfo.Builder createJobInfo(int jobId) {
254         return new JobInfo.Builder(jobId, new ComponentName("foo", "bar"));
255     }
256 
createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder)257     private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder) {
258         return createJobStatus(testTag, jobInfoBuilder, 1234);
259     }
260 
createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder, int callingUid)261     private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
262             int callingUid) {
263         return createJobStatus(testTag, jobInfoBuilder, callingUid, "com.android.test");
264     }
265 
createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder, int callingUid, String sourcePkg)266     private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
267             int callingUid, String sourcePkg) {
268         return JobStatus.createFromJobInfo(
269                 jobInfoBuilder.build(), callingUid, sourcePkg, 0, "JSSTest", testTag);
270     }
271 
grantRunUserInitiatedJobsPermission(boolean grant)272     private void grantRunUserInitiatedJobsPermission(boolean grant) {
273         final int permissionStatus = grant
274                 ? PermissionChecker.PERMISSION_GRANTED : PermissionChecker.PERMISSION_HARD_DENIED;
275         doReturn(permissionStatus)
276                 .when(() -> PermissionChecker.checkPermissionForPreflight(
277                         any(), eq(android.Manifest.permission.RUN_USER_INITIATED_JOBS),
278                         anyInt(), anyInt(), anyString()));
279     }
280 
281     @Test
testGetMinJobExecutionGuaranteeMs()282     public void testGetMinJobExecutionGuaranteeMs() {
283         JobStatus ejMax = createJobStatus("testGetMinJobExecutionGuaranteeMs",
284                 createJobInfo(1).setExpedited(true));
285         JobStatus ejHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs",
286                 createJobInfo(2).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH));
287         JobStatus ejMaxDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs",
288                 createJobInfo(3).setExpedited(true));
289         JobStatus ejHighDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs",
290                 createJobInfo(4).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH));
291         JobStatus jobHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs",
292                 createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH));
293         JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs",
294                 createJobInfo(6));
295         JobStatus jobUIDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
296                 createJobInfo(9)
297                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
298 
299         spyOn(ejMax);
300         spyOn(ejHigh);
301         spyOn(ejMaxDowngraded);
302         spyOn(ejHighDowngraded);
303         spyOn(jobHigh);
304         spyOn(jobDef);
305         spyOn(jobUIDT);
306 
307         when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true);
308         when(ejHigh.shouldTreatAsExpeditedJob()).thenReturn(true);
309         when(ejMaxDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
310         when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
311         when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false);
312         when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false);
313         when(jobUIDT.shouldTreatAsUserInitiatedJob()).thenReturn(true);
314 
315         ConnectivityController connectivityController = mService.getConnectivityController();
316         spyOn(connectivityController);
317         mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
318         mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS = 2 * HOUR_IN_MILLIS;
319         mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
320         mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
321         mService.mConstants.RUNTIME_UI_LIMIT_MS = 6 * HOUR_IN_MILLIS;
322 
323         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
324                 mService.getMinJobExecutionGuaranteeMs(ejMax));
325         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
326                 mService.getMinJobExecutionGuaranteeMs(ejHigh));
327         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
328                 mService.getMinJobExecutionGuaranteeMs(ejMaxDowngraded));
329         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
330                 mService.getMinJobExecutionGuaranteeMs(ejHighDowngraded));
331         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
332                 mService.getMinJobExecutionGuaranteeMs(jobHigh));
333         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
334                 mService.getMinJobExecutionGuaranteeMs(jobDef));
335         // UserInitiated
336         grantRunUserInitiatedJobsPermission(false);
337         // Permission isn't granted, so it should just be treated as a regular job.
338         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
339                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
340 
341         grantRunUserInitiatedJobsPermission(true); // With permission
342         mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = true;
343         doReturn(ConnectivityController.UNKNOWN_TIME)
344                 .when(connectivityController).getEstimatedTransferTimeMs(any());
345         assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
346                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
347         doReturn(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS / 2)
348                 .when(connectivityController).getEstimatedTransferTimeMs(any());
349         assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
350                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
351         final long estimatedTransferTimeMs =
352                 mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS * 2;
353         doReturn(estimatedTransferTimeMs)
354                 .when(connectivityController).getEstimatedTransferTimeMs(any());
355         assertEquals((long) (estimatedTransferTimeMs
356                         * mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
357                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
358         doReturn(mService.mConstants.RUNTIME_UI_LIMIT_MS * 2)
359                 .when(connectivityController).getEstimatedTransferTimeMs(any());
360         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
361                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
362 
363         mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false;
364         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
365                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
366     }
367 
368     @Test
testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_disabled()369     public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_disabled() {
370         JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
371                 createJobInfo(1)
372                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
373         JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
374                 createJobInfo(2).setExpedited(true));
375         JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
376                 createJobInfo(3));
377         spyOn(jobUij);
378         when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
379         jobUij.startedAsUserInitiatedJob = true;
380         spyOn(jobEj);
381         when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
382         jobEj.startedAsExpeditedJob = true;
383 
384         mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
385         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
386         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
387         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
388         mService.updateQuotaTracker();
389         mService.resetScheduleQuota();
390 
391         // Safeguards disabled -> no penalties.
392         grantRunUserInitiatedJobsPermission(true);
393         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
394                 mService.getMinJobExecutionGuaranteeMs(jobUij));
395         grantRunUserInitiatedJobsPermission(false);
396         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
397                 mService.getMinJobExecutionGuaranteeMs(jobUij));
398         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
399                 mService.getMinJobExecutionGuaranteeMs(jobEj));
400         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
401                 mService.getMinJobExecutionGuaranteeMs(jobReg));
402 
403         // 1 UIJ timeout. No max execution penalty yet.
404         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
405         grantRunUserInitiatedJobsPermission(true);
406         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
407                 mService.getMinJobExecutionGuaranteeMs(jobUij));
408         grantRunUserInitiatedJobsPermission(false);
409         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
410                 mService.getMinJobExecutionGuaranteeMs(jobUij));
411         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
412                 mService.getMinJobExecutionGuaranteeMs(jobEj));
413         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
414                 mService.getMinJobExecutionGuaranteeMs(jobReg));
415 
416         // 2 UIJ timeouts. Safeguards disabled -> no penalties.
417         jobUij.madeActive =
418                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
419         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
420         grantRunUserInitiatedJobsPermission(true);
421         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
422                 mService.getMinJobExecutionGuaranteeMs(jobUij));
423         grantRunUserInitiatedJobsPermission(false);
424         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
425                 mService.getMinJobExecutionGuaranteeMs(jobUij));
426         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
427                 mService.getMinJobExecutionGuaranteeMs(jobEj));
428         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
429                 mService.getMinJobExecutionGuaranteeMs(jobReg));
430 
431         // 1 EJ timeout. No max execution penalty yet.
432         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
433         grantRunUserInitiatedJobsPermission(true);
434         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
435                 mService.getMinJobExecutionGuaranteeMs(jobUij));
436         grantRunUserInitiatedJobsPermission(false);
437         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
438                 mService.getMinJobExecutionGuaranteeMs(jobUij));
439         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
440                 mService.getMinJobExecutionGuaranteeMs(jobEj));
441         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
442                 mService.getMinJobExecutionGuaranteeMs(jobReg));
443 
444         // 2 EJ timeouts. Safeguards disabled -> no penalties.
445         jobEj.madeActive =
446                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
447         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
448         grantRunUserInitiatedJobsPermission(true);
449         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
450                 mService.getMinJobExecutionGuaranteeMs(jobUij));
451         grantRunUserInitiatedJobsPermission(false);
452         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
453                 mService.getMinJobExecutionGuaranteeMs(jobUij));
454         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
455                 mService.getMinJobExecutionGuaranteeMs(jobEj));
456         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
457                 mService.getMinJobExecutionGuaranteeMs(jobReg));
458 
459         // 1 reg timeout. No max execution penalty yet.
460         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
461         grantRunUserInitiatedJobsPermission(true);
462         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
463                 mService.getMinJobExecutionGuaranteeMs(jobUij));
464         grantRunUserInitiatedJobsPermission(false);
465         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
466                 mService.getMinJobExecutionGuaranteeMs(jobUij));
467         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
468                 mService.getMinJobExecutionGuaranteeMs(jobEj));
469         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
470                 mService.getMinJobExecutionGuaranteeMs(jobReg));
471 
472         // 2 Reg timeouts. Safeguards disabled -> no penalties.
473         jobReg.madeActive =
474                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
475         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
476         grantRunUserInitiatedJobsPermission(true);
477         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
478                 mService.getMinJobExecutionGuaranteeMs(jobUij));
479         grantRunUserInitiatedJobsPermission(false);
480         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
481                 mService.getMinJobExecutionGuaranteeMs(jobUij));
482         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
483                 mService.getMinJobExecutionGuaranteeMs(jobEj));
484         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
485                 mService.getMinJobExecutionGuaranteeMs(jobReg));
486     }
487 
488     @Test
testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_enabled()489     public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_enabled() {
490         JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
491                 createJobInfo(1)
492                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
493         JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
494                 createJobInfo(2).setExpedited(true));
495         JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
496                 createJobInfo(3));
497         spyOn(jobUij);
498         when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
499         jobUij.startedAsUserInitiatedJob = true;
500         spyOn(jobEj);
501         when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
502         jobEj.startedAsExpeditedJob = true;
503 
504         mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
505         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
506         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
507         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
508         mService.updateQuotaTracker();
509         mService.resetScheduleQuota();
510 
511         // No timeouts -> no penalties.
512         grantRunUserInitiatedJobsPermission(true);
513         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
514                 mService.getMinJobExecutionGuaranteeMs(jobUij));
515         grantRunUserInitiatedJobsPermission(false);
516         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
517                 mService.getMinJobExecutionGuaranteeMs(jobUij));
518         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
519                 mService.getMinJobExecutionGuaranteeMs(jobEj));
520         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
521                 mService.getMinJobExecutionGuaranteeMs(jobReg));
522 
523         // 1 UIJ timeout. No execution penalty yet.
524         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
525         grantRunUserInitiatedJobsPermission(true);
526         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
527                 mService.getMinJobExecutionGuaranteeMs(jobUij));
528         grantRunUserInitiatedJobsPermission(false);
529         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
530                 mService.getMinJobExecutionGuaranteeMs(jobUij));
531         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
532                 mService.getMinJobExecutionGuaranteeMs(jobEj));
533         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
534                 mService.getMinJobExecutionGuaranteeMs(jobReg));
535 
536         // Not a timeout -> 1 UIJ timeout. No execution penalty yet.
537         jobUij.madeActive = sUptimeMillisClock.millis() - 1;
538         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
539         grantRunUserInitiatedJobsPermission(true);
540         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
541                 mService.getMinJobExecutionGuaranteeMs(jobUij));
542         grantRunUserInitiatedJobsPermission(false);
543         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
544                 mService.getMinJobExecutionGuaranteeMs(jobUij));
545         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
546                 mService.getMinJobExecutionGuaranteeMs(jobEj));
547         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
548                 mService.getMinJobExecutionGuaranteeMs(jobReg));
549 
550         // 2 UIJ timeouts. Min execution penalty only for UIJs.
551         jobUij.madeActive =
552                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
553         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
554         grantRunUserInitiatedJobsPermission(true);
555         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
556                 mService.getMinJobExecutionGuaranteeMs(jobUij));
557         grantRunUserInitiatedJobsPermission(false);
558         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
559                 mService.getMinJobExecutionGuaranteeMs(jobUij));
560         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
561                 mService.getMinJobExecutionGuaranteeMs(jobEj));
562         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
563                 mService.getMinJobExecutionGuaranteeMs(jobReg));
564 
565         // 1 EJ timeout. No max execution penalty yet.
566         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
567         grantRunUserInitiatedJobsPermission(true);
568         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
569                 mService.getMinJobExecutionGuaranteeMs(jobUij));
570         grantRunUserInitiatedJobsPermission(false);
571         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
572                 mService.getMinJobExecutionGuaranteeMs(jobUij));
573         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
574                 mService.getMinJobExecutionGuaranteeMs(jobEj));
575         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
576                 mService.getMinJobExecutionGuaranteeMs(jobReg));
577 
578         // 2 EJ timeouts. Max execution penalty for EJs.
579         jobEj.madeActive =
580                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
581         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
582         grantRunUserInitiatedJobsPermission(true);
583         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
584                 mService.getMinJobExecutionGuaranteeMs(jobUij));
585         grantRunUserInitiatedJobsPermission(false);
586         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
587                 mService.getMinJobExecutionGuaranteeMs(jobUij));
588         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
589                 mService.getMinJobExecutionGuaranteeMs(jobEj));
590         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
591                 mService.getMinJobExecutionGuaranteeMs(jobReg));
592 
593         // 1 reg timeout. No max execution penalty yet.
594         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
595         grantRunUserInitiatedJobsPermission(true);
596         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
597                 mService.getMinJobExecutionGuaranteeMs(jobUij));
598         grantRunUserInitiatedJobsPermission(false);
599         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
600                 mService.getMinJobExecutionGuaranteeMs(jobUij));
601         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
602                 mService.getMinJobExecutionGuaranteeMs(jobEj));
603         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
604                 mService.getMinJobExecutionGuaranteeMs(jobReg));
605 
606         // 2 Reg timeouts. Max execution penalty for regular jobs.
607         jobReg.madeActive =
608                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
609         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
610         grantRunUserInitiatedJobsPermission(true);
611         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
612                 mService.getMinJobExecutionGuaranteeMs(jobUij));
613         grantRunUserInitiatedJobsPermission(false);
614         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
615                 mService.getMinJobExecutionGuaranteeMs(jobUij));
616         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
617                 mService.getMinJobExecutionGuaranteeMs(jobEj));
618         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
619                 mService.getMinJobExecutionGuaranteeMs(jobReg));
620     }
621 
622     @Test
testGetMaxJobExecutionTimeMs()623     public void testGetMaxJobExecutionTimeMs() {
624         JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
625                 createJobInfo(10)
626                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
627         JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs",
628                 createJobInfo(2).setExpedited(true));
629         JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs",
630                 createJobInfo(3));
631         spyOn(jobUIDT);
632         when(jobUIDT.shouldTreatAsUserInitiatedJob()).thenReturn(true);
633         spyOn(jobEj);
634         when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
635 
636         QuotaController quotaController = mService.getQuotaController();
637         spyOn(quotaController);
638         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
639                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
640 
641         grantRunUserInitiatedJobsPermission(true);
642         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
643                 mService.getMaxJobExecutionTimeMs(jobUIDT));
644         grantRunUserInitiatedJobsPermission(false);
645         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
646                 mService.getMaxJobExecutionTimeMs(jobUIDT));
647 
648         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
649                 mService.getMaxJobExecutionTimeMs(jobEj));
650         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
651                 mService.getMaxJobExecutionTimeMs(jobReg));
652     }
653 
654     @Test
testGetMaxJobExecutionTimeMs_timeoutSafeguards_disabled()655     public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_disabled() {
656         JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
657                 createJobInfo(1)
658                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
659         JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
660                 createJobInfo(2).setExpedited(true));
661         JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
662                 createJobInfo(3));
663         spyOn(jobUij);
664         when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
665         jobUij.startedAsUserInitiatedJob = true;
666         spyOn(jobEj);
667         when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
668         jobEj.startedAsExpeditedJob = true;
669 
670         QuotaController quotaController = mService.getQuotaController();
671         spyOn(quotaController);
672         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
673                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
674 
675         mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
676         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
677         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
678         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
679         mService.updateQuotaTracker();
680         mService.resetScheduleQuota();
681 
682         // Safeguards disabled -> no penalties.
683         grantRunUserInitiatedJobsPermission(true);
684         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
685                 mService.getMaxJobExecutionTimeMs(jobUij));
686         grantRunUserInitiatedJobsPermission(false);
687         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
688                 mService.getMaxJobExecutionTimeMs(jobUij));
689         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
690                 mService.getMaxJobExecutionTimeMs(jobEj));
691         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
692                 mService.getMaxJobExecutionTimeMs(jobReg));
693 
694         // 1 UIJ timeout. No max execution penalty yet.
695         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
696         grantRunUserInitiatedJobsPermission(true);
697         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
698                 mService.getMaxJobExecutionTimeMs(jobUij));
699         grantRunUserInitiatedJobsPermission(false);
700         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
701                 mService.getMaxJobExecutionTimeMs(jobUij));
702         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
703                 mService.getMaxJobExecutionTimeMs(jobEj));
704         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
705                 mService.getMaxJobExecutionTimeMs(jobReg));
706 
707         // 2 UIJ timeouts. Safeguards disabled -> no penalties.
708         jobUij.madeActive =
709                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
710         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
711         grantRunUserInitiatedJobsPermission(true);
712         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
713                 mService.getMaxJobExecutionTimeMs(jobUij));
714         grantRunUserInitiatedJobsPermission(false);
715         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
716                 mService.getMaxJobExecutionTimeMs(jobUij));
717         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
718                 mService.getMaxJobExecutionTimeMs(jobEj));
719         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
720                 mService.getMaxJobExecutionTimeMs(jobReg));
721 
722         // 1 EJ timeout. No max execution penalty yet.
723         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
724         grantRunUserInitiatedJobsPermission(true);
725         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
726                 mService.getMaxJobExecutionTimeMs(jobUij));
727         grantRunUserInitiatedJobsPermission(false);
728         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
729                 mService.getMaxJobExecutionTimeMs(jobUij));
730         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
731                 mService.getMaxJobExecutionTimeMs(jobEj));
732         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
733                 mService.getMaxJobExecutionTimeMs(jobReg));
734 
735         // 2 EJ timeouts. Safeguards disabled -> no penalties.
736         jobEj.madeActive =
737                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
738         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
739         grantRunUserInitiatedJobsPermission(true);
740         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
741                 mService.getMaxJobExecutionTimeMs(jobUij));
742         grantRunUserInitiatedJobsPermission(false);
743         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
744                 mService.getMaxJobExecutionTimeMs(jobUij));
745         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
746                 mService.getMaxJobExecutionTimeMs(jobEj));
747         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
748                 mService.getMaxJobExecutionTimeMs(jobReg));
749 
750         // 1 reg timeout. No max execution penalty yet.
751         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
752         grantRunUserInitiatedJobsPermission(true);
753         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
754                 mService.getMaxJobExecutionTimeMs(jobUij));
755         grantRunUserInitiatedJobsPermission(false);
756         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
757                 mService.getMaxJobExecutionTimeMs(jobUij));
758         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
759                 mService.getMaxJobExecutionTimeMs(jobEj));
760         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
761                 mService.getMaxJobExecutionTimeMs(jobReg));
762 
763         // 2 Reg timeouts. Safeguards disabled -> no penalties.
764         jobReg.madeActive =
765                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
766         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
767         grantRunUserInitiatedJobsPermission(true);
768         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
769                 mService.getMaxJobExecutionTimeMs(jobUij));
770         grantRunUserInitiatedJobsPermission(false);
771         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
772                 mService.getMaxJobExecutionTimeMs(jobUij));
773         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
774                 mService.getMaxJobExecutionTimeMs(jobEj));
775         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
776                 mService.getMaxJobExecutionTimeMs(jobReg));
777     }
778 
779     @Test
testGetMaxJobExecutionTimeMs_timeoutSafeguards_enabled()780     public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_enabled() {
781         JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
782                 createJobInfo(1)
783                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
784         JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
785                 createJobInfo(2).setExpedited(true));
786         JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
787                 createJobInfo(3));
788         spyOn(jobUij);
789         when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
790         jobUij.startedAsUserInitiatedJob = true;
791         spyOn(jobEj);
792         when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
793         jobEj.startedAsExpeditedJob = true;
794 
795         QuotaController quotaController = mService.getQuotaController();
796         spyOn(quotaController);
797         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
798                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
799 
800         mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
801         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
802         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
803         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
804         mService.updateQuotaTracker();
805         mService.resetScheduleQuota();
806 
807         // No timeouts -> no penalties.
808         grantRunUserInitiatedJobsPermission(true);
809         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
810                 mService.getMaxJobExecutionTimeMs(jobUij));
811         grantRunUserInitiatedJobsPermission(false);
812         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
813                 mService.getMaxJobExecutionTimeMs(jobUij));
814         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
815                 mService.getMaxJobExecutionTimeMs(jobEj));
816         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
817                 mService.getMaxJobExecutionTimeMs(jobReg));
818 
819         // 1 UIJ timeout. No max execution penalty yet.
820         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
821         grantRunUserInitiatedJobsPermission(true);
822         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
823                 mService.getMaxJobExecutionTimeMs(jobUij));
824         grantRunUserInitiatedJobsPermission(false);
825         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
826                 mService.getMaxJobExecutionTimeMs(jobUij));
827         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
828                 mService.getMaxJobExecutionTimeMs(jobEj));
829         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
830                 mService.getMaxJobExecutionTimeMs(jobReg));
831 
832         // Not a timeout -> 1 UIJ timeout. No max execution penalty yet.
833         jobUij.madeActive = sUptimeMillisClock.millis() - 1;
834         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
835         grantRunUserInitiatedJobsPermission(true);
836         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
837                 mService.getMaxJobExecutionTimeMs(jobUij));
838         grantRunUserInitiatedJobsPermission(false);
839         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
840                 mService.getMaxJobExecutionTimeMs(jobUij));
841         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
842                 mService.getMaxJobExecutionTimeMs(jobEj));
843         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
844                 mService.getMaxJobExecutionTimeMs(jobReg));
845 
846         // 2 UIJ timeouts. Max execution penalty only for UIJs.
847         jobUij.madeActive =
848                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
849         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
850         grantRunUserInitiatedJobsPermission(true);
851         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
852                 mService.getMaxJobExecutionTimeMs(jobUij));
853         grantRunUserInitiatedJobsPermission(false);
854         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
855                 mService.getMaxJobExecutionTimeMs(jobUij));
856         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
857                 mService.getMaxJobExecutionTimeMs(jobEj));
858         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
859                 mService.getMaxJobExecutionTimeMs(jobReg));
860 
861         // 1 EJ timeout. No max execution penalty yet.
862         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
863         grantRunUserInitiatedJobsPermission(true);
864         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
865                 mService.getMaxJobExecutionTimeMs(jobUij));
866         grantRunUserInitiatedJobsPermission(false);
867         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
868                 mService.getMaxJobExecutionTimeMs(jobUij));
869         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
870                 mService.getMaxJobExecutionTimeMs(jobEj));
871         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
872                 mService.getMaxJobExecutionTimeMs(jobReg));
873 
874         // Not a timeout -> 1 EJ timeout. No max execution penalty yet.
875         jobEj.madeActive = sUptimeMillisClock.millis() - 1;
876         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
877         grantRunUserInitiatedJobsPermission(true);
878         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
879                 mService.getMaxJobExecutionTimeMs(jobUij));
880         grantRunUserInitiatedJobsPermission(false);
881         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
882                 mService.getMaxJobExecutionTimeMs(jobUij));
883         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
884                 mService.getMaxJobExecutionTimeMs(jobEj));
885         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
886                 mService.getMaxJobExecutionTimeMs(jobReg));
887 
888         // 2 EJ timeouts. Max execution penalty for EJs.
889         jobEj.madeActive =
890                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
891         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
892         grantRunUserInitiatedJobsPermission(true);
893         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
894                 mService.getMaxJobExecutionTimeMs(jobUij));
895         grantRunUserInitiatedJobsPermission(false);
896         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
897                 mService.getMaxJobExecutionTimeMs(jobUij));
898         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
899                 mService.getMaxJobExecutionTimeMs(jobEj));
900         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
901                 mService.getMaxJobExecutionTimeMs(jobReg));
902 
903         // 1 reg timeout. No max execution penalty yet.
904         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
905         grantRunUserInitiatedJobsPermission(true);
906         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
907                 mService.getMaxJobExecutionTimeMs(jobUij));
908         grantRunUserInitiatedJobsPermission(false);
909         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
910                 mService.getMaxJobExecutionTimeMs(jobUij));
911         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
912                 mService.getMaxJobExecutionTimeMs(jobEj));
913         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
914                 mService.getMaxJobExecutionTimeMs(jobReg));
915 
916         // Not a timeout -> 1 reg timeout. No max execution penalty yet.
917         jobReg.madeActive = sUptimeMillisClock.millis() - 1;
918         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
919         grantRunUserInitiatedJobsPermission(true);
920         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
921                 mService.getMaxJobExecutionTimeMs(jobUij));
922         grantRunUserInitiatedJobsPermission(false);
923         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
924                 mService.getMaxJobExecutionTimeMs(jobUij));
925         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
926                 mService.getMaxJobExecutionTimeMs(jobEj));
927         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
928                 mService.getMaxJobExecutionTimeMs(jobReg));
929 
930         // 2 Reg timeouts. Max execution penalty for regular jobs.
931         jobReg.madeActive =
932                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
933         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
934         grantRunUserInitiatedJobsPermission(true);
935         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
936                 mService.getMaxJobExecutionTimeMs(jobUij));
937         grantRunUserInitiatedJobsPermission(false);
938         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
939                 mService.getMaxJobExecutionTimeMs(jobUij));
940         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
941                 mService.getMaxJobExecutionTimeMs(jobEj));
942         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
943                 mService.getMaxJobExecutionTimeMs(jobReg));
944     }
945 
946     /**
947      * Confirm that
948      * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
949      * returns a job that is no longer allowed to run as a user-initiated job after it hits
950      * the cumulative execution limit.
951      */
952     @Test
testGetRescheduleJobForFailure_cumulativeExecution()953     public void testGetRescheduleJobForFailure_cumulativeExecution() {
954         JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
955                 createJobInfo()
956                         .setUserInitiated(true)
957                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
958         assertTrue(originalJob.shouldTreatAsUserInitiatedJob());
959 
960         // Cumulative time = 0
961         JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
962                 JobParameters.STOP_REASON_UNDEFINED,
963                 JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
964         assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
965 
966         // Cumulative time = 50% of limit
967         rescheduledJob.incrementCumulativeExecutionTime(
968                 mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2);
969         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
970                 JobParameters.STOP_REASON_UNDEFINED,
971                 JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
972         assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
973 
974         // Cumulative time = 99.999999% of limit
975         rescheduledJob.incrementCumulativeExecutionTime(
976                 mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2 - 1);
977         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
978                 JobParameters.STOP_REASON_UNDEFINED,
979                 JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
980         assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
981 
982         // Cumulative time = 100+% of limit
983         rescheduledJob.incrementCumulativeExecutionTime(2);
984         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
985                 JobParameters.STOP_REASON_UNDEFINED,
986                 JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
987         assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
988     }
989 
990     /**
991      * Confirm that
992      * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
993      * returns a job with the correct delay and deadline constraints.
994      */
995     @Test
testGetRescheduleJobForFailure_timingCalculations()996     public void testGetRescheduleJobForFailure_timingCalculations() {
997         final long nowElapsed = sElapsedRealtimeClock.millis();
998         final long initialBackoffMs = MINUTE_IN_MILLIS;
999         mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
1000 
1001         JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
1002                 createJobInfo()
1003                         .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
1004         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
1005         assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
1006 
1007         // failure = 0, systemStop = 1
1008         JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
1009                 JobParameters.STOP_REASON_DEVICE_STATE,
1010                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1011         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, rescheduledJob.getEarliestRunTime());
1012         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1013 
1014         // failure = 0, systemStop = 2
1015         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1016                 JobParameters.STOP_REASON_DEVICE_STATE,
1017                 JobParameters.INTERNAL_STOP_REASON_PREEMPT);
1018         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, rescheduledJob.getEarliestRunTime());
1019         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1020         // failure = 0, systemStop = 3
1021         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1022                 JobParameters.STOP_REASON_CONSTRAINT_CHARGING,
1023                 JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED);
1024         assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
1025         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1026 
1027         // failure = 0, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
1028         for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) {
1029             rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1030                     JobParameters.STOP_REASON_SYSTEM_PROCESSING,
1031                     JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED);
1032         }
1033         assertEquals(nowElapsed + 2 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
1034         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1035 
1036         // failure = 1, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
1037         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1038                 JobParameters.STOP_REASON_TIMEOUT,
1039                 JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
1040         assertEquals(nowElapsed + 3 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
1041         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1042 
1043         // failure = 2, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
1044         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1045                 JobParameters.STOP_REASON_UNDEFINED,
1046                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1047         assertEquals(nowElapsed + 4 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
1048         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1049 
1050         // failure = 3, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
1051         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1052                 JobParameters.STOP_REASON_UNDEFINED,
1053                 JobParameters.INTERNAL_STOP_REASON_ANR);
1054         assertEquals(nowElapsed + 5 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
1055         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1056     }
1057 
1058     /**
1059      * Confirm that
1060      * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
1061      * returns a job with the correct delay for abandoned jobs.
1062      */
1063     @Test
1064     @EnableFlags(FLAG_HANDLE_ABANDONED_JOBS)
testGetRescheduleJobForFailure_abandonedJob()1065     public void testGetRescheduleJobForFailure_abandonedJob() {
1066         final long nowElapsed = sElapsedRealtimeClock.millis();
1067         final long initialBackoffMs = MINUTE_IN_MILLIS;
1068         mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
1069 
1070         JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
1071                 createJobInfo()
1072                         .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
1073         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
1074         assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
1075 
1076         // failure = 1, systemStop = 0, abandoned = 1
1077         JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
1078                 JobParameters.STOP_REASON_DEVICE_STATE,
1079                 JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
1080         assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
1081         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1082 
1083         // failure = 2, systemstop = 0, abandoned = 2
1084         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1085                 JobParameters.STOP_REASON_DEVICE_STATE,
1086                 JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
1087         assertEquals(nowElapsed + (2 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
1088         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1089 
1090         // failure = 3, systemstop = 0, abandoned = 3
1091         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1092                 JobParameters.STOP_REASON_DEVICE_STATE,
1093                 JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
1094         assertEquals(nowElapsed + (3 * initialBackoffMs), rescheduledJob.getEarliestRunTime());
1095         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1096 
1097         // failure = 4, systemstop = 0, abandoned = 4
1098         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1099                 JobParameters.STOP_REASON_DEVICE_STATE,
1100                 JobParameters.INTERNAL_STOP_REASON_TIMEOUT_ABANDONED);
1101         assertEquals(
1102                 nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
1103                 rescheduledJob.getEarliestRunTime());
1104         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1105 
1106         // failure = 4, systemstop = 1, abandoned = 4
1107         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1108                 JobParameters.STOP_REASON_DEVICE_STATE,
1109                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1110         assertEquals(
1111                 nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 3)),
1112                 rescheduledJob.getEarliestRunTime());
1113         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1114 
1115         // failure = 4, systemStop =  4  / SYSTEM_STOP_TO_FAILURE_RATIO, abandoned = 4
1116         for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) {
1117             rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1118                     JobParameters.STOP_REASON_SYSTEM_PROCESSING,
1119                     JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED);
1120         }
1121         assertEquals(
1122                 nowElapsed + ((long) Math.scalb((float) initialBackoffMs, 4)),
1123                 rescheduledJob.getEarliestRunTime());
1124         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1125     }
1126 
1127     /**
1128      * Confirm that
1129      * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
1130      * returns a job that is correctly marked as demoted by the user.
1131      */
1132     @Test
testGetRescheduleJobForFailure_userDemotion()1133     public void testGetRescheduleJobForFailure_userDemotion() {
1134         JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
1135         assertEquals(0, originalJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
1136 
1137         // Reschedule for a non-user reason
1138         JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
1139                 JobParameters.STOP_REASON_DEVICE_STATE,
1140                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1141         assertEquals(0,
1142                 rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
1143 
1144         // Reschedule for a user reason
1145         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1146                 JobParameters.STOP_REASON_USER,
1147                 JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
1148         assertNotEquals(0,
1149                 rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
1150 
1151         // Reschedule a previously demoted job for a non-user reason
1152         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1153                 JobParameters.STOP_REASON_CONSTRAINT_CHARGING,
1154                 JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED);
1155         assertNotEquals(0,
1156                 rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
1157     }
1158 
1159     /**
1160      * Confirm that
1161      * returns {@code null} when for user-visible jobs stopped by the user.
1162      */
1163     @Test
testGetRescheduleJobForFailure_userStopped()1164     public void testGetRescheduleJobForFailure_userStopped() {
1165         JobStatus uiJob = createJobStatus("testGetRescheduleJobForFailure",
1166                 createJobInfo().setUserInitiated(true)
1167                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
1168         JobStatus uvJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
1169         spyOn(uvJob);
1170         doReturn(true).when(uvJob).isUserVisibleJob();
1171         JobStatus regJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
1172 
1173         // Reschedule for a non-user reason
1174         JobStatus rescheduledUiJob = mService.getRescheduleJobForFailureLocked(uiJob,
1175                 JobParameters.STOP_REASON_DEVICE_STATE,
1176                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1177         JobStatus rescheduledUvJob = mService.getRescheduleJobForFailureLocked(uvJob,
1178                 JobParameters.STOP_REASON_DEVICE_STATE,
1179                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1180         JobStatus rescheduledRegJob = mService.getRescheduleJobForFailureLocked(regJob,
1181                 JobParameters.STOP_REASON_DEVICE_STATE,
1182                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1183         assertNotNull(rescheduledUiJob);
1184         assertNotNull(rescheduledUvJob);
1185         assertNotNull(rescheduledRegJob);
1186 
1187         // Reschedule for a user reason. The user-visible jobs shouldn't be rescheduled.
1188         spyOn(rescheduledUvJob);
1189         doReturn(true).when(rescheduledUvJob).isUserVisibleJob();
1190         rescheduledUiJob = mService.getRescheduleJobForFailureLocked(rescheduledUiJob,
1191                 JobParameters.STOP_REASON_USER,
1192                 JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
1193         rescheduledUvJob = mService.getRescheduleJobForFailureLocked(rescheduledUvJob,
1194                 JobParameters.STOP_REASON_USER,
1195                 JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
1196         rescheduledRegJob = mService.getRescheduleJobForFailureLocked(rescheduledRegJob,
1197                 JobParameters.STOP_REASON_USER,
1198                 JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
1199         assertNull(rescheduledUiJob);
1200         assertNull(rescheduledUvJob);
1201         assertNotNull(rescheduledRegJob);
1202     }
1203 
1204     /**
1205      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1206      * with the correct delay and deadline constraints if the periodic job is scheduled with the
1207      * minimum possible period.
1208      */
1209     @Test
testGetRescheduleJobForPeriodic_minPeriod()1210     public void testGetRescheduleJobForPeriodic_minPeriod() {
1211         final long now = sElapsedRealtimeClock.millis();
1212         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
1213                 createJobInfo().setPeriodic(15 * MINUTE_IN_MILLIS));
1214         final long nextWindowStartTime = now + 15 * MINUTE_IN_MILLIS;
1215         final long nextWindowEndTime = now + 30 * MINUTE_IN_MILLIS;
1216 
1217         for (int i = 0; i < 25; i++) {
1218             JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1219             assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1220             assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1221             advanceElapsedClock(30_000); // 30 seconds
1222         }
1223 
1224         for (int i = 0; i < 5; i++) {
1225             // Window buffering in last 1/6 of window.
1226             JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1227             assertEquals(nextWindowStartTime + i * 30_000, rescheduledJob.getEarliestRunTime());
1228             assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1229             advanceElapsedClock(30_000); // 30 seconds
1230         }
1231     }
1232 
1233     /**
1234      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1235      * with the correct delay and deadline constraints if the periodic job is scheduled with a
1236      * period that's too large.
1237      */
1238     @Test
testGetRescheduleJobForPeriodic_largePeriod()1239     public void testGetRescheduleJobForPeriodic_largePeriod() {
1240         final long now = sElapsedRealtimeClock.millis();
1241         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
1242                 createJobInfo().setPeriodic(2 * 365 * DAY_IN_MILLIS));
1243         assertEquals(now, job.getEarliestRunTime());
1244         // Periods are capped at 365 days (1 year).
1245         assertEquals(now + 365 * DAY_IN_MILLIS, job.getLatestRunTimeElapsed());
1246         final long nextWindowStartTime = now + 365 * DAY_IN_MILLIS;
1247         final long nextWindowEndTime = nextWindowStartTime + 365 * DAY_IN_MILLIS;
1248 
1249         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1250         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1251         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1252     }
1253 
1254     /**
1255      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1256      * with the correct delay and deadline constraints if the periodic job is completed and
1257      * rescheduled while run in its expected running window.
1258      */
1259     @Test
testGetRescheduleJobForPeriodic_insideWindow()1260     public void testGetRescheduleJobForPeriodic_insideWindow() {
1261         final long now = sElapsedRealtimeClock.millis();
1262         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
1263                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1264         final long nextWindowStartTime = now + HOUR_IN_MILLIS;
1265         final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1266 
1267         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1268         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1269         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1270 
1271         advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 10 minutes
1272 
1273         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1274         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1275         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1276 
1277         advanceElapsedClock(20 * MINUTE_IN_MILLIS); // now + 30 minutes
1278 
1279         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1280         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1281         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1282 
1283         advanceElapsedClock(25 * MINUTE_IN_MILLIS); // now + 55 minutes
1284 
1285         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1286         // Shifted because it's close to the end of the window.
1287         assertEquals(nextWindowStartTime + 5 * MINUTE_IN_MILLIS,
1288                 rescheduledJob.getEarliestRunTime());
1289         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1290 
1291         advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 59 minutes
1292 
1293         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1294         // Shifted because it's close to the end of the window.
1295         assertEquals(nextWindowStartTime + 9 * MINUTE_IN_MILLIS,
1296                 rescheduledJob.getEarliestRunTime());
1297         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1298     }
1299 
1300     /**
1301      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1302      * with an extra delay and correct deadline constraint if the periodic job is completed near the
1303      * end of its expected running window.
1304      */
1305     @Test
testGetRescheduleJobForPeriodic_closeToEndOfWindow()1306     public void testGetRescheduleJobForPeriodic_closeToEndOfWindow() {
1307         JobStatus frequentJob = createJobStatus(
1308                 "testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1309                 createJobInfo().setPeriodic(15 * MINUTE_IN_MILLIS));
1310         long now = sElapsedRealtimeClock.millis();
1311         long nextWindowStartTime = now + 15 * MINUTE_IN_MILLIS;
1312         long nextWindowEndTime = now + 30 * MINUTE_IN_MILLIS;
1313 
1314         // At the beginning of the window. Next window should be unaffected.
1315         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
1316         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1317         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1318 
1319         // Halfway through window. Next window should be unaffected.
1320         advanceElapsedClock((long) (7.5 * MINUTE_IN_MILLIS));
1321         rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
1322         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1323         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1324 
1325         // In last 1/6 of window. Next window start time should be shifted slightly.
1326         advanceElapsedClock(6 * MINUTE_IN_MILLIS);
1327         rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
1328         assertEquals(nextWindowStartTime + MINUTE_IN_MILLIS,
1329                 rescheduledJob.getEarliestRunTime());
1330         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1331 
1332         JobStatus mediumJob = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1333                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1334         now = sElapsedRealtimeClock.millis();
1335         nextWindowStartTime = now + HOUR_IN_MILLIS;
1336         nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1337 
1338         // At the beginning of the window. Next window should be unaffected.
1339         rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
1340         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1341         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1342 
1343         // Halfway through window. Next window should be unaffected.
1344         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1345         rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
1346         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1347         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1348 
1349         // At the edge 1/6 of window. Next window should be unaffected.
1350         advanceElapsedClock(20 * MINUTE_IN_MILLIS);
1351         rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
1352         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1353         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1354 
1355         // In last 1/6 of window. Next window start time should be shifted slightly.
1356         advanceElapsedClock(6 * MINUTE_IN_MILLIS);
1357         rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
1358         assertEquals(nextWindowStartTime + (6 * MINUTE_IN_MILLIS),
1359                 rescheduledJob.getEarliestRunTime());
1360         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1361 
1362         JobStatus longJob = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1363                 createJobInfo().setPeriodic(6 * HOUR_IN_MILLIS));
1364         now = sElapsedRealtimeClock.millis();
1365         nextWindowStartTime = now + 6 * HOUR_IN_MILLIS;
1366         nextWindowEndTime = now + 12 * HOUR_IN_MILLIS;
1367 
1368         // At the beginning of the window. Next window should be unaffected.
1369         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1370         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1371         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1372 
1373         // Halfway through window. Next window should be unaffected.
1374         advanceElapsedClock(3 * HOUR_IN_MILLIS);
1375         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1376         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1377         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1378 
1379         // At the edge 1/6 of window. Next window should be unaffected.
1380         advanceElapsedClock(2 * HOUR_IN_MILLIS);
1381         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1382         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1383         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1384 
1385         // In last 1/6 of window. Next window should be unaffected since we're over the shift cap.
1386         advanceElapsedClock(15 * MINUTE_IN_MILLIS);
1387         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1388         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1389         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1390 
1391         // In last 1/6 of window. Next window start time should be shifted slightly.
1392         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1393         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1394         assertEquals(nextWindowStartTime + (30 * MINUTE_IN_MILLIS),
1395                 rescheduledJob.getEarliestRunTime());
1396         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1397 
1398         // Flex duration close to period duration.
1399         JobStatus gameyFlex = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1400                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 59 * MINUTE_IN_MILLIS));
1401         now = sElapsedRealtimeClock.millis();
1402         nextWindowStartTime = now + HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
1403         nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1404         advanceElapsedClock(MINUTE_IN_MILLIS);
1405 
1406         // At the beginning of the window. Next window should be unaffected.
1407         rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
1408         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1409         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1410 
1411         // Halfway through window. Next window should be unaffected.
1412         advanceElapsedClock(29 * MINUTE_IN_MILLIS);
1413         rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
1414         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1415         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1416 
1417         // At the edge 1/6 of window. Next window should be unaffected.
1418         advanceElapsedClock(20 * MINUTE_IN_MILLIS);
1419         rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
1420         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1421         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1422 
1423         // In last 1/6 of window. Next window start time should be shifted slightly.
1424         advanceElapsedClock(6 * MINUTE_IN_MILLIS);
1425         rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
1426         assertEquals(nextWindowStartTime + (5 * MINUTE_IN_MILLIS),
1427                 rescheduledJob.getEarliestRunTime());
1428         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1429 
1430         // Very short flex duration compared to period duration.
1431         JobStatus superFlex = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1432                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 10 * MINUTE_IN_MILLIS));
1433         now = sElapsedRealtimeClock.millis();
1434         nextWindowStartTime = now + HOUR_IN_MILLIS + 50 * MINUTE_IN_MILLIS;
1435         nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1436         advanceElapsedClock(MINUTE_IN_MILLIS);
1437 
1438         // At the beginning of the window. Next window should be unaffected.
1439         rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
1440         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1441         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1442 
1443         // Halfway through window. Next window should be unaffected.
1444         advanceElapsedClock(29 * MINUTE_IN_MILLIS);
1445         rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
1446         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1447         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1448 
1449         // At the edge 1/6 of window. Next window should be unaffected.
1450         advanceElapsedClock(20 * MINUTE_IN_MILLIS);
1451         rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
1452         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1453         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1454 
1455         // In last 1/6 of window. Next window should be unaffected since the flex duration pushes
1456         // the next window start time far enough away.
1457         advanceElapsedClock(6 * MINUTE_IN_MILLIS);
1458         rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
1459         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1460         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1461     }
1462 
1463     /**
1464      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1465      * with the correct delay and deadline constraints if the periodic job with a custom flex
1466      * setting is completed and rescheduled while run in its expected running window.
1467      */
1468     @Test
testGetRescheduleJobForPeriodic_insideWindow_flex()1469     public void testGetRescheduleJobForPeriodic_insideWindow_flex() {
1470         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_flex",
1471                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
1472         // First window starts 30 minutes from now.
1473         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1474         final long now = sElapsedRealtimeClock.millis();
1475         final long nextWindowStartTime = now + HOUR_IN_MILLIS;
1476         final long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
1477 
1478         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1479         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1480         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1481 
1482         advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 10 minutes
1483 
1484         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1485         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1486         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1487 
1488         advanceElapsedClock(15 * MINUTE_IN_MILLIS); // now + 25 minutes
1489 
1490         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1491         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1492         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1493 
1494         advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 29 minutes
1495 
1496         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1497         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1498         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1499     }
1500 
1501     /**
1502      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1503      * with the correct delay and deadline constraints if the periodic job failed but then ran
1504      * successfully and was rescheduled while run in its expected running window.
1505      */
1506     @Test
testGetRescheduleJobForPeriodic_insideWindow_failedJob()1507     public void testGetRescheduleJobForPeriodic_insideWindow_failedJob() {
1508         final long now = sElapsedRealtimeClock.millis();
1509         final long nextWindowStartTime = now + HOUR_IN_MILLIS;
1510         final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1511         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_failedJob",
1512                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1513         JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
1514                 JobParameters.STOP_REASON_UNDEFINED,
1515                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1516 
1517         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1518         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1519         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1520 
1521         advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 5 minutes
1522         failedJob = mService.getRescheduleJobForFailureLocked(job,
1523                 JobParameters.STOP_REASON_UNDEFINED,
1524                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1525         advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 10 minutes
1526 
1527         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1528         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1529         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1530 
1531         advanceElapsedClock(35 * MINUTE_IN_MILLIS); // now + 45 minutes
1532         failedJob = mService.getRescheduleJobForFailureLocked(job,
1533                 JobParameters.STOP_REASON_UNDEFINED,
1534                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1535         advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes
1536 
1537         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1538         // Shifted because it's close to the end of the window.
1539         assertEquals(nextWindowStartTime + 5 * MINUTE_IN_MILLIS,
1540                 rescheduledJob.getEarliestRunTime());
1541         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1542 
1543         advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
1544         failedJob = mService.getRescheduleJobForFailureLocked(job,
1545                 JobParameters.STOP_REASON_UNDEFINED,
1546                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1547         advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes
1548 
1549         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1550         // Shifted because it's close to the end of the window.
1551         assertEquals(nextWindowStartTime + 9 * MINUTE_IN_MILLIS,
1552                 rescheduledJob.getEarliestRunTime());
1553         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1554     }
1555 
1556     /**
1557      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1558      * with the correct delay and deadline constraints if the periodic job is completed and
1559      * rescheduled when run after its expected running window.
1560      */
1561     @Test
testGetRescheduleJobForPeriodic_outsideWindow()1562     public void testGetRescheduleJobForPeriodic_outsideWindow() {
1563         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow",
1564                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1565         long now = sElapsedRealtimeClock.millis();
1566         long nextWindowStartTime = now + HOUR_IN_MILLIS;
1567         long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1568 
1569         advanceElapsedClock(HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
1570         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1571         // have consistent windows, so the new window should start as soon as the previous window
1572         // ended and end PERIOD time after the previous window ended.
1573         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1574         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1575         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1576 
1577         advanceElapsedClock(2 * HOUR_IN_MILLIS);
1578         // Say that the job ran at this point, possibly due to device idle.
1579         // The next window should be consistent (start and end at the time it would have had the job
1580         // run normally in previous windows).
1581         nextWindowStartTime += 2 * HOUR_IN_MILLIS;
1582         nextWindowEndTime += 2 * HOUR_IN_MILLIS;
1583 
1584         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1585         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1586         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1587     }
1588 
1589     /**
1590      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1591      * with the correct delay and deadline constraints if the periodic job with a custom flex
1592      * setting is completed and rescheduled when run after its expected running window.
1593      */
1594     @Test
testGetRescheduleJobForPeriodic_outsideWindow_flex()1595     public void testGetRescheduleJobForPeriodic_outsideWindow_flex() {
1596         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_flex",
1597                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
1598         // First window starts 30 minutes from now.
1599         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1600         long now = sElapsedRealtimeClock.millis();
1601         long nextWindowStartTime = now + HOUR_IN_MILLIS;
1602         long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
1603 
1604         advanceElapsedClock(31 * MINUTE_IN_MILLIS);
1605         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1606         // have consistent windows, so the new window should start as soon as the previous window
1607         // ended and end PERIOD time after the previous window ended.
1608         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1609         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1610         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1611 
1612         // 5 minutes before the start of the next window. It's too close to the next window, so the
1613         // returned job should be for the window after.
1614         advanceElapsedClock(24 * MINUTE_IN_MILLIS);
1615         nextWindowStartTime += HOUR_IN_MILLIS;
1616         nextWindowEndTime += HOUR_IN_MILLIS;
1617         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1618         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1619         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1620 
1621         advanceElapsedClock(2 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS);
1622         // Say that the job ran at this point, possibly due to device idle.
1623         // The next window should be consistent (start and end at the time it would have had the job
1624         // run normally in previous windows).
1625         nextWindowStartTime += 2 * HOUR_IN_MILLIS;
1626         nextWindowEndTime += 2 * HOUR_IN_MILLIS;
1627 
1628         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1629         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1630         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1631     }
1632 
1633     /**
1634      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1635      * with the correct delay and deadline constraints if the periodic job failed but then ran
1636      * successfully and was rescheduled when run after its expected running window.
1637      */
1638     @Test
testGetRescheduleJobForPeriodic_outsideWindow_failedJob()1639     public void testGetRescheduleJobForPeriodic_outsideWindow_failedJob() {
1640         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_failedJob",
1641                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1642         JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
1643                 JobParameters.STOP_REASON_UNDEFINED,
1644                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1645         long now = sElapsedRealtimeClock.millis();
1646         long nextWindowStartTime = now + HOUR_IN_MILLIS;
1647         long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1648 
1649         advanceElapsedClock(HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
1650         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1651         // have consistent windows, so the new window should start as soon as the previous window
1652         // ended and end PERIOD time after the previous window ended.
1653         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1654         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1655         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1656 
1657         advanceElapsedClock(2 * HOUR_IN_MILLIS);
1658         // Say that the job ran at this point, possibly due to device idle.
1659         // The next window should be consistent (start and end at the time it would have had the job
1660         // run normally in previous windows).
1661         nextWindowStartTime += 2 * HOUR_IN_MILLIS;
1662         nextWindowEndTime += 2 * HOUR_IN_MILLIS;
1663 
1664         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1665         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1666         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1667     }
1668 
1669     /**
1670      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1671      * with the correct delay and deadline constraints if the periodic job with a custom flex
1672      * setting failed but then ran successfully and was rescheduled when run after its expected
1673      * running window.
1674      */
1675     @Test
testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob()1676     public void testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob() {
1677         JobStatus job = createJobStatus(
1678                 "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob",
1679                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
1680         JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
1681                 JobParameters.STOP_REASON_UNDEFINED,
1682                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1683         // First window starts 30 minutes from now.
1684         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1685         long now = sElapsedRealtimeClock.millis();
1686         long nextWindowStartTime = now + HOUR_IN_MILLIS;
1687         long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
1688 
1689         advanceElapsedClock(31 * MINUTE_IN_MILLIS);
1690         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1691         // have consistent windows, so the new window should start as soon as the previous window
1692         // ended and end PERIOD time after the previous window ended.
1693         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1694         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1695         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1696 
1697         // 5 minutes before the start of the next window. It's too close to the next window, so the
1698         // returned job should be for the window after.
1699         advanceElapsedClock(24 * MINUTE_IN_MILLIS);
1700         nextWindowStartTime += HOUR_IN_MILLIS;
1701         nextWindowEndTime += HOUR_IN_MILLIS;
1702         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1703         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1704         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1705 
1706         advanceElapsedClock(2 * HOUR_IN_MILLIS);
1707         // Say that the job ran at this point, possibly due to device idle.
1708         // The next window should be consistent (start and end at the time it would have had the job
1709         // run normally in previous windows).
1710         nextWindowStartTime += 2 * HOUR_IN_MILLIS;
1711         nextWindowEndTime += 2 * HOUR_IN_MILLIS;
1712 
1713         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1714         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1715         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1716     }
1717 
1718     @Test
testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod()1719     public void testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod() {
1720         JobStatus job = createJobStatus(
1721                 "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod",
1722                 createJobInfo().setPeriodic(7 * DAY_IN_MILLIS, 9 * HOUR_IN_MILLIS));
1723         JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
1724                 JobParameters.STOP_REASON_UNDEFINED,
1725                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1726         // First window starts 6.625 days from now.
1727         advanceElapsedClock(6 * DAY_IN_MILLIS + 15 * HOUR_IN_MILLIS);
1728         long now = sElapsedRealtimeClock.millis();
1729         long nextWindowStartTime = now + 7 * DAY_IN_MILLIS;
1730         long nextWindowEndTime = nextWindowStartTime + 9 * HOUR_IN_MILLIS;
1731 
1732         advanceElapsedClock(6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
1733         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1734         // have consistent windows, so the new window should start as soon as the previous window
1735         // ended and end PERIOD time after the previous window ended.
1736         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1737         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1738         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1739 
1740         advanceElapsedClock(DAY_IN_MILLIS);
1741         // Say the job ran a day late. Since the period is massive compared to the flex, JSS should
1742         // put the rescheduled job in the original window.
1743         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1744         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1745         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1746 
1747         // 1 day before the start of the next window. Given the large period, respect the original
1748         // next window.
1749         advanceElapsedClock(nextWindowStartTime - sElapsedRealtimeClock.millis() - DAY_IN_MILLIS);
1750         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1751         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1752         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1753 
1754         // 1 hour before the start of the next window. It's too close to the next window, so the
1755         // returned job should be for the window after.
1756         long oneHourBeforeNextWindow =
1757                 nextWindowStartTime - sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS;
1758         long fiveMinsBeforeNextWindow =
1759                 nextWindowStartTime - sElapsedRealtimeClock.millis() - 5 * MINUTE_IN_MILLIS;
1760         advanceElapsedClock(oneHourBeforeNextWindow);
1761         nextWindowStartTime += 7 * DAY_IN_MILLIS;
1762         nextWindowEndTime += 7 * DAY_IN_MILLIS;
1763         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1764         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1765         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1766 
1767         // 5 minutes before the start of the next window. It's too close to the next window, so the
1768         // returned job should be for the window after.
1769         advanceElapsedClock(fiveMinsBeforeNextWindow);
1770         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1771         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1772         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1773 
1774         advanceElapsedClock(14 * DAY_IN_MILLIS);
1775         // Say that the job ran at this point, probably because the phone was off the entire time.
1776         // The next window should be consistent (start and end at the time it would have had the job
1777         // run normally in previous windows).
1778         nextWindowStartTime += 14 * DAY_IN_MILLIS;
1779         nextWindowEndTime += 14 * DAY_IN_MILLIS;
1780 
1781         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1782         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1783         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1784 
1785         // Test original job again but with a huge delay from the original execution window
1786 
1787         // 1 day before the start of the next window. Given the large period, respect the original
1788         // next window.
1789         advanceElapsedClock(nextWindowStartTime - sElapsedRealtimeClock.millis() - DAY_IN_MILLIS);
1790         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1791         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1792         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1793 
1794         // 1 hour before the start of the next window. It's too close to the next window, so the
1795         // returned job should be for the window after.
1796         oneHourBeforeNextWindow =
1797                 nextWindowStartTime - sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS;
1798         fiveMinsBeforeNextWindow =
1799                 nextWindowStartTime - sElapsedRealtimeClock.millis() - 5 * MINUTE_IN_MILLIS;
1800         advanceElapsedClock(oneHourBeforeNextWindow);
1801         nextWindowStartTime += 7 * DAY_IN_MILLIS;
1802         nextWindowEndTime += 7 * DAY_IN_MILLIS;
1803         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1804         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1805         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1806 
1807         // 5 minutes before the start of the next window. It's too close to the next window, so the
1808         // returned job should be for the window after.
1809         advanceElapsedClock(fiveMinsBeforeNextWindow);
1810         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1811         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1812         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1813     }
1814 
1815     @Test
testBatteryStateTrackerRegistersForImportantIntents()1816     public void testBatteryStateTrackerRegistersForImportantIntents() {
1817         verify(mContext).registerReceiver(any(), ArgumentMatchers.argThat(filter -> true
1818                 && filter.hasAction(BatteryManager.ACTION_CHARGING)
1819                 && filter.hasAction(BatteryManager.ACTION_DISCHARGING)
1820                 && filter.hasAction(Intent.ACTION_BATTERY_LEVEL_CHANGED)
1821                 && filter.hasAction(Intent.ACTION_BATTERY_LOW)
1822                 && filter.hasAction(Intent.ACTION_BATTERY_OKAY)
1823                 && filter.hasAction(Intent.ACTION_POWER_CONNECTED)
1824                 && filter.hasAction(Intent.ACTION_POWER_DISCONNECTED)));
1825     }
1826 
1827     @Test
testIsCharging_standardChargingIntent()1828     public void testIsCharging_standardChargingIntent() {
1829         JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
1830 
1831         Intent chargingIntent = new Intent(BatteryManager.ACTION_CHARGING);
1832         Intent dischargingIntent = new Intent(BatteryManager.ACTION_DISCHARGING);
1833         tracker.onReceive(mContext, dischargingIntent);
1834         assertFalse(tracker.isCharging());
1835         assertFalse(mService.isBatteryCharging());
1836 
1837         tracker.onReceive(mContext, chargingIntent);
1838         assertTrue(tracker.isCharging());
1839         assertTrue(mService.isBatteryCharging());
1840 
1841         tracker.onReceive(mContext, dischargingIntent);
1842         assertFalse(tracker.isCharging());
1843         assertFalse(mService.isBatteryCharging());
1844     }
1845 
1846     @Test
testIsCharging_adaptiveCharging_batteryTooLow()1847     public void testIsCharging_adaptiveCharging_batteryTooLow() {
1848         JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
1849 
1850         tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
1851         assertFalse(tracker.isCharging());
1852         assertFalse(mService.isBatteryCharging());
1853 
1854         setBatteryLevel(15);
1855         setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
1856         assertFalse(tracker.isCharging());
1857         assertFalse(mService.isBatteryCharging());
1858 
1859         tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
1860 
1861         setBatteryLevel(70);
1862         assertTrue(tracker.isCharging());
1863         assertTrue(mService.isBatteryCharging());
1864     }
1865 
1866     @Test
testIsCharging_adaptiveCharging_chargeBelowThreshold()1867     public void testIsCharging_adaptiveCharging_chargeBelowThreshold() {
1868         JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
1869 
1870         setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
1871         tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
1872         setBatteryLevel(5);
1873 
1874         tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_CHARGING));
1875         assertTrue(tracker.isCharging());
1876         assertTrue(mService.isBatteryCharging());
1877 
1878         for (int level = 5; level < 80; ++level) {
1879             setBatteryLevel(level);
1880             assertTrue(tracker.isCharging());
1881             assertTrue(mService.isBatteryCharging());
1882         }
1883     }
1884 
1885     @Test
testIsCharging_adaptiveCharging_dischargeAboveThreshold()1886     public void testIsCharging_adaptiveCharging_dischargeAboveThreshold() {
1887         JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
1888 
1889         setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
1890         tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_CONNECTED));
1891         setBatteryLevel(80);
1892 
1893         tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
1894         assertTrue(tracker.isCharging());
1895         assertTrue(mService.isBatteryCharging());
1896 
1897         for (int level = 80; level > 60; --level) {
1898             setBatteryLevel(level);
1899             assertEquals(level >= 70, tracker.isCharging());
1900             assertEquals(level >= 70, mService.isBatteryCharging());
1901         }
1902     }
1903 
1904     @Test
testIsCharging_adaptiveCharging_notPluggedIn()1905     public void testIsCharging_adaptiveCharging_notPluggedIn() {
1906         JobSchedulerService.BatteryStateTracker tracker = mService.mBatteryStateTracker;
1907 
1908         tracker.onReceive(mContext, new Intent(Intent.ACTION_POWER_DISCONNECTED));
1909         tracker.onReceive(mContext, new Intent(BatteryManager.ACTION_DISCHARGING));
1910         assertFalse(tracker.isCharging());
1911         assertFalse(mService.isBatteryCharging());
1912 
1913         setBatteryLevel(15);
1914         setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
1915         assertFalse(tracker.isCharging());
1916         assertFalse(mService.isBatteryCharging());
1917 
1918         setBatteryLevel(50);
1919         setChargingPolicy(BatteryManager.CHARGING_POLICY_ADAPTIVE_AC);
1920         assertFalse(tracker.isCharging());
1921         assertFalse(mService.isBatteryCharging());
1922 
1923         setBatteryLevel(70);
1924         assertFalse(tracker.isCharging());
1925         assertFalse(mService.isBatteryCharging());
1926 
1927         setBatteryLevel(95);
1928         assertFalse(tracker.isCharging());
1929         assertFalse(mService.isBatteryCharging());
1930 
1931         setBatteryLevel(100);
1932         assertFalse(tracker.isCharging());
1933         assertFalse(mService.isBatteryCharging());
1934     }
1935 
1936     /** Tests that rare job batching works as expected. */
1937     @Test
testConnectivityJobBatching()1938     public void testConnectivityJobBatching() {
1939         mSetFlagsRule.enableFlags(FLAG_BATCH_CONNECTIVITY_JOBS_PER_NETWORK);
1940 
1941         spyOn(mService);
1942         doReturn(false).when(mService).evaluateControllerStatesLocked(any());
1943         doNothing().when(mService).noteJobsPending(any());
1944         doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
1945         ConnectivityController connectivityController = mService.getConnectivityController();
1946         spyOn(connectivityController);
1947         advanceElapsedClock(24 * HOUR_IN_MILLIS);
1948 
1949         JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
1950                 mService.new MaybeReadyJobQueueFunctor();
1951         mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD.clear();
1952         mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
1953                 .put(NetworkCapabilities.TRANSPORT_CELLULAR, 5);
1954         mService.mConstants.CONN_TRANSPORT_BATCH_THRESHOLD
1955                 .put(NetworkCapabilities.TRANSPORT_WIFI, 2);
1956         mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
1957 
1958         final Network network = mock(Network.class);
1959 
1960         // Not enough connectivity jobs to run.
1961         mService.getPendingJobQueue().clear();
1962         maybeQueueFunctor.reset();
1963         NetworkCapabilities capabilities = new NetworkCapabilities.Builder()
1964                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
1965                 .build();
1966         doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
1967         doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
1968         for (int i = 0; i < 4; ++i) {
1969             JobStatus job = createJobStatus(
1970                     "testConnectivityJobBatching",
1971                     createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
1972             job.setStandbyBucket(ACTIVE_INDEX);
1973             job.network = network;
1974 
1975             maybeQueueFunctor.accept(job);
1976             assertNull(maybeQueueFunctor.mBatches.get(null));
1977             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
1978             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
1979             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
1980         }
1981         maybeQueueFunctor.postProcessLocked();
1982         assertEquals(0, mService.getPendingJobQueue().size());
1983 
1984         // Not enough connectivity jobs to run, but the network is already active
1985         mService.getPendingJobQueue().clear();
1986         maybeQueueFunctor.reset();
1987         doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
1988         doReturn(true).when(connectivityController).isNetworkInStateForJobRunLocked(any());
1989         for (int i = 0; i < 4; ++i) {
1990             JobStatus job = createJobStatus(
1991                     "testConnectivityJobBatching",
1992                     createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
1993             job.setStandbyBucket(ACTIVE_INDEX);
1994             job.network = network;
1995 
1996             maybeQueueFunctor.accept(job);
1997             assertNull(maybeQueueFunctor.mBatches.get(null));
1998             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
1999             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2000             assertEquals(0, job.getFirstForceBatchedTimeElapsed());
2001         }
2002         maybeQueueFunctor.postProcessLocked();
2003         assertEquals(4, mService.getPendingJobQueue().size());
2004 
2005         // Enough connectivity jobs to run.
2006         mService.getPendingJobQueue().clear();
2007         maybeQueueFunctor.reset();
2008         capabilities = new NetworkCapabilities.Builder()
2009                 .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
2010                 .build();
2011         doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
2012         doReturn(false).when(connectivityController).isNetworkInStateForJobRunLocked(any());
2013         for (int i = 0; i < 3; ++i) {
2014             JobStatus job = createJobStatus(
2015                     "testConnectivityJobBatching",
2016                     createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
2017             job.setStandbyBucket(ACTIVE_INDEX);
2018             job.network = network;
2019 
2020             maybeQueueFunctor.accept(job);
2021             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
2022             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2023             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2024         }
2025         maybeQueueFunctor.postProcessLocked();
2026         assertEquals(3, mService.getPendingJobQueue().size());
2027 
2028         // Not enough connectivity jobs to run, but a non-batched job saves the day.
2029         mService.getPendingJobQueue().clear();
2030         maybeQueueFunctor.reset();
2031         JobStatus runningJob = createJobStatus(
2032                 "testConnectivityJobBatching",
2033                 createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
2034         runningJob.network = network;
2035         doReturn(true).when(mService).isCurrentlyRunningLocked(runningJob);
2036         capabilities = new NetworkCapabilities.Builder()
2037                 .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
2038                 .build();
2039         doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
2040         for (int i = 0; i < 3; ++i) {
2041             JobStatus job = createJobStatus(
2042                     "testConnectivityJobBatching",
2043                     createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
2044             job.setStandbyBucket(ACTIVE_INDEX);
2045             job.network = network;
2046 
2047             maybeQueueFunctor.accept(job);
2048             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
2049             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2050             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2051         }
2052         maybeQueueFunctor.accept(runningJob);
2053         maybeQueueFunctor.postProcessLocked();
2054         assertEquals(3, mService.getPendingJobQueue().size());
2055 
2056         // Not enough connectivity jobs to run, but an old connectivity job saves the day.
2057         mService.getPendingJobQueue().clear();
2058         maybeQueueFunctor.reset();
2059         JobStatus oldConnJob = createJobStatus("testConnectivityJobBatching",
2060                 createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
2061         oldConnJob.network = network;
2062         final long oldBatchTime = sElapsedRealtimeClock.millis()
2063                 - 2 * mService.mConstants.CONN_MAX_CONNECTIVITY_JOB_BATCH_DELAY_MS;
2064         oldConnJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
2065         for (int i = 0; i < 2; ++i) {
2066             JobStatus job = createJobStatus(
2067                     "testConnectivityJobBatching",
2068                     createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
2069             job.setStandbyBucket(ACTIVE_INDEX);
2070             job.network = network;
2071 
2072             maybeQueueFunctor.accept(job);
2073             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(network).size());
2074             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2075             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2076         }
2077         maybeQueueFunctor.accept(oldConnJob);
2078         assertEquals(oldBatchTime, oldConnJob.getFirstForceBatchedTimeElapsed());
2079         maybeQueueFunctor.postProcessLocked();
2080         assertEquals(3, mService.getPendingJobQueue().size());
2081 
2082         // Transport type doesn't have a set threshold. One job should be the default threshold.
2083         mService.getPendingJobQueue().clear();
2084         maybeQueueFunctor.reset();
2085         capabilities = new NetworkCapabilities.Builder()
2086                 .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
2087                 .build();
2088         doReturn(capabilities).when(connectivityController).getNetworkCapabilities(network);
2089         JobStatus job = createJobStatus(
2090                 "testConnectivityJobBatching",
2091                 createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
2092         job.setStandbyBucket(ACTIVE_INDEX);
2093         job.network = network;
2094         maybeQueueFunctor.accept(job);
2095         assertEquals(1, maybeQueueFunctor.mBatches.get(network).size());
2096         assertEquals(1, maybeQueueFunctor.runnableJobs.size());
2097         assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2098         maybeQueueFunctor.postProcessLocked();
2099         assertEquals(1, mService.getPendingJobQueue().size());
2100     }
2101 
2102     /** Tests that active job batching works as expected. */
2103     @Test
testActiveJobBatching_activeBatchingEnabled()2104     public void testActiveJobBatching_activeBatchingEnabled() {
2105         mSetFlagsRule.enableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
2106 
2107         spyOn(mService);
2108         doReturn(false).when(mService).evaluateControllerStatesLocked(any());
2109         doNothing().when(mService).noteJobsPending(any());
2110         doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
2111         advanceElapsedClock(24 * HOUR_IN_MILLIS);
2112 
2113         JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
2114                 mService.new MaybeReadyJobQueueFunctor();
2115         mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT = 5;
2116         mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
2117 
2118         // Not enough ACTIVE jobs to run.
2119         mService.getPendingJobQueue().clear();
2120         maybeQueueFunctor.reset();
2121         for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
2122             JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
2123             job.setStandbyBucket(ACTIVE_INDEX);
2124 
2125             maybeQueueFunctor.accept(job);
2126             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
2127             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2128             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2129         }
2130         maybeQueueFunctor.postProcessLocked();
2131         assertEquals(0, mService.getPendingJobQueue().size());
2132 
2133         // Enough ACTIVE jobs to run.
2134         mService.getPendingJobQueue().clear();
2135         maybeQueueFunctor.reset();
2136         for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT; ++i) {
2137             JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
2138             job.setStandbyBucket(ACTIVE_INDEX);
2139 
2140             maybeQueueFunctor.accept(job);
2141             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
2142             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2143             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2144         }
2145         maybeQueueFunctor.postProcessLocked();
2146         assertEquals(5, mService.getPendingJobQueue().size());
2147 
2148         // Not enough ACTIVE jobs to run, but a non-batched job saves the day.
2149         mService.getPendingJobQueue().clear();
2150         maybeQueueFunctor.reset();
2151         JobStatus expeditedJob = createJobStatus("testActiveJobBatching",
2152                 createJobInfo().setExpedited(true));
2153         spyOn(expeditedJob);
2154         when(expeditedJob.shouldTreatAsExpeditedJob()).thenReturn(true);
2155         expeditedJob.setStandbyBucket(RARE_INDEX);
2156         for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
2157             JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
2158             job.setStandbyBucket(ACTIVE_INDEX);
2159 
2160             maybeQueueFunctor.accept(job);
2161             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
2162             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2163             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2164         }
2165         maybeQueueFunctor.accept(expeditedJob);
2166         maybeQueueFunctor.postProcessLocked();
2167         assertEquals(3, mService.getPendingJobQueue().size());
2168 
2169         // Not enough ACTIVE jobs to run, but an old ACTIVE job saves the day.
2170         mService.getPendingJobQueue().clear();
2171         maybeQueueFunctor.reset();
2172         JobStatus oldActiveJob = createJobStatus("testActiveJobBatching", createJobInfo());
2173         oldActiveJob.setStandbyBucket(ACTIVE_INDEX);
2174         final long oldBatchTime = sElapsedRealtimeClock.millis()
2175                 - 2 * mService.mConstants.MAX_CPU_ONLY_JOB_BATCH_DELAY_MS;
2176         oldActiveJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
2177         for (int i = 0; i < mService.mConstants.MIN_READY_CPU_ONLY_JOBS_COUNT / 2; ++i) {
2178             JobStatus job = createJobStatus("testActiveJobBatching", createJobInfo());
2179             job.setStandbyBucket(ACTIVE_INDEX);
2180 
2181             maybeQueueFunctor.accept(job);
2182             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
2183             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2184             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2185         }
2186         maybeQueueFunctor.accept(oldActiveJob);
2187         assertEquals(oldBatchTime, oldActiveJob.getFirstForceBatchedTimeElapsed());
2188         maybeQueueFunctor.postProcessLocked();
2189         assertEquals(3, mService.getPendingJobQueue().size());
2190     }
2191 
2192     /** Tests that rare job batching works as expected. */
2193     @Test
testRareJobBatching()2194     public void testRareJobBatching() {
2195         spyOn(mService);
2196         doReturn(false).when(mService).evaluateControllerStatesLocked(any());
2197         doNothing().when(mService).noteJobsPending(any());
2198         doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
2199         advanceElapsedClock(24 * HOUR_IN_MILLIS);
2200 
2201         JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
2202                 mService.new MaybeReadyJobQueueFunctor();
2203         mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
2204         mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
2205 
2206         // Not enough RARE jobs to run.
2207         mService.getPendingJobQueue().clear();
2208         maybeQueueFunctor.reset();
2209         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
2210             JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
2211             job.setStandbyBucket(RARE_INDEX);
2212 
2213             maybeQueueFunctor.accept(job);
2214             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
2215             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2216             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2217         }
2218         maybeQueueFunctor.postProcessLocked();
2219         assertEquals(0, mService.getPendingJobQueue().size());
2220 
2221         // Enough RARE jobs to run.
2222         mService.getPendingJobQueue().clear();
2223         maybeQueueFunctor.reset();
2224         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) {
2225             JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
2226             job.setStandbyBucket(RARE_INDEX);
2227 
2228             maybeQueueFunctor.accept(job);
2229             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
2230             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2231             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2232         }
2233         maybeQueueFunctor.postProcessLocked();
2234         assertEquals(5, mService.getPendingJobQueue().size());
2235 
2236         // Not enough RARE jobs to run, but a non-batched job saves the day.
2237         mSetFlagsRule.disableFlags(FLAG_BATCH_ACTIVE_BUCKET_JOBS);
2238         mService.getPendingJobQueue().clear();
2239         maybeQueueFunctor.reset();
2240         JobStatus activeJob = createJobStatus("testRareJobBatching", createJobInfo());
2241         activeJob.setStandbyBucket(ACTIVE_INDEX);
2242         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
2243             JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
2244             job.setStandbyBucket(RARE_INDEX);
2245 
2246             maybeQueueFunctor.accept(job);
2247             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
2248             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2249             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2250         }
2251         maybeQueueFunctor.accept(activeJob);
2252         maybeQueueFunctor.postProcessLocked();
2253         assertEquals(3, mService.getPendingJobQueue().size());
2254 
2255         // Not enough RARE jobs to run, but an old RARE job saves the day.
2256         mService.getPendingJobQueue().clear();
2257         maybeQueueFunctor.reset();
2258         JobStatus oldRareJob = createJobStatus("testRareJobBatching", createJobInfo());
2259         oldRareJob.setStandbyBucket(RARE_INDEX);
2260         final long oldBatchTime = sElapsedRealtimeClock.millis()
2261                 - 2 * mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
2262         oldRareJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
2263         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
2264             JobStatus job = createJobStatus("testRareJobBatching", createJobInfo());
2265             job.setStandbyBucket(RARE_INDEX);
2266 
2267             maybeQueueFunctor.accept(job);
2268             assertEquals(i + 1, maybeQueueFunctor.mBatches.get(null).size());
2269             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
2270             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
2271         }
2272         maybeQueueFunctor.accept(oldRareJob);
2273         assertEquals(oldBatchTime, oldRareJob.getFirstForceBatchedTimeElapsed());
2274         maybeQueueFunctor.postProcessLocked();
2275         assertEquals(3, mService.getPendingJobQueue().size());
2276     }
2277 
2278     /** Tests that jobs scheduled by the app itself are counted towards scheduling limits. */
2279     @Test
testScheduleLimiting_RegularSchedule_Blocked()2280     public void testScheduleLimiting_RegularSchedule_Blocked() {
2281         mService.mConstants.ENABLE_API_QUOTAS = true;
2282         mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
2283         mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
2284         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
2285         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
2286         mService.updateQuotaTracker();
2287         mService.resetScheduleQuota();
2288 
2289         final JobInfo job = createJobInfo().setPersisted(true).build();
2290         for (int i = 0; i < 500; ++i) {
2291             final int expected =
2292                     i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
2293             assertEquals("Got unexpected result for schedule #" + (i + 1),
2294                     expected,
2295                     mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", ""));
2296         }
2297     }
2298 
2299     /**
2300      * Tests that jobs scheduled by the app itself succeed even if the app is above the scheduling
2301      * limit.
2302      */
2303     @Test
2304     public void testScheduleLimiting_RegularSchedule_Allowed() {
2305         mService.mConstants.ENABLE_API_QUOTAS = true;
2306         mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
2307         mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
2308         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
2309         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
2310         mService.updateQuotaTracker();
2311         mService.resetScheduleQuota();
2312 
2313         final JobInfo job = createJobInfo().setPersisted(true).build();
2314         for (int i = 0; i < 500; ++i) {
2315             assertEquals("Got unexpected result for schedule #" + (i + 1),
2316                     JobScheduler.RESULT_SUCCESS,
2317                     mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", ""));
2318         }
2319     }
2320 
2321     /**
2322      * Tests that jobs scheduled through a proxy (eg. system server) don't count towards scheduling
2323      * limits.
2324      */
2325     @Test
2326     public void testScheduleLimiting_Proxy() {
2327         mService.mConstants.ENABLE_API_QUOTAS = true;
2328         mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
2329         mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
2330         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
2331         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
2332         mService.updateQuotaTracker();
2333         mService.resetScheduleQuota();
2334 
2335         final JobInfo job = createJobInfo().setPersisted(true).build();
2336         for (int i = 0; i < 500; ++i) {
2337             assertEquals("Got unexpected result for schedule #" + (i + 1),
2338                     JobScheduler.RESULT_SUCCESS,
2339                     mService.scheduleAsPackage(job, null, TEST_UID, "proxied.package", 0, "JSSTest",
2340                             ""));
2341         }
2342     }
2343 
2344     /**
2345      * Tests that jobs scheduled by an app for itself as if through a proxy are counted towards
2346      * scheduling limits.
2347      */
2348     @Test
2349     public void testScheduleLimiting_SelfProxy() {
2350         mService.mConstants.ENABLE_API_QUOTAS = true;
2351         mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
2352         mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
2353         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
2354         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
2355         mService.updateQuotaTracker();
2356         mService.resetScheduleQuota();
2357 
2358         final JobInfo job = createJobInfo().setPersisted(true).build();
2359         for (int i = 0; i < 500; ++i) {
2360             final int expected =
2361                     i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
2362             assertEquals("Got unexpected result for schedule #" + (i + 1),
2363                     expected,
2364                     mService.scheduleAsPackage(job, null, TEST_UID,
2365                             job.getService().getPackageName(),
2366                             0, "JSSTest", ""));
2367         }
2368     }
2369 
2370     /**
2371      * Tests that the number of persisted JobWorkItems is capped.
2372      */
2373     @Test
2374     public void testScheduleLimiting_JobWorkItems_Nonpersisted() {
2375         mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500;
2376         mService.mConstants.ENABLE_API_QUOTAS = false;
2377         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
2378         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
2379         mService.updateQuotaTracker();
2380         mService.resetScheduleQuota();
2381 
2382         final JobInfo job = createJobInfo().setPersisted(false).build();
2383         final JobWorkItem item = new JobWorkItem.Builder().build();
2384         for (int i = 0; i < 1000; ++i) {
2385             assertEquals("Got unexpected result for schedule #" + (i + 1),
2386                     JobScheduler.RESULT_SUCCESS,
2387                     mService.scheduleAsPackage(job, item, TEST_UID,
2388                             job.getService().getPackageName(),
2389                             0, "JSSTest", ""));
2390         }
2391     }
2392 
2393     /**
2394      * Tests that the number of persisted JobWorkItems is capped.
2395      */
2396     @Test
2397     public void testScheduleLimiting_JobWorkItems_Persisted() {
2398         mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500;
2399         mService.mConstants.ENABLE_API_QUOTAS = false;
2400         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
2401         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
2402         mService.updateQuotaTracker();
2403         mService.resetScheduleQuota();
2404 
2405         final JobInfo job = createJobInfo().setPersisted(true).build();
2406         final JobWorkItem item = new JobWorkItem.Builder().build();
2407         for (int i = 0; i < 500; ++i) {
2408             assertEquals("Got unexpected result for schedule #" + (i + 1),
2409                     JobScheduler.RESULT_SUCCESS,
2410                     mService.scheduleAsPackage(job, item, TEST_UID,
2411                             job.getService().getPackageName(),
2412                             0, "JSSTest", ""));
2413         }
2414         try {
2415             mService.scheduleAsPackage(job, item, TEST_UID, job.getService().getPackageName(),
2416                     0, "JSSTest", "");
2417             fail("Added more items than allowed");
2418         } catch (IllegalStateException expected) {
2419             // Success
2420         }
2421     }
2422 
2423     /** Tests that jobs are removed from the pending list if the user stops the app. */
2424     @Test
2425     @RequiresFlagsDisabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API)
2426     public void testUserStopRemovesPending() {
2427         spyOn(mService);
2428 
2429         JobStatus job1a = createJobStatus("testUserStopRemovesPending",
2430                 createJobInfo(1), 1, "pkg1");
2431         JobStatus job1b = createJobStatus("testUserStopRemovesPending",
2432                 createJobInfo(2), 1, "pkg1");
2433         JobStatus job2a = createJobStatus("testUserStopRemovesPending",
2434                 createJobInfo(1), 2, "pkg2");
2435         JobStatus job2b = createJobStatus("testUserStopRemovesPending",
2436                 createJobInfo(2), 2, "pkg2");
2437         doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0);
2438         doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1);
2439         doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0);
2440 
2441         mService.getPendingJobQueue().clear();
2442         mService.getPendingJobQueue().add(job1a);
2443         mService.getPendingJobQueue().add(job1b);
2444         mService.getPendingJobQueue().add(job2a);
2445         mService.getPendingJobQueue().add(job2b);
2446         mService.getJobStore().add(job1a);
2447         mService.getJobStore().add(job1b);
2448         mService.getJobStore().add(job2a);
2449         mService.getJobStore().add(job2b);
2450 
2451         mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test");
2452         assertEquals(4, mService.getPendingJobQueue().size());
2453         assertTrue(mService.getPendingJobQueue().contains(job1a));
2454         assertTrue(mService.getPendingJobQueue().contains(job1b));
2455         assertTrue(mService.getPendingJobQueue().contains(job2a));
2456         assertTrue(mService.getPendingJobQueue().contains(job2b));
2457 
2458         mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test");
2459         assertEquals(2, mService.getPendingJobQueue().size());
2460         assertFalse(mService.getPendingJobQueue().contains(job1a));
2461         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1a));
2462         assertFalse(mService.getPendingJobQueue().contains(job1b));
2463         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1b));
2464         assertTrue(mService.getPendingJobQueue().contains(job2a));
2465         assertTrue(mService.getPendingJobQueue().contains(job2b));
2466 
2467         mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test");
2468         assertEquals(0, mService.getPendingJobQueue().size());
2469         assertFalse(mService.getPendingJobQueue().contains(job1a));
2470         assertFalse(mService.getPendingJobQueue().contains(job1b));
2471         assertFalse(mService.getPendingJobQueue().contains(job2a));
2472         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2a));
2473         assertFalse(mService.getPendingJobQueue().contains(job2b));
2474         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
2475     }
2476 
2477     /** Tests that jobs are removed from the pending list if the user stops the app. */
2478     @Test
2479     @RequiresFlagsEnabled(android.app.job.Flags.FLAG_GET_PENDING_JOB_REASONS_API)
2480     public void testUserStopRemovesPending_withPendingJobReasonsApi() {
2481         spyOn(mService);
2482 
2483         JobStatus job1a = createJobStatus("testUserStopRemovesPending",
2484                 createJobInfo(1), 1, "pkg1");
2485         JobStatus job1b = createJobStatus("testUserStopRemovesPending",
2486                 createJobInfo(2), 1, "pkg1");
2487         JobStatus job2a = createJobStatus("testUserStopRemovesPending",
2488                 createJobInfo(1), 2, "pkg2");
2489         JobStatus job2b = createJobStatus("testUserStopRemovesPending",
2490                 createJobInfo(2), 2, "pkg2");
2491         doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0);
2492         doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1);
2493         doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0);
2494 
2495         mService.getPendingJobQueue().clear();
2496         mService.getPendingJobQueue().add(job1a);
2497         mService.getPendingJobQueue().add(job1b);
2498         mService.getPendingJobQueue().add(job2a);
2499         mService.getPendingJobQueue().add(job2b);
2500         mService.getJobStore().add(job1a);
2501         mService.getJobStore().add(job1b);
2502         mService.getJobStore().add(job2a);
2503         mService.getJobStore().add(job2b);
2504 
2505         mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test");
2506         assertEquals(4, mService.getPendingJobQueue().size());
2507         assertTrue(mService.getPendingJobQueue().contains(job1a));
2508         assertTrue(mService.getPendingJobQueue().contains(job1b));
2509         assertTrue(mService.getPendingJobQueue().contains(job2a));
2510         assertTrue(mService.getPendingJobQueue().contains(job2b));
2511 
2512         mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test");
2513         assertEquals(2, mService.getPendingJobQueue().size());
2514         assertFalse(mService.getPendingJobQueue().contains(job1a));
2515         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1a)[0]);
2516         assertFalse(mService.getPendingJobQueue().contains(job1b));
2517         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job1b)[0]);
2518         assertTrue(mService.getPendingJobQueue().contains(job2a));
2519         assertTrue(mService.getPendingJobQueue().contains(job2b));
2520 
2521         mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test");
2522         assertEquals(0, mService.getPendingJobQueue().size());
2523         assertFalse(mService.getPendingJobQueue().contains(job1a));
2524         assertFalse(mService.getPendingJobQueue().contains(job1b));
2525         assertFalse(mService.getPendingJobQueue().contains(job2a));
2526         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2a)[0]);
2527         assertFalse(mService.getPendingJobQueue().contains(job2b));
2528         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReasons(job2b)[0]);
2529     }
2530 
2531     /**
2532      * Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with single {@link
2533      * JobRestriction} registered.
2534      */
2535     @Test
2536     public void testCheckIfRestrictedSingleRestriction() {
2537         int bias = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE;
2538         JobStatus fgsJob =
2539                 createJobStatus(
2540                         "testCheckIfRestrictedSingleRestriction", createJobInfo(1).setBias(bias));
2541         ThermalStatusRestriction mockThermalStatusRestriction =
2542                 mock(ThermalStatusRestriction.class);
2543         mService.mJobRestrictions.clear();
2544         mService.mJobRestrictions.add(mockThermalStatusRestriction);
2545         when(mockThermalStatusRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
2546 
2547         synchronized (mService.mLock) {
2548             assertEquals(mService.checkIfRestricted(fgsJob), mockThermalStatusRestriction);
2549         }
2550 
2551         when(mockThermalStatusRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
2552         synchronized (mService.mLock) {
2553             assertNull(mService.checkIfRestricted(fgsJob));
2554         }
2555     }
2556 
2557     /**
2558      * Unit tests {@link JobSchedulerService#checkIfRestricted(JobStatus)} with multiple {@link
2559      * JobRestriction} registered.
2560      */
2561     @Test
2562     public void testCheckIfRestrictedMultipleRestrictions() {
2563         int bias = JobInfo.BIAS_BOUND_FOREGROUND_SERVICE;
2564         JobStatus fgsJob =
2565                 createJobStatus(
2566                         "testGetMinJobExecutionGuaranteeMs", createJobInfo(1).setBias(bias));
2567         JobRestriction mock1JobRestriction = mock(JobRestriction.class);
2568         JobRestriction mock2JobRestriction = mock(JobRestriction.class);
2569         mService.mJobRestrictions.clear();
2570         mService.mJobRestrictions.add(mock1JobRestriction);
2571         mService.mJobRestrictions.add(mock2JobRestriction);
2572 
2573         // Jobs will be restricted if any one of the registered {@link JobRestriction}
2574         // reports true.
2575         when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
2576         when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
2577         synchronized (mService.mLock) {
2578             assertEquals(mService.checkIfRestricted(fgsJob), mock1JobRestriction);
2579         }
2580 
2581         when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
2582         when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
2583         synchronized (mService.mLock) {
2584             assertEquals(mService.checkIfRestricted(fgsJob), mock2JobRestriction);
2585         }
2586 
2587         when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
2588         when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(false);
2589         synchronized (mService.mLock) {
2590             assertNull(mService.checkIfRestricted(fgsJob));
2591         }
2592 
2593         when(mock1JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
2594         when(mock2JobRestriction.isJobRestricted(fgsJob, bias)).thenReturn(true);
2595         synchronized (mService.mLock) {
2596             assertNotEquals(mService.checkIfRestricted(fgsJob), mock1JobRestriction);
2597         }
2598     }
2599 
2600     /**
2601      * Jobs with foreground service and top app biases must not be restricted when the flag is
2602      * disabled.
2603      */
2604     @Test
2605     @RequiresFlagsDisabled(FLAG_THERMAL_RESTRICTIONS_TO_FGS_JOBS)
2606     public void testCheckIfRestricted_highJobBias_flagThermalRestrictionsToFgsJobsDisabled() {
2607         JobStatus fgsJob =
2608                 createJobStatus(
2609                         "testCheckIfRestrictedJobBiasFgs",
2610                         createJobInfo(1).setBias(JobInfo.BIAS_FOREGROUND_SERVICE));
2611         JobStatus topAppJob =
2612                 createJobStatus(
2613                         "testCheckIfRestrictedJobBiasTopApp",
2614                         createJobInfo(2).setBias(JobInfo.BIAS_TOP_APP));
2615 
2616         synchronized (mService.mLock) {
2617             assertNull(mService.checkIfRestricted(fgsJob));
2618             assertNull(mService.checkIfRestricted(topAppJob));
2619         }
2620     }
2621 
2622     /** Jobs with top app biases must not be restricted. */
2623     @Test
2624     public void testCheckIfRestricted_highJobBias() {
2625         JobStatus topAppJob = createJobStatus(
2626                 "testCheckIfRestrictedJobBiasTopApp",
2627                 createJobInfo(1).setBias(JobInfo.BIAS_TOP_APP));
2628         synchronized (mService.mLock) {
2629             assertNull(mService.checkIfRestricted(topAppJob));
2630         }
2631     }
2632 
2633     @RequiresFlagsEnabled(FLAG_CREATE_WORK_CHAIN_BY_DEFAULT)
2634     @Test
2635     public void testDeriveWorkSource_flagCreateWorkChainByDefaultEnabled() {
2636         final WorkSource workSource = mService.deriveWorkSource(TEST_UID, "com.test.pkg");
2637         assertEquals(TEST_UID, workSource.getAttributionUid());
2638 
2639         assertEquals(1, workSource.getWorkChains().size());
2640         final WorkChain workChain = workSource.getWorkChains().get(0);
2641         final int[] expectedUids = {TEST_UID, Process.SYSTEM_UID};
2642         assertArrayEquals(expectedUids, workChain.getUids());
2643     }
2644 
2645     @RequiresFlagsDisabled(FLAG_CREATE_WORK_CHAIN_BY_DEFAULT)
2646     @Test
2647     public void testDeriveWorkSource_flagCreateWorkChainByDefaultDisabled() {
2648         final ContentResolver contentResolver = mock(ContentResolver.class);
2649         doReturn(contentResolver).when(mContext).getContentResolver();
2650         final IContentProvider iContentProvider = mock(IContentProvider.class);
2651         doReturn(iContentProvider).when(contentResolver).acquireProvider(anyString());
2652 
2653         final WorkSource workSource = mService.deriveWorkSource(TEST_UID, "com.test.pkg");
2654         assertEquals(TEST_UID, workSource.getAttributionUid());
2655 
2656         assertNull(workSource.getWorkChains());
2657     }
2658 
2659     private void setBatteryLevel(int level) {
2660         doReturn(level).when(mBatteryManagerInternal).getBatteryLevel();
2661         mService.mBatteryStateTracker
2662                 .onReceive(mContext, new Intent(Intent.ACTION_BATTERY_LEVEL_CHANGED));
2663     }
2664 
2665     private void setChargingPolicy(int policy) {
2666         doReturn(policy).when(mBatteryManagerInternal).getChargingPolicy();
2667         if (mChargingPolicyChangeListener != null) {
2668             mChargingPolicyChangeListener.onChargingPolicyChanged(policy);
2669         }
2670     }
2671 }
2672