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