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