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.text.format.DateUtils.DAY_IN_MILLIS;
20 import static android.text.format.DateUtils.HOUR_IN_MILLIS;
21 import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
22 
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
28 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
29 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
30 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
31 import static com.android.server.job.JobSchedulerService.sUptimeMillisClock;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertFalse;
35 import static org.junit.Assert.assertNotEquals;
36 import static org.junit.Assert.assertNotNull;
37 import static org.junit.Assert.assertNull;
38 import static org.junit.Assert.assertTrue;
39 import static org.junit.Assert.fail;
40 import static org.mockito.ArgumentMatchers.any;
41 import static org.mockito.ArgumentMatchers.anyBoolean;
42 import static org.mockito.ArgumentMatchers.anyInt;
43 import static org.mockito.ArgumentMatchers.anyString;
44 import static org.mockito.ArgumentMatchers.eq;
45 import static org.mockito.Mockito.when;
46 
47 import android.app.ActivityManager;
48 import android.app.ActivityManagerInternal;
49 import android.app.IActivityManager;
50 import android.app.UiModeManager;
51 import android.app.job.JobInfo;
52 import android.app.job.JobParameters;
53 import android.app.job.JobScheduler;
54 import android.app.job.JobWorkItem;
55 import android.app.usage.UsageStatsManagerInternal;
56 import android.content.ComponentName;
57 import android.content.Context;
58 import android.content.PermissionChecker;
59 import android.content.pm.PackageManager;
60 import android.content.pm.PackageManagerInternal;
61 import android.content.res.Resources;
62 import android.net.ConnectivityManager;
63 import android.net.NetworkPolicyManager;
64 import android.os.BatteryManagerInternal;
65 import android.os.Looper;
66 import android.os.RemoteException;
67 import android.os.ServiceManager;
68 import android.os.SystemClock;
69 
70 import com.android.server.AppStateTracker;
71 import com.android.server.AppStateTrackerImpl;
72 import com.android.server.DeviceIdleInternal;
73 import com.android.server.LocalServices;
74 import com.android.server.PowerAllowlistInternal;
75 import com.android.server.SystemServiceManager;
76 import com.android.server.job.controllers.ConnectivityController;
77 import com.android.server.job.controllers.JobStatus;
78 import com.android.server.job.controllers.QuotaController;
79 import com.android.server.job.controllers.TareController;
80 import com.android.server.pm.UserManagerInternal;
81 import com.android.server.usage.AppStandbyInternal;
82 
83 import org.junit.After;
84 import org.junit.Before;
85 import org.junit.Test;
86 import org.mockito.Mock;
87 import org.mockito.MockitoSession;
88 import org.mockito.quality.Strictness;
89 
90 import java.time.Clock;
91 import java.time.Duration;
92 import java.time.ZoneOffset;
93 
94 public class JobSchedulerServiceTest {
95     private static final String TAG = JobSchedulerServiceTest.class.getSimpleName();
96     private static final int TEST_UID = 10123;
97 
98     private JobSchedulerService mService;
99 
100     private MockitoSession mMockingSession;
101     @Mock
102     private ActivityManagerInternal mActivityMangerInternal;
103     @Mock
104     private Context mContext;
105     @Mock
106     private PackageManagerInternal mPackageManagerInternal;
107 
108     private class TestJobSchedulerService extends JobSchedulerService {
TestJobSchedulerService(Context context)109         TestJobSchedulerService(Context context) {
110             super(context);
111             mAppStateTracker = mock(AppStateTrackerImpl.class);
112         }
113     }
114 
115     @Before
setUp()116     public void setUp() {
117         mMockingSession = mockitoSession()
118                 .initMocks(this)
119                 .strictness(Strictness.LENIENT)
120                 .mockStatic(LocalServices.class)
121                 .mockStatic(PermissionChecker.class)
122                 .mockStatic(ServiceManager.class)
123                 .startMocking();
124 
125         // Called in JobSchedulerService constructor.
126         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
127         doReturn(mActivityMangerInternal)
128                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
129         doReturn(mock(AppStandbyInternal.class))
130                 .when(() -> LocalServices.getService(AppStandbyInternal.class));
131         doReturn(mock(BatteryManagerInternal.class))
132                 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
133         doReturn(mPackageManagerInternal)
134                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
135         doReturn(mock(UsageStatsManagerInternal.class))
136                 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
137         when(mContext.getString(anyInt())).thenReturn("some_test_string");
138         // Called in BackgroundJobsController constructor.
139         doReturn(mock(AppStateTrackerImpl.class))
140                 .when(() -> LocalServices.getService(AppStateTracker.class));
141         // Called in ConnectivityController constructor.
142         when(mContext.getSystemService(ConnectivityManager.class))
143                 .thenReturn(mock(ConnectivityManager.class));
144         when(mContext.getSystemService(NetworkPolicyManager.class))
145                 .thenReturn(mock(NetworkPolicyManager.class));
146         // Called in DeviceIdleJobsController constructor.
147         doReturn(mock(DeviceIdleInternal.class))
148                 .when(() -> LocalServices.getService(DeviceIdleInternal.class));
149         // Used in JobConcurrencyManager.
150         doReturn(mock(UserManagerInternal.class))
151                 .when(() -> LocalServices.getService(UserManagerInternal.class));
152         // Used in JobStatus.
153         doReturn(mock(JobSchedulerInternal.class))
154                 .when(() -> LocalServices.getService(JobSchedulerInternal.class));
155         // Called via IdleController constructor.
156         when(mContext.getPackageManager()).thenReturn(mock(PackageManager.class));
157         when(mContext.getResources()).thenReturn(mock(Resources.class));
158         // Called in QuotaController constructor.
159         doReturn(mock(PowerAllowlistInternal.class))
160                 .when(() -> LocalServices.getService(PowerAllowlistInternal.class));
161         IActivityManager activityManager = ActivityManager.getService();
162         spyOn(activityManager);
163         try {
164             doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
165         } catch (RemoteException e) {
166             fail("registerUidObserver threw exception: " + e.getMessage());
167         }
168         // Called by QuotaTracker
169         doReturn(mock(SystemServiceManager.class))
170                 .when(() -> LocalServices.getService(SystemServiceManager.class));
171 
172         JobSchedulerService.sSystemClock = Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
173         JobSchedulerService.sElapsedRealtimeClock =
174                 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
175         // Make sure the uptime is at least 24 hours so that tests that rely on high uptime work.
176         sUptimeMillisClock = getAdvancedClock(sUptimeMillisClock, 24 * HOUR_IN_MILLIS);
177         // Called by DeviceIdlenessTracker
178         when(mContext.getSystemService(UiModeManager.class)).thenReturn(mock(UiModeManager.class));
179 
180         mService = new TestJobSchedulerService(mContext);
181     }
182 
183     @After
tearDown()184     public void tearDown() {
185         if (mMockingSession != null) {
186             mMockingSession.finishMocking();
187         }
188         mService.cancelJobsForUid(TEST_UID, true,
189                 JobParameters.STOP_REASON_UNDEFINED, JobParameters.INTERNAL_STOP_REASON_UNKNOWN,
190                 "test cleanup");
191     }
192 
getAdvancedClock(Clock clock, long incrementMs)193     private Clock getAdvancedClock(Clock clock, long incrementMs) {
194         return Clock.offset(clock, Duration.ofMillis(incrementMs));
195     }
196 
advanceElapsedClock(long incrementMs)197     private void advanceElapsedClock(long incrementMs) {
198         JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
199                 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
200     }
201 
createJobInfo()202     private static JobInfo.Builder createJobInfo() {
203         return createJobInfo(351);
204     }
205 
createJobInfo(int jobId)206     private static JobInfo.Builder createJobInfo(int jobId) {
207         return new JobInfo.Builder(jobId, new ComponentName("foo", "bar"));
208     }
209 
createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder)210     private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder) {
211         return createJobStatus(testTag, jobInfoBuilder, 1234);
212     }
213 
createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder, int callingUid)214     private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
215             int callingUid) {
216         return createJobStatus(testTag, jobInfoBuilder, callingUid, "com.android.test");
217     }
218 
createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder, int callingUid, String sourcePkg)219     private JobStatus createJobStatus(String testTag, JobInfo.Builder jobInfoBuilder,
220             int callingUid, String sourcePkg) {
221         return JobStatus.createFromJobInfo(
222                 jobInfoBuilder.build(), callingUid, sourcePkg, 0, "JSSTest", testTag);
223     }
224 
grantRunUserInitiatedJobsPermission(boolean grant)225     private void grantRunUserInitiatedJobsPermission(boolean grant) {
226         final int permissionStatus = grant
227                 ? PermissionChecker.PERMISSION_GRANTED : PermissionChecker.PERMISSION_HARD_DENIED;
228         doReturn(permissionStatus)
229                 .when(() -> PermissionChecker.checkPermissionForPreflight(
230                         any(), eq(android.Manifest.permission.RUN_USER_INITIATED_JOBS),
231                         anyInt(), anyInt(), anyString()));
232     }
233 
234     @Test
testGetMinJobExecutionGuaranteeMs()235     public void testGetMinJobExecutionGuaranteeMs() {
236         JobStatus ejMax = createJobStatus("testGetMinJobExecutionGuaranteeMs",
237                 createJobInfo(1).setExpedited(true));
238         JobStatus ejHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs",
239                 createJobInfo(2).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH));
240         JobStatus ejMaxDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs",
241                 createJobInfo(3).setExpedited(true));
242         JobStatus ejHighDowngraded = createJobStatus("testGetMinJobExecutionGuaranteeMs",
243                 createJobInfo(4).setExpedited(true).setPriority(JobInfo.PRIORITY_HIGH));
244         JobStatus jobHigh = createJobStatus("testGetMinJobExecutionGuaranteeMs",
245                 createJobInfo(5).setPriority(JobInfo.PRIORITY_HIGH));
246         JobStatus jobDef = createJobStatus("testGetMinJobExecutionGuaranteeMs",
247                 createJobInfo(6));
248         JobStatus jobUIDT = createJobStatus("testGetMinJobExecutionGuaranteeMs",
249                 createJobInfo(9)
250                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
251 
252         spyOn(ejMax);
253         spyOn(ejHigh);
254         spyOn(ejMaxDowngraded);
255         spyOn(ejHighDowngraded);
256         spyOn(jobHigh);
257         spyOn(jobDef);
258         spyOn(jobUIDT);
259 
260         when(ejMax.shouldTreatAsExpeditedJob()).thenReturn(true);
261         when(ejHigh.shouldTreatAsExpeditedJob()).thenReturn(true);
262         when(ejMaxDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
263         when(ejHighDowngraded.shouldTreatAsExpeditedJob()).thenReturn(false);
264         when(jobHigh.shouldTreatAsExpeditedJob()).thenReturn(false);
265         when(jobDef.shouldTreatAsExpeditedJob()).thenReturn(false);
266         when(jobUIDT.shouldTreatAsUserInitiatedJob()).thenReturn(true);
267 
268         ConnectivityController connectivityController = mService.getConnectivityController();
269         spyOn(connectivityController);
270         mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS;
271         mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS = 2 * HOUR_IN_MILLIS;
272         mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f;
273         mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS;
274         mService.mConstants.RUNTIME_UI_LIMIT_MS = 6 * HOUR_IN_MILLIS;
275 
276         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
277                 mService.getMinJobExecutionGuaranteeMs(ejMax));
278         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
279                 mService.getMinJobExecutionGuaranteeMs(ejHigh));
280         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
281                 mService.getMinJobExecutionGuaranteeMs(ejMaxDowngraded));
282         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
283                 mService.getMinJobExecutionGuaranteeMs(ejHighDowngraded));
284         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
285                 mService.getMinJobExecutionGuaranteeMs(jobHigh));
286         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
287                 mService.getMinJobExecutionGuaranteeMs(jobDef));
288         // UserInitiated
289         grantRunUserInitiatedJobsPermission(false);
290         // Permission isn't granted, so it should just be treated as a regular job.
291         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
292                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
293 
294         grantRunUserInitiatedJobsPermission(true); // With permission
295         mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = true;
296         doReturn(ConnectivityController.UNKNOWN_TIME)
297                 .when(connectivityController).getEstimatedTransferTimeMs(any());
298         assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
299                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
300         doReturn(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS / 2)
301                 .when(connectivityController).getEstimatedTransferTimeMs(any());
302         assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS,
303                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
304         final long estimatedTransferTimeMs =
305                 mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS * 2;
306         doReturn(estimatedTransferTimeMs)
307                 .when(connectivityController).getEstimatedTransferTimeMs(any());
308         assertEquals((long) (estimatedTransferTimeMs
309                         * mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR),
310                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
311         doReturn(mService.mConstants.RUNTIME_UI_LIMIT_MS * 2)
312                 .when(connectivityController).getEstimatedTransferTimeMs(any());
313         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
314                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
315 
316         mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false;
317         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
318                 mService.getMinJobExecutionGuaranteeMs(jobUIDT));
319     }
320 
321     @Test
testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_disabled()322     public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_disabled() {
323         JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
324                 createJobInfo(1)
325                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
326         JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
327                 createJobInfo(2).setExpedited(true));
328         JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
329                 createJobInfo(3));
330         spyOn(jobUij);
331         when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
332         jobUij.startedAsUserInitiatedJob = true;
333         spyOn(jobEj);
334         when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
335         jobEj.startedAsExpeditedJob = true;
336 
337         mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
338         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
339         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
340         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
341         mService.updateQuotaTracker();
342         mService.resetScheduleQuota();
343 
344         // Safeguards disabled -> no penalties.
345         grantRunUserInitiatedJobsPermission(true);
346         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
347                 mService.getMinJobExecutionGuaranteeMs(jobUij));
348         grantRunUserInitiatedJobsPermission(false);
349         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
350                 mService.getMinJobExecutionGuaranteeMs(jobUij));
351         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
352                 mService.getMinJobExecutionGuaranteeMs(jobEj));
353         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
354                 mService.getMinJobExecutionGuaranteeMs(jobReg));
355 
356         // 1 UIJ timeout. No max execution penalty yet.
357         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
358         grantRunUserInitiatedJobsPermission(true);
359         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
360                 mService.getMinJobExecutionGuaranteeMs(jobUij));
361         grantRunUserInitiatedJobsPermission(false);
362         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
363                 mService.getMinJobExecutionGuaranteeMs(jobUij));
364         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
365                 mService.getMinJobExecutionGuaranteeMs(jobEj));
366         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
367                 mService.getMinJobExecutionGuaranteeMs(jobReg));
368 
369         // 2 UIJ timeouts. Safeguards disabled -> no penalties.
370         jobUij.madeActive =
371                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
372         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
373         grantRunUserInitiatedJobsPermission(true);
374         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
375                 mService.getMinJobExecutionGuaranteeMs(jobUij));
376         grantRunUserInitiatedJobsPermission(false);
377         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
378                 mService.getMinJobExecutionGuaranteeMs(jobUij));
379         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
380                 mService.getMinJobExecutionGuaranteeMs(jobEj));
381         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
382                 mService.getMinJobExecutionGuaranteeMs(jobReg));
383 
384         // 1 EJ timeout. No max execution penalty yet.
385         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
386         grantRunUserInitiatedJobsPermission(true);
387         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
388                 mService.getMinJobExecutionGuaranteeMs(jobUij));
389         grantRunUserInitiatedJobsPermission(false);
390         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
391                 mService.getMinJobExecutionGuaranteeMs(jobUij));
392         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
393                 mService.getMinJobExecutionGuaranteeMs(jobEj));
394         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
395                 mService.getMinJobExecutionGuaranteeMs(jobReg));
396 
397         // 2 EJ timeouts. Safeguards disabled -> no penalties.
398         jobEj.madeActive =
399                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
400         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
401         grantRunUserInitiatedJobsPermission(true);
402         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
403                 mService.getMinJobExecutionGuaranteeMs(jobUij));
404         grantRunUserInitiatedJobsPermission(false);
405         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
406                 mService.getMinJobExecutionGuaranteeMs(jobUij));
407         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
408                 mService.getMinJobExecutionGuaranteeMs(jobEj));
409         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
410                 mService.getMinJobExecutionGuaranteeMs(jobReg));
411 
412         // 1 reg timeout. No max execution penalty yet.
413         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
414         grantRunUserInitiatedJobsPermission(true);
415         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
416                 mService.getMinJobExecutionGuaranteeMs(jobUij));
417         grantRunUserInitiatedJobsPermission(false);
418         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
419                 mService.getMinJobExecutionGuaranteeMs(jobUij));
420         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
421                 mService.getMinJobExecutionGuaranteeMs(jobEj));
422         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
423                 mService.getMinJobExecutionGuaranteeMs(jobReg));
424 
425         // 2 Reg timeouts. Safeguards disabled -> no penalties.
426         jobReg.madeActive =
427                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
428         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
429         grantRunUserInitiatedJobsPermission(true);
430         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
431                 mService.getMinJobExecutionGuaranteeMs(jobUij));
432         grantRunUserInitiatedJobsPermission(false);
433         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
434                 mService.getMinJobExecutionGuaranteeMs(jobUij));
435         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
436                 mService.getMinJobExecutionGuaranteeMs(jobEj));
437         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
438                 mService.getMinJobExecutionGuaranteeMs(jobReg));
439     }
440 
441     @Test
testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_enabled()442     public void testGetMinJobExecutionGuaranteeMs_timeoutSafeguards_enabled() {
443         JobStatus jobUij = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
444                 createJobInfo(1)
445                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
446         JobStatus jobEj = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
447                 createJobInfo(2).setExpedited(true));
448         JobStatus jobReg = createJobStatus("testGetMinJobExecutionGuaranteeMs_timeoutSafeguards",
449                 createJobInfo(3));
450         spyOn(jobUij);
451         when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
452         jobUij.startedAsUserInitiatedJob = true;
453         spyOn(jobEj);
454         when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
455         jobEj.startedAsExpeditedJob = true;
456 
457         mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
458         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
459         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
460         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
461         mService.updateQuotaTracker();
462         mService.resetScheduleQuota();
463 
464         // No timeouts -> no penalties.
465         grantRunUserInitiatedJobsPermission(true);
466         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
467                 mService.getMinJobExecutionGuaranteeMs(jobUij));
468         grantRunUserInitiatedJobsPermission(false);
469         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
470                 mService.getMinJobExecutionGuaranteeMs(jobUij));
471         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
472                 mService.getMinJobExecutionGuaranteeMs(jobEj));
473         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
474                 mService.getMinJobExecutionGuaranteeMs(jobReg));
475 
476         // 1 UIJ timeout. No execution penalty yet.
477         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
478         grantRunUserInitiatedJobsPermission(true);
479         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
480                 mService.getMinJobExecutionGuaranteeMs(jobUij));
481         grantRunUserInitiatedJobsPermission(false);
482         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
483                 mService.getMinJobExecutionGuaranteeMs(jobUij));
484         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
485                 mService.getMinJobExecutionGuaranteeMs(jobEj));
486         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
487                 mService.getMinJobExecutionGuaranteeMs(jobReg));
488 
489         // Not a timeout -> 1 UIJ timeout. No execution penalty yet.
490         jobUij.madeActive = sUptimeMillisClock.millis() - 1;
491         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
492         grantRunUserInitiatedJobsPermission(true);
493         assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS,
494                 mService.getMinJobExecutionGuaranteeMs(jobUij));
495         grantRunUserInitiatedJobsPermission(false);
496         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
497                 mService.getMinJobExecutionGuaranteeMs(jobUij));
498         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
499                 mService.getMinJobExecutionGuaranteeMs(jobEj));
500         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
501                 mService.getMinJobExecutionGuaranteeMs(jobReg));
502 
503         // 2 UIJ timeouts. Min execution penalty only for UIJs.
504         jobUij.madeActive =
505                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
506         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
507         grantRunUserInitiatedJobsPermission(true);
508         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
509                 mService.getMinJobExecutionGuaranteeMs(jobUij));
510         grantRunUserInitiatedJobsPermission(false);
511         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
512                 mService.getMinJobExecutionGuaranteeMs(jobUij));
513         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
514                 mService.getMinJobExecutionGuaranteeMs(jobEj));
515         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
516                 mService.getMinJobExecutionGuaranteeMs(jobReg));
517 
518         // 1 EJ timeout. No max execution penalty yet.
519         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
520         grantRunUserInitiatedJobsPermission(true);
521         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
522                 mService.getMinJobExecutionGuaranteeMs(jobUij));
523         grantRunUserInitiatedJobsPermission(false);
524         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
525                 mService.getMinJobExecutionGuaranteeMs(jobUij));
526         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
527                 mService.getMinJobExecutionGuaranteeMs(jobEj));
528         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
529                 mService.getMinJobExecutionGuaranteeMs(jobReg));
530 
531         // 2 EJ timeouts. Max execution penalty for EJs.
532         jobEj.madeActive =
533                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
534         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
535         grantRunUserInitiatedJobsPermission(true);
536         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
537                 mService.getMinJobExecutionGuaranteeMs(jobUij));
538         grantRunUserInitiatedJobsPermission(false);
539         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
540                 mService.getMinJobExecutionGuaranteeMs(jobUij));
541         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
542                 mService.getMinJobExecutionGuaranteeMs(jobEj));
543         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
544                 mService.getMinJobExecutionGuaranteeMs(jobReg));
545 
546         // 1 reg timeout. No max execution penalty yet.
547         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
548         grantRunUserInitiatedJobsPermission(true);
549         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
550                 mService.getMinJobExecutionGuaranteeMs(jobUij));
551         grantRunUserInitiatedJobsPermission(false);
552         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
553                 mService.getMinJobExecutionGuaranteeMs(jobUij));
554         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
555                 mService.getMinJobExecutionGuaranteeMs(jobEj));
556         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
557                 mService.getMinJobExecutionGuaranteeMs(jobReg));
558 
559         // 2 Reg timeouts. Max execution penalty for regular jobs.
560         jobReg.madeActive =
561                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
562         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
563         grantRunUserInitiatedJobsPermission(true);
564         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
565                 mService.getMinJobExecutionGuaranteeMs(jobUij));
566         grantRunUserInitiatedJobsPermission(false);
567         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
568                 mService.getMinJobExecutionGuaranteeMs(jobUij));
569         assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS,
570                 mService.getMinJobExecutionGuaranteeMs(jobEj));
571         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
572                 mService.getMinJobExecutionGuaranteeMs(jobReg));
573     }
574 
575     @Test
testGetMaxJobExecutionTimeMs()576     public void testGetMaxJobExecutionTimeMs() {
577         JobStatus jobUIDT = createJobStatus("testGetMaxJobExecutionTimeMs",
578                 createJobInfo(10)
579                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
580         spyOn(jobUIDT);
581         when(jobUIDT.shouldTreatAsUserInitiatedJob()).thenReturn(true);
582 
583         QuotaController quotaController = mService.getQuotaController();
584         spyOn(quotaController);
585         TareController tareController = mService.getTareController();
586         spyOn(tareController);
587         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
588                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
589         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
590                 .when(tareController).getMaxJobExecutionTimeMsLocked(any());
591 
592         grantRunUserInitiatedJobsPermission(true);
593         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
594                 mService.getMaxJobExecutionTimeMs(jobUIDT));
595         grantRunUserInitiatedJobsPermission(false);
596         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
597                 mService.getMaxJobExecutionTimeMs(jobUIDT));
598     }
599 
600     @Test
testGetMaxJobExecutionTimeMs_timeoutSafeguards_disabled()601     public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_disabled() {
602         JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
603                 createJobInfo(1)
604                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
605         JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
606                 createJobInfo(2).setExpedited(true));
607         JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
608                 createJobInfo(3));
609         spyOn(jobUij);
610         when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
611         jobUij.startedAsUserInitiatedJob = true;
612         spyOn(jobEj);
613         when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
614         jobEj.startedAsExpeditedJob = true;
615 
616         QuotaController quotaController = mService.getQuotaController();
617         spyOn(quotaController);
618         TareController tareController = mService.getTareController();
619         spyOn(tareController);
620         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
621                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
622         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
623                 .when(tareController).getMaxJobExecutionTimeMsLocked(any());
624 
625         mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = false;
626         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
627         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
628         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
629         mService.updateQuotaTracker();
630         mService.resetScheduleQuota();
631 
632         // Safeguards disabled -> no penalties.
633         grantRunUserInitiatedJobsPermission(true);
634         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
635                 mService.getMaxJobExecutionTimeMs(jobUij));
636         grantRunUserInitiatedJobsPermission(false);
637         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
638                 mService.getMaxJobExecutionTimeMs(jobUij));
639         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
640                 mService.getMaxJobExecutionTimeMs(jobEj));
641         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
642                 mService.getMaxJobExecutionTimeMs(jobReg));
643 
644         // 1 UIJ timeout. No max execution penalty yet.
645         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
646         grantRunUserInitiatedJobsPermission(true);
647         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
648                 mService.getMaxJobExecutionTimeMs(jobUij));
649         grantRunUserInitiatedJobsPermission(false);
650         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
651                 mService.getMaxJobExecutionTimeMs(jobUij));
652         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
653                 mService.getMaxJobExecutionTimeMs(jobEj));
654         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
655                 mService.getMaxJobExecutionTimeMs(jobReg));
656 
657         // 2 UIJ timeouts. Safeguards disabled -> no penalties.
658         jobUij.madeActive =
659                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
660         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
661         grantRunUserInitiatedJobsPermission(true);
662         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
663                 mService.getMaxJobExecutionTimeMs(jobUij));
664         grantRunUserInitiatedJobsPermission(false);
665         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
666                 mService.getMaxJobExecutionTimeMs(jobUij));
667         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
668                 mService.getMaxJobExecutionTimeMs(jobEj));
669         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
670                 mService.getMaxJobExecutionTimeMs(jobReg));
671 
672         // 1 EJ timeout. No max execution penalty yet.
673         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
674         grantRunUserInitiatedJobsPermission(true);
675         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
676                 mService.getMaxJobExecutionTimeMs(jobUij));
677         grantRunUserInitiatedJobsPermission(false);
678         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
679                 mService.getMaxJobExecutionTimeMs(jobUij));
680         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
681                 mService.getMaxJobExecutionTimeMs(jobEj));
682         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
683                 mService.getMaxJobExecutionTimeMs(jobReg));
684 
685         // 2 EJ timeouts. Safeguards disabled -> no penalties.
686         jobEj.madeActive =
687                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
688         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
689         grantRunUserInitiatedJobsPermission(true);
690         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
691                 mService.getMaxJobExecutionTimeMs(jobUij));
692         grantRunUserInitiatedJobsPermission(false);
693         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
694                 mService.getMaxJobExecutionTimeMs(jobUij));
695         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
696                 mService.getMaxJobExecutionTimeMs(jobEj));
697         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
698                 mService.getMaxJobExecutionTimeMs(jobReg));
699 
700         // 1 reg timeout. No max execution penalty yet.
701         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
702         grantRunUserInitiatedJobsPermission(true);
703         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
704                 mService.getMaxJobExecutionTimeMs(jobUij));
705         grantRunUserInitiatedJobsPermission(false);
706         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
707                 mService.getMaxJobExecutionTimeMs(jobUij));
708         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
709                 mService.getMaxJobExecutionTimeMs(jobEj));
710         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
711                 mService.getMaxJobExecutionTimeMs(jobReg));
712 
713         // 2 Reg timeouts. Safeguards disabled -> no penalties.
714         jobReg.madeActive =
715                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
716         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
717         grantRunUserInitiatedJobsPermission(true);
718         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
719                 mService.getMaxJobExecutionTimeMs(jobUij));
720         grantRunUserInitiatedJobsPermission(false);
721         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
722                 mService.getMaxJobExecutionTimeMs(jobUij));
723         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
724                 mService.getMaxJobExecutionTimeMs(jobEj));
725         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
726                 mService.getMaxJobExecutionTimeMs(jobReg));
727     }
728 
729     @Test
testGetMaxJobExecutionTimeMs_timeoutSafeguards_enabled()730     public void testGetMaxJobExecutionTimeMs_timeoutSafeguards_enabled() {
731         JobStatus jobUij = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
732                 createJobInfo(1)
733                         .setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
734         JobStatus jobEj = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
735                 createJobInfo(2).setExpedited(true));
736         JobStatus jobReg = createJobStatus("testGetMaxJobExecutionTimeMs_timeoutSafeguards",
737                 createJobInfo(3));
738         spyOn(jobUij);
739         when(jobUij.shouldTreatAsUserInitiatedJob()).thenReturn(true);
740         jobUij.startedAsUserInitiatedJob = true;
741         spyOn(jobEj);
742         when(jobEj.shouldTreatAsExpeditedJob()).thenReturn(true);
743         jobEj.startedAsExpeditedJob = true;
744 
745         QuotaController quotaController = mService.getQuotaController();
746         spyOn(quotaController);
747         TareController tareController = mService.getTareController();
748         spyOn(tareController);
749         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
750                 .when(quotaController).getMaxJobExecutionTimeMsLocked(any());
751         doReturn(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS)
752                 .when(tareController).getMaxJobExecutionTimeMsLocked(any());
753 
754         mService.mConstants.ENABLE_EXECUTION_SAFEGUARDS_UDC = true;
755         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_UIJ_COUNT = 2;
756         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_EJ_COUNT = 2;
757         mService.mConstants.EXECUTION_SAFEGUARDS_UDC_TIMEOUT_REG_COUNT = 2;
758         mService.updateQuotaTracker();
759         mService.resetScheduleQuota();
760 
761         // No timeouts -> no penalties.
762         grantRunUserInitiatedJobsPermission(true);
763         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
764                 mService.getMaxJobExecutionTimeMs(jobUij));
765         grantRunUserInitiatedJobsPermission(false);
766         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
767                 mService.getMaxJobExecutionTimeMs(jobUij));
768         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
769                 mService.getMaxJobExecutionTimeMs(jobEj));
770         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
771                 mService.getMaxJobExecutionTimeMs(jobReg));
772 
773         // 1 UIJ timeout. No max execution penalty yet.
774         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
775         grantRunUserInitiatedJobsPermission(true);
776         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
777                 mService.getMaxJobExecutionTimeMs(jobUij));
778         grantRunUserInitiatedJobsPermission(false);
779         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
780                 mService.getMaxJobExecutionTimeMs(jobUij));
781         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
782                 mService.getMaxJobExecutionTimeMs(jobEj));
783         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
784                 mService.getMaxJobExecutionTimeMs(jobReg));
785 
786         // Not a timeout -> 1 UIJ timeout. No max execution penalty yet.
787         jobUij.madeActive = sUptimeMillisClock.millis() - 1;
788         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
789         grantRunUserInitiatedJobsPermission(true);
790         assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS,
791                 mService.getMaxJobExecutionTimeMs(jobUij));
792         grantRunUserInitiatedJobsPermission(false);
793         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
794                 mService.getMaxJobExecutionTimeMs(jobUij));
795         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
796                 mService.getMaxJobExecutionTimeMs(jobEj));
797         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
798                 mService.getMaxJobExecutionTimeMs(jobReg));
799 
800         // 2 UIJ timeouts. Max execution penalty only for UIJs.
801         jobUij.madeActive =
802                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS;
803         mService.maybeProcessBuggyJob(jobUij, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
804         grantRunUserInitiatedJobsPermission(true);
805         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
806                 mService.getMaxJobExecutionTimeMs(jobUij));
807         grantRunUserInitiatedJobsPermission(false);
808         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
809                 mService.getMaxJobExecutionTimeMs(jobUij));
810         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
811                 mService.getMaxJobExecutionTimeMs(jobEj));
812         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
813                 mService.getMaxJobExecutionTimeMs(jobReg));
814 
815         // 1 EJ timeout. No max execution penalty yet.
816         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
817         grantRunUserInitiatedJobsPermission(true);
818         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
819                 mService.getMaxJobExecutionTimeMs(jobUij));
820         grantRunUserInitiatedJobsPermission(false);
821         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
822                 mService.getMaxJobExecutionTimeMs(jobUij));
823         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
824                 mService.getMaxJobExecutionTimeMs(jobEj));
825         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
826                 mService.getMaxJobExecutionTimeMs(jobReg));
827 
828         // Not a timeout -> 1 EJ timeout. No max execution penalty yet.
829         jobEj.madeActive = sUptimeMillisClock.millis() - 1;
830         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
831         grantRunUserInitiatedJobsPermission(true);
832         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
833                 mService.getMaxJobExecutionTimeMs(jobUij));
834         grantRunUserInitiatedJobsPermission(false);
835         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
836                 mService.getMaxJobExecutionTimeMs(jobUij));
837         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
838                 mService.getMaxJobExecutionTimeMs(jobEj));
839         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
840                 mService.getMaxJobExecutionTimeMs(jobReg));
841 
842         // 2 EJ timeouts. Max execution penalty for EJs.
843         jobEj.madeActive =
844                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS;
845         mService.maybeProcessBuggyJob(jobEj, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
846         grantRunUserInitiatedJobsPermission(true);
847         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
848                 mService.getMaxJobExecutionTimeMs(jobUij));
849         grantRunUserInitiatedJobsPermission(false);
850         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
851                 mService.getMaxJobExecutionTimeMs(jobUij));
852         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
853                 mService.getMaxJobExecutionTimeMs(jobEj));
854         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
855                 mService.getMaxJobExecutionTimeMs(jobReg));
856 
857         // 1 reg timeout. No max execution penalty yet.
858         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
859         grantRunUserInitiatedJobsPermission(true);
860         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
861                 mService.getMaxJobExecutionTimeMs(jobUij));
862         grantRunUserInitiatedJobsPermission(false);
863         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
864                 mService.getMaxJobExecutionTimeMs(jobUij));
865         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
866                 mService.getMaxJobExecutionTimeMs(jobEj));
867         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
868                 mService.getMaxJobExecutionTimeMs(jobReg));
869 
870         // Not a timeout -> 1 reg timeout. No max execution penalty yet.
871         jobReg.madeActive = sUptimeMillisClock.millis() - 1;
872         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
873         grantRunUserInitiatedJobsPermission(true);
874         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
875                 mService.getMaxJobExecutionTimeMs(jobUij));
876         grantRunUserInitiatedJobsPermission(false);
877         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
878                 mService.getMaxJobExecutionTimeMs(jobUij));
879         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
880                 mService.getMaxJobExecutionTimeMs(jobEj));
881         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
882                 mService.getMaxJobExecutionTimeMs(jobReg));
883 
884         // 2 Reg timeouts. Max execution penalty for regular jobs.
885         jobReg.madeActive =
886                 sUptimeMillisClock.millis() - mService.mConstants.RUNTIME_MIN_GUARANTEE_MS;
887         mService.maybeProcessBuggyJob(jobReg, JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
888         grantRunUserInitiatedJobsPermission(true);
889         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
890                 mService.getMaxJobExecutionTimeMs(jobUij));
891         grantRunUserInitiatedJobsPermission(false);
892         assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
893                 mService.getMaxJobExecutionTimeMs(jobUij));
894         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
895                 mService.getMaxJobExecutionTimeMs(jobEj));
896         assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS,
897                 mService.getMaxJobExecutionTimeMs(jobReg));
898     }
899 
900     /**
901      * Confirm that
902      * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
903      * returns a job that is no longer allowed to run as a user-initiated job after it hits
904      * the cumulative execution limit.
905      */
906     @Test
testGetRescheduleJobForFailure_cumulativeExecution()907     public void testGetRescheduleJobForFailure_cumulativeExecution() {
908         JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
909                 createJobInfo()
910                         .setUserInitiated(true)
911                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
912         assertTrue(originalJob.shouldTreatAsUserInitiatedJob());
913 
914         // Cumulative time = 0
915         JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
916                 JobParameters.STOP_REASON_UNDEFINED,
917                 JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
918         assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
919 
920         // Cumulative time = 50% of limit
921         rescheduledJob.incrementCumulativeExecutionTime(
922                 mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2);
923         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
924                 JobParameters.STOP_REASON_UNDEFINED,
925                 JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
926         assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
927 
928         // Cumulative time = 99.999999% of limit
929         rescheduledJob.incrementCumulativeExecutionTime(
930                 mService.mConstants.RUNTIME_CUMULATIVE_UI_LIMIT_MS / 2 - 1);
931         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
932                 JobParameters.STOP_REASON_UNDEFINED,
933                 JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
934         assertTrue(rescheduledJob.shouldTreatAsUserInitiatedJob());
935 
936         // Cumulative time = 100+% of limit
937         rescheduledJob.incrementCumulativeExecutionTime(2);
938         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
939                 JobParameters.STOP_REASON_UNDEFINED,
940                 JobParameters.INTERNAL_STOP_REASON_UNKNOWN);
941         assertFalse(rescheduledJob.shouldTreatAsUserInitiatedJob());
942     }
943 
944     /**
945      * Confirm that
946      * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
947      * returns a job with the correct delay and deadline constraints.
948      */
949     @Test
testGetRescheduleJobForFailure_timingCalculations()950     public void testGetRescheduleJobForFailure_timingCalculations() {
951         final long nowElapsed = sElapsedRealtimeClock.millis();
952         final long initialBackoffMs = MINUTE_IN_MILLIS;
953         mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
954 
955         JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
956                 createJobInfo()
957                         .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
958         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
959         assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
960 
961         // failure = 0, systemStop = 1
962         JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
963                 JobParameters.STOP_REASON_DEVICE_STATE,
964                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
965         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, rescheduledJob.getEarliestRunTime());
966         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
967 
968         // failure = 0, systemStop = 2
969         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
970                 JobParameters.STOP_REASON_DEVICE_STATE,
971                 JobParameters.INTERNAL_STOP_REASON_PREEMPT);
972         assertEquals(JobStatus.NO_EARLIEST_RUNTIME, rescheduledJob.getEarliestRunTime());
973         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
974         // failure = 0, systemStop = 3
975         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
976                 JobParameters.STOP_REASON_CONSTRAINT_CHARGING,
977                 JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED);
978         assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
979         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
980 
981         // failure = 0, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
982         for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) {
983             rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
984                     JobParameters.STOP_REASON_SYSTEM_PROCESSING,
985                     JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED);
986         }
987         assertEquals(nowElapsed + 2 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
988         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
989 
990         // failure = 1, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
991         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
992                 JobParameters.STOP_REASON_TIMEOUT,
993                 JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
994         assertEquals(nowElapsed + 3 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
995         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
996 
997         // failure = 2, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
998         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
999                 JobParameters.STOP_REASON_UNDEFINED,
1000                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1001         assertEquals(nowElapsed + 4 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
1002         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1003 
1004         // failure = 3, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
1005         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1006                 JobParameters.STOP_REASON_UNDEFINED,
1007                 JobParameters.INTERNAL_STOP_REASON_ANR);
1008         assertEquals(nowElapsed + 5 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
1009         assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
1010     }
1011 
1012     /**
1013      * Confirm that
1014      * {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int, int)}
1015      * returns a job that is correctly marked as demoted by the user.
1016      */
1017     @Test
testGetRescheduleJobForFailure_userDemotion()1018     public void testGetRescheduleJobForFailure_userDemotion() {
1019         JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
1020         assertEquals(0, originalJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
1021 
1022         // Reschedule for a non-user reason
1023         JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
1024                 JobParameters.STOP_REASON_DEVICE_STATE,
1025                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1026         assertEquals(0,
1027                 rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
1028 
1029         // Reschedule for a user reason
1030         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1031                 JobParameters.STOP_REASON_USER,
1032                 JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
1033         assertNotEquals(0,
1034                 rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
1035 
1036         // Reschedule a previously demoted job for a non-user reason
1037         rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
1038                 JobParameters.STOP_REASON_CONSTRAINT_CHARGING,
1039                 JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED);
1040         assertNotEquals(0,
1041                 rescheduledJob.getInternalFlags() & JobStatus.INTERNAL_FLAG_DEMOTED_BY_USER);
1042     }
1043 
1044     /**
1045      * Confirm that
1046      * returns {@code null} when for user-visible jobs stopped by the user.
1047      */
1048     @Test
testGetRescheduleJobForFailure_userStopped()1049     public void testGetRescheduleJobForFailure_userStopped() {
1050         JobStatus uiJob = createJobStatus("testGetRescheduleJobForFailure",
1051                 createJobInfo().setUserInitiated(true)
1052                         .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
1053         JobStatus uvJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
1054         spyOn(uvJob);
1055         doReturn(true).when(uvJob).isUserVisibleJob();
1056         JobStatus regJob = createJobStatus("testGetRescheduleJobForFailure", createJobInfo());
1057 
1058         // Reschedule for a non-user reason
1059         JobStatus rescheduledUiJob = mService.getRescheduleJobForFailureLocked(uiJob,
1060                 JobParameters.STOP_REASON_DEVICE_STATE,
1061                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1062         JobStatus rescheduledUvJob = mService.getRescheduleJobForFailureLocked(uvJob,
1063                 JobParameters.STOP_REASON_DEVICE_STATE,
1064                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1065         JobStatus rescheduledRegJob = mService.getRescheduleJobForFailureLocked(regJob,
1066                 JobParameters.STOP_REASON_DEVICE_STATE,
1067                 JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
1068         assertNotNull(rescheduledUiJob);
1069         assertNotNull(rescheduledUvJob);
1070         assertNotNull(rescheduledRegJob);
1071 
1072         // Reschedule for a user reason. The user-visible jobs shouldn't be rescheduled.
1073         spyOn(rescheduledUvJob);
1074         doReturn(true).when(rescheduledUvJob).isUserVisibleJob();
1075         rescheduledUiJob = mService.getRescheduleJobForFailureLocked(rescheduledUiJob,
1076                 JobParameters.STOP_REASON_USER,
1077                 JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
1078         rescheduledUvJob = mService.getRescheduleJobForFailureLocked(rescheduledUvJob,
1079                 JobParameters.STOP_REASON_USER,
1080                 JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
1081         rescheduledRegJob = mService.getRescheduleJobForFailureLocked(rescheduledRegJob,
1082                 JobParameters.STOP_REASON_USER,
1083                 JobParameters.INTERNAL_STOP_REASON_USER_UI_STOP);
1084         assertNull(rescheduledUiJob);
1085         assertNull(rescheduledUvJob);
1086         assertNotNull(rescheduledRegJob);
1087     }
1088 
1089     /**
1090      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1091      * with the correct delay and deadline constraints if the periodic job is scheduled with the
1092      * minimum possible period.
1093      */
1094     @Test
testGetRescheduleJobForPeriodic_minPeriod()1095     public void testGetRescheduleJobForPeriodic_minPeriod() {
1096         final long now = sElapsedRealtimeClock.millis();
1097         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
1098                 createJobInfo().setPeriodic(15 * MINUTE_IN_MILLIS));
1099         final long nextWindowStartTime = now + 15 * MINUTE_IN_MILLIS;
1100         final long nextWindowEndTime = now + 30 * MINUTE_IN_MILLIS;
1101 
1102         for (int i = 0; i < 25; i++) {
1103             JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1104             assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1105             assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1106             advanceElapsedClock(30_000); // 30 seconds
1107         }
1108 
1109         for (int i = 0; i < 5; i++) {
1110             // Window buffering in last 1/6 of window.
1111             JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1112             assertEquals(nextWindowStartTime + i * 30_000, rescheduledJob.getEarliestRunTime());
1113             assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1114             advanceElapsedClock(30_000); // 30 seconds
1115         }
1116     }
1117 
1118     /**
1119      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1120      * with the correct delay and deadline constraints if the periodic job is scheduled with a
1121      * period that's too large.
1122      */
1123     @Test
testGetRescheduleJobForPeriodic_largePeriod()1124     public void testGetRescheduleJobForPeriodic_largePeriod() {
1125         final long now = sElapsedRealtimeClock.millis();
1126         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
1127                 createJobInfo().setPeriodic(2 * 365 * DAY_IN_MILLIS));
1128         assertEquals(now, job.getEarliestRunTime());
1129         // Periods are capped at 365 days (1 year).
1130         assertEquals(now + 365 * DAY_IN_MILLIS, job.getLatestRunTimeElapsed());
1131         final long nextWindowStartTime = now + 365 * DAY_IN_MILLIS;
1132         final long nextWindowEndTime = nextWindowStartTime + 365 * DAY_IN_MILLIS;
1133 
1134         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1135         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1136         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1137     }
1138 
1139     /**
1140      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1141      * with the correct delay and deadline constraints if the periodic job is completed and
1142      * rescheduled while run in its expected running window.
1143      */
1144     @Test
testGetRescheduleJobForPeriodic_insideWindow()1145     public void testGetRescheduleJobForPeriodic_insideWindow() {
1146         final long now = sElapsedRealtimeClock.millis();
1147         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow",
1148                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1149         final long nextWindowStartTime = now + HOUR_IN_MILLIS;
1150         final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1151 
1152         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1153         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1154         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1155 
1156         advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 10 minutes
1157 
1158         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1159         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1160         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1161 
1162         advanceElapsedClock(20 * MINUTE_IN_MILLIS); // now + 30 minutes
1163 
1164         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1165         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1166         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1167 
1168         advanceElapsedClock(25 * MINUTE_IN_MILLIS); // now + 55 minutes
1169 
1170         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1171         // Shifted because it's close to the end of the window.
1172         assertEquals(nextWindowStartTime + 5 * MINUTE_IN_MILLIS,
1173                 rescheduledJob.getEarliestRunTime());
1174         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1175 
1176         advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 59 minutes
1177 
1178         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1179         // Shifted because it's close to the end of the window.
1180         assertEquals(nextWindowStartTime + 9 * MINUTE_IN_MILLIS,
1181                 rescheduledJob.getEarliestRunTime());
1182         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1183     }
1184 
1185     /**
1186      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1187      * with an extra delay and correct deadline constraint if the periodic job is completed near the
1188      * end of its expected running window.
1189      */
1190     @Test
testGetRescheduleJobForPeriodic_closeToEndOfWindow()1191     public void testGetRescheduleJobForPeriodic_closeToEndOfWindow() {
1192         JobStatus frequentJob = createJobStatus(
1193                 "testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1194                 createJobInfo().setPeriodic(15 * MINUTE_IN_MILLIS));
1195         long now = sElapsedRealtimeClock.millis();
1196         long nextWindowStartTime = now + 15 * MINUTE_IN_MILLIS;
1197         long nextWindowEndTime = now + 30 * MINUTE_IN_MILLIS;
1198 
1199         // At the beginning of the window. Next window should be unaffected.
1200         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
1201         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1202         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1203 
1204         // Halfway through window. Next window should be unaffected.
1205         advanceElapsedClock((long) (7.5 * MINUTE_IN_MILLIS));
1206         rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
1207         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1208         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1209 
1210         // In last 1/6 of window. Next window start time should be shifted slightly.
1211         advanceElapsedClock(6 * MINUTE_IN_MILLIS);
1212         rescheduledJob = mService.getRescheduleJobForPeriodic(frequentJob);
1213         assertEquals(nextWindowStartTime + MINUTE_IN_MILLIS,
1214                 rescheduledJob.getEarliestRunTime());
1215         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1216 
1217         JobStatus mediumJob = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1218                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1219         now = sElapsedRealtimeClock.millis();
1220         nextWindowStartTime = now + HOUR_IN_MILLIS;
1221         nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1222 
1223         // At the beginning of the window. Next window should be unaffected.
1224         rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
1225         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1226         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1227 
1228         // Halfway through window. Next window should be unaffected.
1229         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1230         rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
1231         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1232         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1233 
1234         // At the edge 1/6 of window. Next window should be unaffected.
1235         advanceElapsedClock(20 * MINUTE_IN_MILLIS);
1236         rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
1237         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1238         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1239 
1240         // In last 1/6 of window. Next window start time should be shifted slightly.
1241         advanceElapsedClock(6 * MINUTE_IN_MILLIS);
1242         rescheduledJob = mService.getRescheduleJobForPeriodic(mediumJob);
1243         assertEquals(nextWindowStartTime + (6 * MINUTE_IN_MILLIS),
1244                 rescheduledJob.getEarliestRunTime());
1245         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1246 
1247         JobStatus longJob = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1248                 createJobInfo().setPeriodic(6 * HOUR_IN_MILLIS));
1249         now = sElapsedRealtimeClock.millis();
1250         nextWindowStartTime = now + 6 * HOUR_IN_MILLIS;
1251         nextWindowEndTime = now + 12 * HOUR_IN_MILLIS;
1252 
1253         // At the beginning of the window. Next window should be unaffected.
1254         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1255         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1256         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1257 
1258         // Halfway through window. Next window should be unaffected.
1259         advanceElapsedClock(3 * HOUR_IN_MILLIS);
1260         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1261         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1262         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1263 
1264         // At the edge 1/6 of window. Next window should be unaffected.
1265         advanceElapsedClock(2 * HOUR_IN_MILLIS);
1266         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1267         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1268         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1269 
1270         // In last 1/6 of window. Next window should be unaffected since we're over the shift cap.
1271         advanceElapsedClock(15 * MINUTE_IN_MILLIS);
1272         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1273         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1274         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1275 
1276         // In last 1/6 of window. Next window start time should be shifted slightly.
1277         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1278         rescheduledJob = mService.getRescheduleJobForPeriodic(longJob);
1279         assertEquals(nextWindowStartTime + (30 * MINUTE_IN_MILLIS),
1280                 rescheduledJob.getEarliestRunTime());
1281         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1282 
1283         // Flex duration close to period duration.
1284         JobStatus gameyFlex = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1285                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 59 * MINUTE_IN_MILLIS));
1286         now = sElapsedRealtimeClock.millis();
1287         nextWindowStartTime = now + HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
1288         nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1289         advanceElapsedClock(MINUTE_IN_MILLIS);
1290 
1291         // At the beginning of the window. Next window should be unaffected.
1292         rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
1293         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1294         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1295 
1296         // Halfway through window. Next window should be unaffected.
1297         advanceElapsedClock(29 * MINUTE_IN_MILLIS);
1298         rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
1299         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1300         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1301 
1302         // At the edge 1/6 of window. Next window should be unaffected.
1303         advanceElapsedClock(20 * MINUTE_IN_MILLIS);
1304         rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
1305         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1306         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1307 
1308         // In last 1/6 of window. Next window start time should be shifted slightly.
1309         advanceElapsedClock(6 * MINUTE_IN_MILLIS);
1310         rescheduledJob = mService.getRescheduleJobForPeriodic(gameyFlex);
1311         assertEquals(nextWindowStartTime + (5 * MINUTE_IN_MILLIS),
1312                 rescheduledJob.getEarliestRunTime());
1313         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1314 
1315         // Very short flex duration compared to period duration.
1316         JobStatus superFlex = createJobStatus("testGetRescheduleJobForPeriodic_closeToEndOfWindow",
1317                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 10 * MINUTE_IN_MILLIS));
1318         now = sElapsedRealtimeClock.millis();
1319         nextWindowStartTime = now + HOUR_IN_MILLIS + 50 * MINUTE_IN_MILLIS;
1320         nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1321         advanceElapsedClock(MINUTE_IN_MILLIS);
1322 
1323         // At the beginning of the window. Next window should be unaffected.
1324         rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
1325         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1326         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1327 
1328         // Halfway through window. Next window should be unaffected.
1329         advanceElapsedClock(29 * MINUTE_IN_MILLIS);
1330         rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
1331         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1332         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1333 
1334         // At the edge 1/6 of window. Next window should be unaffected.
1335         advanceElapsedClock(20 * MINUTE_IN_MILLIS);
1336         rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
1337         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1338         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1339 
1340         // In last 1/6 of window. Next window should be unaffected since the flex duration pushes
1341         // the next window start time far enough away.
1342         advanceElapsedClock(6 * MINUTE_IN_MILLIS);
1343         rescheduledJob = mService.getRescheduleJobForPeriodic(superFlex);
1344         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1345         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1346     }
1347 
1348     /**
1349      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1350      * with the correct delay and deadline constraints if the periodic job with a custom flex
1351      * setting is completed and rescheduled while run in its expected running window.
1352      */
1353     @Test
testGetRescheduleJobForPeriodic_insideWindow_flex()1354     public void testGetRescheduleJobForPeriodic_insideWindow_flex() {
1355         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_flex",
1356                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
1357         // First window starts 30 minutes from now.
1358         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1359         final long now = sElapsedRealtimeClock.millis();
1360         final long nextWindowStartTime = now + HOUR_IN_MILLIS;
1361         final long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
1362 
1363         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1364         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1365         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1366 
1367         advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 10 minutes
1368 
1369         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1370         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1371         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1372 
1373         advanceElapsedClock(15 * MINUTE_IN_MILLIS); // now + 25 minutes
1374 
1375         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1376         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1377         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1378 
1379         advanceElapsedClock(4 * MINUTE_IN_MILLIS); // now + 29 minutes
1380 
1381         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1382         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1383         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1384     }
1385 
1386     /**
1387      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1388      * with the correct delay and deadline constraints if the periodic job failed but then ran
1389      * successfully and was rescheduled while run in its expected running window.
1390      */
1391     @Test
testGetRescheduleJobForPeriodic_insideWindow_failedJob()1392     public void testGetRescheduleJobForPeriodic_insideWindow_failedJob() {
1393         final long now = sElapsedRealtimeClock.millis();
1394         final long nextWindowStartTime = now + HOUR_IN_MILLIS;
1395         final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1396         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_failedJob",
1397                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1398         JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
1399                 JobParameters.STOP_REASON_UNDEFINED,
1400                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1401 
1402         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1403         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1404         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1405 
1406         advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 5 minutes
1407         failedJob = mService.getRescheduleJobForFailureLocked(job,
1408                 JobParameters.STOP_REASON_UNDEFINED,
1409                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1410         advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 10 minutes
1411 
1412         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1413         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1414         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1415 
1416         advanceElapsedClock(35 * MINUTE_IN_MILLIS); // now + 45 minutes
1417         failedJob = mService.getRescheduleJobForFailureLocked(job,
1418                 JobParameters.STOP_REASON_UNDEFINED,
1419                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1420         advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes
1421 
1422         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1423         // Shifted because it's close to the end of the window.
1424         assertEquals(nextWindowStartTime + 5 * MINUTE_IN_MILLIS,
1425                 rescheduledJob.getEarliestRunTime());
1426         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1427 
1428         advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
1429         failedJob = mService.getRescheduleJobForFailureLocked(job,
1430                 JobParameters.STOP_REASON_UNDEFINED,
1431                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1432         advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes
1433 
1434         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1435         // Shifted because it's close to the end of the window.
1436         assertEquals(nextWindowStartTime + 9 * MINUTE_IN_MILLIS,
1437                 rescheduledJob.getEarliestRunTime());
1438         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1439     }
1440 
1441     /**
1442      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1443      * with the correct delay and deadline constraints if the periodic job is completed and
1444      * rescheduled when run after its expected running window.
1445      */
1446     @Test
testGetRescheduleJobForPeriodic_outsideWindow()1447     public void testGetRescheduleJobForPeriodic_outsideWindow() {
1448         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow",
1449                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1450         long now = sElapsedRealtimeClock.millis();
1451         long nextWindowStartTime = now + HOUR_IN_MILLIS;
1452         long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1453 
1454         advanceElapsedClock(HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
1455         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1456         // have consistent windows, so the new window should start as soon as the previous window
1457         // ended and end PERIOD time after the previous window ended.
1458         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1459         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1460         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1461 
1462         advanceElapsedClock(2 * HOUR_IN_MILLIS);
1463         // Say that the job ran at this point, possibly due to device idle.
1464         // The next window should be consistent (start and end at the time it would have had the job
1465         // run normally in previous windows).
1466         nextWindowStartTime += 2 * HOUR_IN_MILLIS;
1467         nextWindowEndTime += 2 * HOUR_IN_MILLIS;
1468 
1469         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1470         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1471         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1472     }
1473 
1474     /**
1475      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1476      * with the correct delay and deadline constraints if the periodic job with a custom flex
1477      * setting is completed and rescheduled when run after its expected running window.
1478      */
1479     @Test
testGetRescheduleJobForPeriodic_outsideWindow_flex()1480     public void testGetRescheduleJobForPeriodic_outsideWindow_flex() {
1481         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_flex",
1482                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
1483         // First window starts 30 minutes from now.
1484         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1485         long now = sElapsedRealtimeClock.millis();
1486         long nextWindowStartTime = now + HOUR_IN_MILLIS;
1487         long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
1488 
1489         advanceElapsedClock(31 * MINUTE_IN_MILLIS);
1490         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1491         // have consistent windows, so the new window should start as soon as the previous window
1492         // ended and end PERIOD time after the previous window ended.
1493         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1494         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1495         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1496 
1497         // 5 minutes before the start of the next window. It's too close to the next window, so the
1498         // returned job should be for the window after.
1499         advanceElapsedClock(24 * MINUTE_IN_MILLIS);
1500         nextWindowStartTime += HOUR_IN_MILLIS;
1501         nextWindowEndTime += HOUR_IN_MILLIS;
1502         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1503         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1504         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1505 
1506         advanceElapsedClock(2 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS);
1507         // Say that the job ran at this point, possibly due to device idle.
1508         // The next window should be consistent (start and end at the time it would have had the job
1509         // run normally in previous windows).
1510         nextWindowStartTime += 2 * HOUR_IN_MILLIS;
1511         nextWindowEndTime += 2 * HOUR_IN_MILLIS;
1512 
1513         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1514         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1515         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1516     }
1517 
1518     /**
1519      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1520      * with the correct delay and deadline constraints if the periodic job failed but then ran
1521      * successfully and was rescheduled when run after its expected running window.
1522      */
1523     @Test
testGetRescheduleJobForPeriodic_outsideWindow_failedJob()1524     public void testGetRescheduleJobForPeriodic_outsideWindow_failedJob() {
1525         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_failedJob",
1526                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
1527         JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
1528                 JobParameters.STOP_REASON_UNDEFINED,
1529                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1530         long now = sElapsedRealtimeClock.millis();
1531         long nextWindowStartTime = now + HOUR_IN_MILLIS;
1532         long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
1533 
1534         advanceElapsedClock(HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
1535         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1536         // have consistent windows, so the new window should start as soon as the previous window
1537         // ended and end PERIOD time after the previous window ended.
1538         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1539         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1540         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1541 
1542         advanceElapsedClock(2 * HOUR_IN_MILLIS);
1543         // Say that the job ran at this point, possibly due to device idle.
1544         // The next window should be consistent (start and end at the time it would have had the job
1545         // run normally in previous windows).
1546         nextWindowStartTime += 2 * HOUR_IN_MILLIS;
1547         nextWindowEndTime += 2 * HOUR_IN_MILLIS;
1548 
1549         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1550         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1551         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1552     }
1553 
1554     /**
1555      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
1556      * with the correct delay and deadline constraints if the periodic job with a custom flex
1557      * setting failed but then ran successfully and was rescheduled when run after its expected
1558      * running window.
1559      */
1560     @Test
testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob()1561     public void testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob() {
1562         JobStatus job = createJobStatus(
1563                 "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob",
1564                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
1565         JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
1566                 JobParameters.STOP_REASON_UNDEFINED,
1567                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1568         // First window starts 30 minutes from now.
1569         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
1570         long now = sElapsedRealtimeClock.millis();
1571         long nextWindowStartTime = now + HOUR_IN_MILLIS;
1572         long nextWindowEndTime = now + 90 * MINUTE_IN_MILLIS;
1573 
1574         advanceElapsedClock(31 * MINUTE_IN_MILLIS);
1575         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1576         // have consistent windows, so the new window should start as soon as the previous window
1577         // ended and end PERIOD time after the previous window ended.
1578         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1579         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1580         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1581 
1582         // 5 minutes before the start of the next window. It's too close to the next window, so the
1583         // returned job should be for the window after.
1584         advanceElapsedClock(24 * MINUTE_IN_MILLIS);
1585         nextWindowStartTime += HOUR_IN_MILLIS;
1586         nextWindowEndTime += HOUR_IN_MILLIS;
1587         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1588         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1589         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1590 
1591         advanceElapsedClock(2 * HOUR_IN_MILLIS);
1592         // Say that the job ran at this point, possibly due to device idle.
1593         // The next window should be consistent (start and end at the time it would have had the job
1594         // run normally in previous windows).
1595         nextWindowStartTime += 2 * HOUR_IN_MILLIS;
1596         nextWindowEndTime += 2 * HOUR_IN_MILLIS;
1597 
1598         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1599         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1600         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1601     }
1602 
1603     @Test
testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod()1604     public void testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod() {
1605         JobStatus job = createJobStatus(
1606                 "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod",
1607                 createJobInfo().setPeriodic(7 * DAY_IN_MILLIS, 9 * HOUR_IN_MILLIS));
1608         JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
1609                 JobParameters.STOP_REASON_UNDEFINED,
1610                 JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
1611         // First window starts 6.625 days from now.
1612         advanceElapsedClock(6 * DAY_IN_MILLIS + 15 * HOUR_IN_MILLIS);
1613         long now = sElapsedRealtimeClock.millis();
1614         long nextWindowStartTime = now + 7 * DAY_IN_MILLIS;
1615         long nextWindowEndTime = nextWindowStartTime + 9 * HOUR_IN_MILLIS;
1616 
1617         advanceElapsedClock(6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS);
1618         // Say the job ran at the very end of its previous window. The intended JSS behavior is to
1619         // have consistent windows, so the new window should start as soon as the previous window
1620         // ended and end PERIOD time after the previous window ended.
1621         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1622         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1623         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1624 
1625         advanceElapsedClock(DAY_IN_MILLIS);
1626         // Say the job ran a day late. Since the period is massive compared to the flex, JSS should
1627         // put the rescheduled job in the original window.
1628         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1629         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1630         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1631 
1632         // 1 day before the start of the next window. Given the large period, respect the original
1633         // next window.
1634         advanceElapsedClock(nextWindowStartTime - sElapsedRealtimeClock.millis() - DAY_IN_MILLIS);
1635         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1636         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1637         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1638 
1639         // 1 hour before the start of the next window. It's too close to the next window, so the
1640         // returned job should be for the window after.
1641         long oneHourBeforeNextWindow =
1642                 nextWindowStartTime - sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS;
1643         long fiveMinsBeforeNextWindow =
1644                 nextWindowStartTime - sElapsedRealtimeClock.millis() - 5 * MINUTE_IN_MILLIS;
1645         advanceElapsedClock(oneHourBeforeNextWindow);
1646         nextWindowStartTime += 7 * DAY_IN_MILLIS;
1647         nextWindowEndTime += 7 * DAY_IN_MILLIS;
1648         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1649         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1650         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1651 
1652         // 5 minutes before the start of the next window. It's too close to the next window, so the
1653         // returned job should be for the window after.
1654         advanceElapsedClock(fiveMinsBeforeNextWindow);
1655         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1656         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1657         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1658 
1659         advanceElapsedClock(14 * DAY_IN_MILLIS);
1660         // Say that the job ran at this point, probably because the phone was off the entire time.
1661         // The next window should be consistent (start and end at the time it would have had the job
1662         // run normally in previous windows).
1663         nextWindowStartTime += 14 * DAY_IN_MILLIS;
1664         nextWindowEndTime += 14 * DAY_IN_MILLIS;
1665 
1666         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
1667         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1668         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1669 
1670         // Test original job again but with a huge delay from the original execution window
1671 
1672         // 1 day before the start of the next window. Given the large period, respect the original
1673         // next window.
1674         advanceElapsedClock(nextWindowStartTime - sElapsedRealtimeClock.millis() - DAY_IN_MILLIS);
1675         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1676         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1677         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1678 
1679         // 1 hour before the start of the next window. It's too close to the next window, so the
1680         // returned job should be for the window after.
1681         oneHourBeforeNextWindow =
1682                 nextWindowStartTime - sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS;
1683         fiveMinsBeforeNextWindow =
1684                 nextWindowStartTime - sElapsedRealtimeClock.millis() - 5 * MINUTE_IN_MILLIS;
1685         advanceElapsedClock(oneHourBeforeNextWindow);
1686         nextWindowStartTime += 7 * DAY_IN_MILLIS;
1687         nextWindowEndTime += 7 * DAY_IN_MILLIS;
1688         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1689         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1690         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1691 
1692         // 5 minutes before the start of the next window. It's too close to the next window, so the
1693         // returned job should be for the window after.
1694         advanceElapsedClock(fiveMinsBeforeNextWindow);
1695         rescheduledJob = mService.getRescheduleJobForPeriodic(job);
1696         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
1697         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
1698     }
1699 
1700     /** Tests that rare job batching works as expected. */
1701     @Test
testRareJobBatching()1702     public void testRareJobBatching() {
1703         spyOn(mService);
1704         doReturn(false).when(mService).evaluateControllerStatesLocked(any());
1705         doNothing().when(mService).noteJobsPending(any());
1706         doReturn(true).when(mService).isReadyToBeExecutedLocked(any(), anyBoolean());
1707         advanceElapsedClock(24 * HOUR_IN_MILLIS);
1708 
1709         JobSchedulerService.MaybeReadyJobQueueFunctor maybeQueueFunctor =
1710                 mService.new MaybeReadyJobQueueFunctor();
1711         mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT = 5;
1712         mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS = HOUR_IN_MILLIS;
1713 
1714         JobStatus job = createJobStatus(
1715                 "testRareJobBatching",
1716                 createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
1717         job.setStandbyBucket(RARE_INDEX);
1718 
1719         // Not enough RARE jobs to run.
1720         mService.getPendingJobQueue().clear();
1721         maybeQueueFunctor.reset();
1722         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
1723             maybeQueueFunctor.accept(job);
1724             assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
1725             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
1726             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
1727         }
1728         maybeQueueFunctor.postProcessLocked();
1729         assertEquals(0, mService.getPendingJobQueue().size());
1730 
1731         // Enough RARE jobs to run.
1732         mService.getPendingJobQueue().clear();
1733         maybeQueueFunctor.reset();
1734         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT; ++i) {
1735             maybeQueueFunctor.accept(job);
1736             assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
1737             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
1738             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
1739         }
1740         maybeQueueFunctor.postProcessLocked();
1741         assertEquals(5, mService.getPendingJobQueue().size());
1742 
1743         // Not enough RARE jobs to run, but a non-batched job saves the day.
1744         mService.getPendingJobQueue().clear();
1745         maybeQueueFunctor.reset();
1746         JobStatus activeJob = createJobStatus(
1747                 "testRareJobBatching",
1748                 createJobInfo().setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY));
1749         activeJob.setStandbyBucket(ACTIVE_INDEX);
1750         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
1751             maybeQueueFunctor.accept(job);
1752             assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
1753             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
1754             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
1755         }
1756         maybeQueueFunctor.accept(activeJob);
1757         maybeQueueFunctor.postProcessLocked();
1758         assertEquals(3, mService.getPendingJobQueue().size());
1759 
1760         // Not enough RARE jobs to run, but an old RARE job saves the day.
1761         mService.getPendingJobQueue().clear();
1762         maybeQueueFunctor.reset();
1763         JobStatus oldRareJob = createJobStatus("testRareJobBatching", createJobInfo());
1764         oldRareJob.setStandbyBucket(RARE_INDEX);
1765         final long oldBatchTime = sElapsedRealtimeClock.millis()
1766                 - 2 * mService.mConstants.MAX_NON_ACTIVE_JOB_BATCH_DELAY_MS;
1767         oldRareJob.setFirstForceBatchedTimeElapsed(oldBatchTime);
1768         for (int i = 0; i < mService.mConstants.MIN_READY_NON_ACTIVE_JOBS_COUNT / 2; ++i) {
1769             maybeQueueFunctor.accept(job);
1770             assertEquals(i + 1, maybeQueueFunctor.forceBatchedCount);
1771             assertEquals(i + 1, maybeQueueFunctor.runnableJobs.size());
1772             assertEquals(sElapsedRealtimeClock.millis(), job.getFirstForceBatchedTimeElapsed());
1773         }
1774         maybeQueueFunctor.accept(oldRareJob);
1775         assertEquals(oldBatchTime, oldRareJob.getFirstForceBatchedTimeElapsed());
1776         maybeQueueFunctor.postProcessLocked();
1777         assertEquals(3, mService.getPendingJobQueue().size());
1778     }
1779 
1780     /** Tests that jobs scheduled by the app itself are counted towards scheduling limits. */
1781     @Test
testScheduleLimiting_RegularSchedule_Blocked()1782     public void testScheduleLimiting_RegularSchedule_Blocked() {
1783         mService.mConstants.ENABLE_API_QUOTAS = true;
1784         mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
1785         mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
1786         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
1787         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
1788         mService.updateQuotaTracker();
1789         mService.resetScheduleQuota();
1790 
1791         final JobInfo job = createJobInfo().setPersisted(true).build();
1792         for (int i = 0; i < 500; ++i) {
1793             final int expected =
1794                     i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
1795             assertEquals("Got unexpected result for schedule #" + (i + 1),
1796                     expected,
1797                     mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", ""));
1798         }
1799     }
1800 
1801     /**
1802      * Tests that jobs scheduled by the app itself succeed even if the app is above the scheduling
1803      * limit.
1804      */
1805     @Test
1806     public void testScheduleLimiting_RegularSchedule_Allowed() {
1807         mService.mConstants.ENABLE_API_QUOTAS = true;
1808         mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
1809         mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
1810         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
1811         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
1812         mService.updateQuotaTracker();
1813         mService.resetScheduleQuota();
1814 
1815         final JobInfo job = createJobInfo().setPersisted(true).build();
1816         for (int i = 0; i < 500; ++i) {
1817             assertEquals("Got unexpected result for schedule #" + (i + 1),
1818                     JobScheduler.RESULT_SUCCESS,
1819                     mService.scheduleAsPackage(job, null, TEST_UID, null, 0, "JSSTest", ""));
1820         }
1821     }
1822 
1823     /**
1824      * Tests that jobs scheduled through a proxy (eg. system server) don't count towards scheduling
1825      * limits.
1826      */
1827     @Test
1828     public void testScheduleLimiting_Proxy() {
1829         mService.mConstants.ENABLE_API_QUOTAS = true;
1830         mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
1831         mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
1832         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
1833         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
1834         mService.updateQuotaTracker();
1835         mService.resetScheduleQuota();
1836 
1837         final JobInfo job = createJobInfo().setPersisted(true).build();
1838         for (int i = 0; i < 500; ++i) {
1839             assertEquals("Got unexpected result for schedule #" + (i + 1),
1840                     JobScheduler.RESULT_SUCCESS,
1841                     mService.scheduleAsPackage(job, null, TEST_UID, "proxied.package", 0, "JSSTest",
1842                             ""));
1843         }
1844     }
1845 
1846     /**
1847      * Tests that jobs scheduled by an app for itself as if through a proxy are counted towards
1848      * scheduling limits.
1849      */
1850     @Test
1851     public void testScheduleLimiting_SelfProxy() {
1852         mService.mConstants.ENABLE_API_QUOTAS = true;
1853         mService.mConstants.API_QUOTA_SCHEDULE_COUNT = 300;
1854         mService.mConstants.API_QUOTA_SCHEDULE_WINDOW_MS = 300000;
1855         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
1856         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = true;
1857         mService.updateQuotaTracker();
1858         mService.resetScheduleQuota();
1859 
1860         final JobInfo job = createJobInfo().setPersisted(true).build();
1861         for (int i = 0; i < 500; ++i) {
1862             final int expected =
1863                     i < 300 ? JobScheduler.RESULT_SUCCESS : JobScheduler.RESULT_FAILURE;
1864             assertEquals("Got unexpected result for schedule #" + (i + 1),
1865                     expected,
1866                     mService.scheduleAsPackage(job, null, TEST_UID,
1867                             job.getService().getPackageName(),
1868                             0, "JSSTest", ""));
1869         }
1870     }
1871 
1872     /**
1873      * Tests that the number of persisted JobWorkItems is capped.
1874      */
1875     @Test
1876     public void testScheduleLimiting_JobWorkItems_Nonpersisted() {
1877         mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500;
1878         mService.mConstants.ENABLE_API_QUOTAS = false;
1879         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
1880         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
1881         mService.updateQuotaTracker();
1882         mService.resetScheduleQuota();
1883 
1884         final JobInfo job = createJobInfo().setPersisted(false).build();
1885         final JobWorkItem item = new JobWorkItem.Builder().build();
1886         for (int i = 0; i < 1000; ++i) {
1887             assertEquals("Got unexpected result for schedule #" + (i + 1),
1888                     JobScheduler.RESULT_SUCCESS,
1889                     mService.scheduleAsPackage(job, item, TEST_UID,
1890                             job.getService().getPackageName(),
1891                             0, "JSSTest", ""));
1892         }
1893     }
1894 
1895     /**
1896      * Tests that the number of persisted JobWorkItems is capped.
1897      */
1898     @Test
1899     public void testScheduleLimiting_JobWorkItems_Persisted() {
1900         mService.mConstants.MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 500;
1901         mService.mConstants.ENABLE_API_QUOTAS = false;
1902         mService.mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION = false;
1903         mService.mConstants.API_QUOTA_SCHEDULE_RETURN_FAILURE_RESULT = false;
1904         mService.updateQuotaTracker();
1905         mService.resetScheduleQuota();
1906 
1907         final JobInfo job = createJobInfo().setPersisted(true).build();
1908         final JobWorkItem item = new JobWorkItem.Builder().build();
1909         for (int i = 0; i < 500; ++i) {
1910             assertEquals("Got unexpected result for schedule #" + (i + 1),
1911                     JobScheduler.RESULT_SUCCESS,
1912                     mService.scheduleAsPackage(job, item, TEST_UID,
1913                             job.getService().getPackageName(),
1914                             0, "JSSTest", ""));
1915         }
1916         try {
1917             mService.scheduleAsPackage(job, item, TEST_UID, job.getService().getPackageName(),
1918                     0, "JSSTest", "");
1919             fail("Added more items than allowed");
1920         } catch (IllegalStateException expected) {
1921             // Success
1922         }
1923     }
1924 
1925     /** Tests that jobs are removed from the pending list if the user stops the app. */
1926     @Test
1927     public void testUserStopRemovesPending() {
1928         spyOn(mService);
1929 
1930         JobStatus job1a = createJobStatus("testUserStopRemovesPending",
1931                 createJobInfo(1), 1, "pkg1");
1932         JobStatus job1b = createJobStatus("testUserStopRemovesPending",
1933                 createJobInfo(2), 1, "pkg1");
1934         JobStatus job2a = createJobStatus("testUserStopRemovesPending",
1935                 createJobInfo(1), 2, "pkg2");
1936         JobStatus job2b = createJobStatus("testUserStopRemovesPending",
1937                 createJobInfo(2), 2, "pkg2");
1938         doReturn(1).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 0);
1939         doReturn(11).when(mPackageManagerInternal).getPackageUid("pkg1", 0, 1);
1940         doReturn(2).when(mPackageManagerInternal).getPackageUid("pkg2", 0, 0);
1941 
1942         mService.getPendingJobQueue().clear();
1943         mService.getPendingJobQueue().add(job1a);
1944         mService.getPendingJobQueue().add(job1b);
1945         mService.getPendingJobQueue().add(job2a);
1946         mService.getPendingJobQueue().add(job2b);
1947         mService.getJobStore().add(job1a);
1948         mService.getJobStore().add(job1b);
1949         mService.getJobStore().add(job2a);
1950         mService.getJobStore().add(job2b);
1951 
1952         mService.notePendingUserRequestedAppStopInternal("pkg1", 1, "test");
1953         assertEquals(4, mService.getPendingJobQueue().size());
1954         assertTrue(mService.getPendingJobQueue().contains(job1a));
1955         assertTrue(mService.getPendingJobQueue().contains(job1b));
1956         assertTrue(mService.getPendingJobQueue().contains(job2a));
1957         assertTrue(mService.getPendingJobQueue().contains(job2b));
1958 
1959         mService.notePendingUserRequestedAppStopInternal("pkg1", 0, "test");
1960         assertEquals(2, mService.getPendingJobQueue().size());
1961         assertFalse(mService.getPendingJobQueue().contains(job1a));
1962         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1a));
1963         assertFalse(mService.getPendingJobQueue().contains(job1b));
1964         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job1b));
1965         assertTrue(mService.getPendingJobQueue().contains(job2a));
1966         assertTrue(mService.getPendingJobQueue().contains(job2b));
1967 
1968         mService.notePendingUserRequestedAppStopInternal("pkg2", 0, "test");
1969         assertEquals(0, mService.getPendingJobQueue().size());
1970         assertFalse(mService.getPendingJobQueue().contains(job1a));
1971         assertFalse(mService.getPendingJobQueue().contains(job1b));
1972         assertFalse(mService.getPendingJobQueue().contains(job2a));
1973         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2a));
1974         assertFalse(mService.getPendingJobQueue().contains(job2b));
1975         assertEquals(JobScheduler.PENDING_JOB_REASON_USER, mService.getPendingJobReason(job2b));
1976     }
1977 }
1978