• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.controllers;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
27 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
28 import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX;
29 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
30 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
31 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
32 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
33 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
34 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
35 import static com.android.server.job.JobSchedulerService.sSystemClock;
36 
37 import static org.junit.Assert.assertEquals;
38 import static org.junit.Assert.assertFalse;
39 import static org.junit.Assert.assertNotEquals;
40 import static org.junit.Assert.assertNotNull;
41 import static org.junit.Assert.assertNull;
42 import static org.junit.Assert.assertTrue;
43 import static org.junit.Assert.fail;
44 import static org.mockito.ArgumentMatchers.any;
45 import static org.mockito.ArgumentMatchers.anyInt;
46 import static org.mockito.ArgumentMatchers.anyLong;
47 import static org.mockito.ArgumentMatchers.anyString;
48 import static org.mockito.ArgumentMatchers.argThat;
49 import static org.mockito.Mockito.atLeast;
50 import static org.mockito.Mockito.eq;
51 import static org.mockito.Mockito.never;
52 import static org.mockito.Mockito.timeout;
53 import static org.mockito.Mockito.times;
54 import static org.mockito.Mockito.verify;
55 
56 import android.Manifest;
57 import android.app.ActivityManager;
58 import android.app.ActivityManagerInternal;
59 import android.app.AlarmManager;
60 import android.app.AppGlobals;
61 import android.app.IActivityManager;
62 import android.app.IUidObserver;
63 import android.app.job.JobInfo;
64 import android.app.usage.UsageEvents;
65 import android.app.usage.UsageStatsManager;
66 import android.app.usage.UsageStatsManagerInternal;
67 import android.content.ComponentName;
68 import android.content.Context;
69 import android.content.pm.ApplicationInfo;
70 import android.content.pm.PackageInfo;
71 import android.content.pm.PackageManager;
72 import android.content.pm.PackageManagerInternal;
73 import android.content.pm.ServiceInfo;
74 import android.os.BatteryManagerInternal;
75 import android.os.Handler;
76 import android.os.Looper;
77 import android.os.RemoteException;
78 import android.os.SystemClock;
79 import android.platform.test.annotations.LargeTest;
80 import android.provider.DeviceConfig;
81 import android.util.ArraySet;
82 import android.util.SparseBooleanArray;
83 
84 import androidx.test.runner.AndroidJUnit4;
85 
86 import com.android.internal.util.ArrayUtils;
87 import com.android.server.LocalServices;
88 import com.android.server.PowerAllowlistInternal;
89 import com.android.server.job.JobSchedulerService;
90 import com.android.server.job.JobStore;
91 import com.android.server.job.controllers.QuotaController.ExecutionStats;
92 import com.android.server.job.controllers.QuotaController.QcConstants;
93 import com.android.server.job.controllers.QuotaController.QuotaBump;
94 import com.android.server.job.controllers.QuotaController.ShrinkableDebits;
95 import com.android.server.job.controllers.QuotaController.TimedEvent;
96 import com.android.server.job.controllers.QuotaController.TimingSession;
97 import com.android.server.usage.AppStandbyInternal;
98 
99 import org.junit.After;
100 import org.junit.Before;
101 import org.junit.Test;
102 import org.junit.runner.RunWith;
103 import org.mockito.ArgumentCaptor;
104 import org.mockito.ArgumentMatchers;
105 import org.mockito.InOrder;
106 import org.mockito.Mock;
107 import org.mockito.MockitoSession;
108 import org.mockito.quality.Strictness;
109 import org.mockito.stubbing.Answer;
110 
111 import java.time.Clock;
112 import java.time.Duration;
113 import java.time.ZoneOffset;
114 import java.util.ArrayList;
115 import java.util.List;
116 import java.util.concurrent.Executor;
117 
118 @RunWith(AndroidJUnit4.class)
119 public class QuotaControllerTest {
120     private static final long SECOND_IN_MILLIS = 1000L;
121     private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
122     private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
123     private static final String TAG_CLEANUP = "*job.cleanup*";
124     private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
125     private static final int CALLING_UID = 1000;
126     private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
127     private static final int SOURCE_USER_ID = 0;
128 
129     private QuotaController mQuotaController;
130     private QuotaController.QcConstants mQcConstants;
131     private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants();
132     private int mSourceUid;
133     private AppStandbyInternal.AppIdleStateChangeListener mAppIdleStateChangeListener;
134     private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener;
135     private IUidObserver mUidObserver;
136     private UsageStatsManagerInternal.UsageEventListener mUsageEventListener;
137     DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder;
138 
139     private MockitoSession mMockingSession;
140     @Mock
141     private ActivityManagerInternal mActivityMangerInternal;
142     @Mock
143     private AlarmManager mAlarmManager;
144     @Mock
145     private Context mContext;
146     @Mock
147     private JobSchedulerService mJobSchedulerService;
148     @Mock
149     private PackageManager mPackageManager;
150     @Mock
151     private PackageManagerInternal mPackageManagerInternal;
152     @Mock
153     private PowerAllowlistInternal mPowerAllowlistInternal;
154     @Mock
155     private UsageStatsManagerInternal mUsageStatsManager;
156 
157     private JobStore mJobStore;
158 
159     @Before
setUp()160     public void setUp() {
161         mMockingSession = mockitoSession()
162                 .initMocks(this)
163                 .strictness(Strictness.LENIENT)
164                 .spyStatic(DeviceConfig.class)
165                 .mockStatic(LocalServices.class)
166                 .startMocking();
167 
168         // Called in StateController constructor.
169         when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
170         when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
171         when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
172         // Called in QuotaController constructor.
173         IActivityManager activityManager = ActivityManager.getService();
174         spyOn(activityManager);
175         try {
176             doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
177         } catch (RemoteException e) {
178             fail("registerUidObserver threw exception: " + e.getMessage());
179         }
180         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
181         when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
182         doReturn(mActivityMangerInternal)
183                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
184         final AppStandbyInternal appStandbyInternal = mock(AppStandbyInternal.class);
185         doReturn(appStandbyInternal)
186                 .when(() -> LocalServices.getService(AppStandbyInternal.class));
187         doReturn(mock(BatteryManagerInternal.class))
188                 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
189         doReturn(mUsageStatsManager)
190                 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
191         doReturn(mPowerAllowlistInternal)
192                 .when(() -> LocalServices.getService(PowerAllowlistInternal.class));
193         // Used in JobStatus.
194         doReturn(mPackageManagerInternal)
195                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
196         // Used in QuotaController.Handler.
197         mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
198         when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
199         // Used in QuotaController.QcConstants
200         doAnswer((Answer<Void>) invocationOnMock -> null)
201                 .when(() -> DeviceConfig.addOnPropertiesChangedListener(
202                         anyString(), any(Executor.class),
203                         any(DeviceConfig.OnPropertiesChangedListener.class)));
204         mDeviceConfigPropertiesBuilder =
205                 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
206         doAnswer(
207                 (Answer<DeviceConfig.Properties>) invocationOnMock
208                         -> mDeviceConfigPropertiesBuilder.build())
209                 .when(() -> DeviceConfig.getProperties(
210                         eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any()));
211         // Used in QuotaController.onSystemServicesReady
212         when(mContext.getPackageManager()).thenReturn(mPackageManager);
213 
214         // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
215         // in the past, and QuotaController sometimes floors values at 0, so if the test time
216         // causes sessions with negative timestamps, they will fail.
217         JobSchedulerService.sSystemClock =
218                 getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
219                         24 * HOUR_IN_MILLIS);
220         JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
221                 Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC),
222                 24 * HOUR_IN_MILLIS);
223         JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
224                 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
225                 24 * HOUR_IN_MILLIS);
226 
227         // Initialize real objects.
228         // Capture the listeners.
229         ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> aiscListenerCaptor =
230                 ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class);
231         ArgumentCaptor<IUidObserver> uidObserverCaptor =
232                 ArgumentCaptor.forClass(IUidObserver.class);
233         ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor =
234                 ArgumentCaptor.forClass(PowerAllowlistInternal.TempAllowlistChangeListener.class);
235         ArgumentCaptor<UsageStatsManagerInternal.UsageEventListener> ueListenerCaptor =
236                 ArgumentCaptor.forClass(UsageStatsManagerInternal.UsageEventListener.class);
237         mQuotaController = new QuotaController(mJobSchedulerService,
238                 mock(BackgroundJobsController.class), mock(ConnectivityController.class));
239 
240         verify(appStandbyInternal).addListener(aiscListenerCaptor.capture());
241         mAppIdleStateChangeListener = aiscListenerCaptor.getValue();
242         verify(mPowerAllowlistInternal)
243                 .registerTempAllowlistChangeListener(taChangeCaptor.capture());
244         mTempAllowlistListener = taChangeCaptor.getValue();
245         verify(mUsageStatsManager).registerListener(ueListenerCaptor.capture());
246         mUsageEventListener = ueListenerCaptor.getValue();
247         try {
248             verify(activityManager).registerUidObserver(
249                     uidObserverCaptor.capture(),
250                     eq(ActivityManager.UID_OBSERVER_PROCSTATE),
251                     eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE),
252                     any());
253             mUidObserver = uidObserverCaptor.getValue();
254             mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
255             // Need to do this since we're using a mock JS and not a real object.
256             doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE}))
257                     .when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid);
258         } catch (RemoteException e) {
259             fail(e.getMessage());
260         }
261         mQcConstants = mQuotaController.getQcConstants();
262     }
263 
264     @After
tearDown()265     public void tearDown() {
266         if (mMockingSession != null) {
267             mMockingSession.finishMocking();
268         }
269     }
270 
getAdvancedClock(Clock clock, long incrementMs)271     private Clock getAdvancedClock(Clock clock, long incrementMs) {
272         return Clock.offset(clock, Duration.ofMillis(incrementMs));
273     }
274 
advanceElapsedClock(long incrementMs)275     private void advanceElapsedClock(long incrementMs) {
276         JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
277                 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
278     }
279 
setCharging()280     private void setCharging() {
281         doReturn(true).when(mJobSchedulerService).isBatteryCharging();
282         synchronized (mQuotaController.mLock) {
283             mQuotaController.onBatteryStateChangedLocked();
284         }
285     }
286 
setDischarging()287     private void setDischarging() {
288         doReturn(false).when(mJobSchedulerService).isBatteryCharging();
289         synchronized (mQuotaController.mLock) {
290             mQuotaController.onBatteryStateChangedLocked();
291         }
292     }
293 
setProcessState(int procState)294     private void setProcessState(int procState) {
295         setProcessState(procState, mSourceUid);
296     }
297 
setProcessState(int procState, int uid)298     private void setProcessState(int procState, int uid) {
299         try {
300             doReturn(procState).when(mActivityMangerInternal).getUidProcessState(uid);
301             SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
302             spyOn(foregroundUids);
303             final boolean contained = foregroundUids.get(uid);
304             mUidObserver.onUidStateChanged(uid, procState, 0,
305                     ActivityManager.PROCESS_CAPABILITY_NONE);
306             if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
307                 if (!contained) {
308                     verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
309                             .put(eq(uid), eq(true));
310                 }
311                 assertTrue(foregroundUids.get(uid));
312             } else {
313                 if (contained) {
314                     verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
315                             .delete(eq(uid));
316                 }
317                 assertFalse(foregroundUids.get(uid));
318             }
319             waitForNonDelayedMessagesProcessed();
320         } catch (Exception e) {
321             fail("exception encountered: " + e.getMessage());
322         }
323     }
324 
bucketIndexToUsageStatsBucket(int bucketIndex)325     private int bucketIndexToUsageStatsBucket(int bucketIndex) {
326         switch (bucketIndex) {
327             case EXEMPTED_INDEX:
328                 return UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
329             case ACTIVE_INDEX:
330                 return UsageStatsManager.STANDBY_BUCKET_ACTIVE;
331             case WORKING_INDEX:
332                 return UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
333             case FREQUENT_INDEX:
334                 return UsageStatsManager.STANDBY_BUCKET_FREQUENT;
335             case RARE_INDEX:
336                 return UsageStatsManager.STANDBY_BUCKET_RARE;
337             case RESTRICTED_INDEX:
338                 return UsageStatsManager.STANDBY_BUCKET_RESTRICTED;
339             default:
340                 return UsageStatsManager.STANDBY_BUCKET_NEVER;
341         }
342     }
343 
setStandbyBucket(int bucketIndex)344     private void setStandbyBucket(int bucketIndex) {
345         when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
346                 anyLong())).thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
347         mQuotaController.updateStandbyBucket(SOURCE_USER_ID, SOURCE_PACKAGE, bucketIndex);
348     }
349 
setStandbyBucket(int bucketIndex, JobStatus... jobs)350     private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
351         setStandbyBucket(bucketIndex);
352         for (JobStatus job : jobs) {
353             job.setStandbyBucket(bucketIndex);
354             when(mUsageStatsManager.getAppStandbyBucket(
355                     eq(job.getSourcePackageName()), eq(job.getSourceUserId()), anyLong()))
356                     .thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
357         }
358     }
359 
trackJobs(JobStatus... jobs)360     private void trackJobs(JobStatus... jobs) {
361         for (JobStatus job : jobs) {
362             mJobStore.add(job);
363             synchronized (mQuotaController.mLock) {
364                 mQuotaController.maybeStartTrackingJobLocked(job, null);
365             }
366         }
367     }
368 
createJobStatus(String testTag, int jobId)369     private JobStatus createJobStatus(String testTag, int jobId) {
370         JobInfo jobInfo = new JobInfo.Builder(jobId,
371                 new ComponentName(mContext, "TestQuotaJobService"))
372                 .build();
373         return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
374     }
375 
createExpeditedJobStatus(String testTag, int jobId)376     private JobStatus createExpeditedJobStatus(String testTag, int jobId) {
377         JobInfo jobInfo = new JobInfo.Builder(jobId,
378                 new ComponentName(mContext, "TestQuotaExpeditedJobService"))
379                 .setExpedited(true)
380                 .build();
381         return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
382     }
383 
createJobStatus(String testTag, String packageName, int callingUid, JobInfo jobInfo)384     private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
385             JobInfo jobInfo) {
386         JobStatus js = JobStatus.createFromJobInfo(
387                 jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
388         js.serviceInfo = mock(ServiceInfo.class);
389         // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
390         js.setStandbyBucket(FREQUENT_INDEX);
391         // Make sure Doze and background-not-restricted don't affect tests.
392         js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(),
393                 /* state */ true, /* allowlisted */false);
394         js.setBackgroundNotRestrictedConstraintSatisfied(
395                 sElapsedRealtimeClock.millis(), true, false);
396         js.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), true);
397         js.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), true);
398         return js;
399     }
400 
createTimingSession(long start, long duration, int count)401     private TimingSession createTimingSession(long start, long duration, int count) {
402         return new TimingSession(start, start + duration, count);
403     }
404 
setDeviceConfigLong(String key, long val)405     private void setDeviceConfigLong(String key, long val) {
406         mDeviceConfigPropertiesBuilder.setLong(key, val);
407         synchronized (mQuotaController.mLock) {
408             mQuotaController.prepareForUpdatedConstantsLocked();
409             mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
410         }
411     }
412 
setDeviceConfigInt(String key, int val)413     private void setDeviceConfigInt(String key, int val) {
414         mDeviceConfigPropertiesBuilder.setInt(key, val);
415         synchronized (mQuotaController.mLock) {
416             mQuotaController.prepareForUpdatedConstantsLocked();
417             mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key);
418         }
419     }
420 
waitForNonDelayedMessagesProcessed()421     private void waitForNonDelayedMessagesProcessed() {
422         mQuotaController.getHandler().runWithScissors(() -> {}, 15_000);
423     }
424 
425     @Test
testSaveTimingSession()426     public void testSaveTimingSession() {
427         assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
428 
429         List<TimingSession> expectedRegular = new ArrayList<>();
430         List<TimingSession> expectedEJ = new ArrayList<>();
431         TimingSession one = new TimingSession(1, 10, 1);
432         TimingSession two = new TimingSession(11, 20, 2);
433         TimingSession thr = new TimingSession(21, 30, 3);
434         TimingSession fou = new TimingSession(31, 40, 4);
435 
436         mQuotaController.saveTimingSession(0, "com.android.test", one, false);
437         expectedRegular.add(one);
438         assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test"));
439         assertTrue(
440                 ArrayUtils.isEmpty(mQuotaController.getEJTimingSessions(0, "com.android.test")));
441 
442         mQuotaController.saveTimingSession(0, "com.android.test", two, false);
443         expectedRegular.add(two);
444         assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test"));
445         assertTrue(
446                 ArrayUtils.isEmpty(mQuotaController.getEJTimingSessions(0, "com.android.test")));
447 
448         mQuotaController.saveTimingSession(0, "com.android.test", thr, true);
449         expectedEJ.add(thr);
450         assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test"));
451         assertEquals(expectedEJ, mQuotaController.getEJTimingSessions(0, "com.android.test"));
452 
453         mQuotaController.saveTimingSession(0, "com.android.test", fou, false);
454         mQuotaController.saveTimingSession(0, "com.android.test", fou, true);
455         expectedRegular.add(fou);
456         expectedEJ.add(fou);
457         assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test"));
458         assertEquals(expectedEJ, mQuotaController.getEJTimingSessions(0, "com.android.test"));
459     }
460 
461     @Test
testDeleteObsoleteSessionsLocked()462     public void testDeleteObsoleteSessionsLocked() {
463         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
464         TimingSession one = createTimingSession(
465                 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
466         TimingSession two = createTimingSession(
467                 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
468         QuotaBump bump1 = new QuotaBump(now - 2 * HOUR_IN_MILLIS);
469         TimingSession thr = createTimingSession(
470                 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
471         // Overlaps 24 hour boundary.
472         TimingSession fou = createTimingSession(
473                 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
474         // Way past the 24 hour boundary.
475         QuotaBump bump2 = new QuotaBump(now - 24 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
476         TimingSession fiv = createTimingSession(
477                 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
478         List<TimedEvent> expectedRegular = new ArrayList<>();
479         List<TimedEvent> expectedEJ = new ArrayList<>();
480         // Added in correct (chronological) order.
481         expectedRegular.add(fou);
482         expectedRegular.add(thr);
483         expectedRegular.add(bump1);
484         expectedRegular.add(two);
485         expectedRegular.add(one);
486         expectedEJ.add(fou);
487         expectedEJ.add(one);
488         mQuotaController.saveTimingSession(0, "com.android.test", fiv, false);
489         mQuotaController.getTimingSessions(0, "com.android.test").add(bump2);
490         mQuotaController.saveTimingSession(0, "com.android.test", fou, false);
491         mQuotaController.saveTimingSession(0, "com.android.test", thr, false);
492         mQuotaController.getTimingSessions(0, "com.android.test").add(bump1);
493         mQuotaController.saveTimingSession(0, "com.android.test", two, false);
494         mQuotaController.saveTimingSession(0, "com.android.test", one, false);
495         mQuotaController.saveTimingSession(0, "com.android.test", fiv, true);
496         mQuotaController.saveTimingSession(0, "com.android.test", fou, true);
497         mQuotaController.saveTimingSession(0, "com.android.test", one, true);
498 
499         synchronized (mQuotaController.mLock) {
500             mQuotaController.deleteObsoleteSessionsLocked();
501         }
502 
503         assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test"));
504         assertEquals(expectedEJ, mQuotaController.getEJTimingSessions(0, "com.android.test"));
505     }
506 
507     @Test
testOnAppRemovedLocked()508     public void testOnAppRemovedLocked() {
509         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
510         mQuotaController.saveTimingSession(0, "com.android.test.remove",
511                 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
512         mQuotaController.saveTimingSession(0, "com.android.test.remove",
513                 createTimingSession(
514                         now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5),
515                 false);
516         mQuotaController.saveTimingSession(0, "com.android.test.remove",
517                 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false);
518         mQuotaController.saveTimingSession(0, "com.android.test.remove",
519                 createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), true);
520         mQuotaController.saveTimingSession(0, "com.android.test.remove",
521                 createTimingSession(now - (15 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), true);
522         // Test that another app isn't affected.
523         TimingSession one = createTimingSession(
524                 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
525         TimingSession two = createTimingSession(
526                 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
527         List<TimingSession> expected = new ArrayList<>();
528         // Added in correct (chronological) order.
529         expected.add(two);
530         expected.add(one);
531         mQuotaController.saveTimingSession(0, "com.android.test.stay", two, false);
532         mQuotaController.saveTimingSession(0, "com.android.test.stay", one, false);
533         mQuotaController.saveTimingSession(0, "com.android.test.stay", one, true);
534 
535         assertNotNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
536         assertNotNull(mQuotaController.getEJTimingSessions(0, "com.android.test.remove"));
537         assertNotNull(mQuotaController.getTimingSessions(0, "com.android.test.stay"));
538         assertNotNull(mQuotaController.getEJTimingSessions(0, "com.android.test.stay"));
539 
540         ExecutionStats expectedStats = new ExecutionStats();
541         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
542         expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
543         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
544         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
545         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
546 
547         final int uid = 10001;
548         synchronized (mQuotaController.mLock) {
549             mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
550         }
551         assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
552         assertNull(mQuotaController.getEJTimingSessions(0, "com.android.test.remove"));
553         assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
554         synchronized (mQuotaController.mLock) {
555             assertEquals(expectedStats,
556                     mQuotaController.getExecutionStatsLocked(
557                             0, "com.android.test.remove", RARE_INDEX));
558             assertNotEquals(expectedStats,
559                     mQuotaController.getExecutionStatsLocked(
560                             0, "com.android.test.stay", RARE_INDEX));
561 
562             assertFalse(mQuotaController.getForegroundUids().get(uid));
563         }
564     }
565 
566     @Test
testOnUserRemovedLocked()567     public void testOnUserRemovedLocked() {
568         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
569         mQuotaController.saveTimingSession(0, "com.android.test",
570                 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
571         mQuotaController.saveTimingSession(0, "com.android.test",
572                 createTimingSession(
573                         now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5),
574                 false);
575         mQuotaController.saveTimingSession(0, "com.android.test",
576                 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false);
577         mQuotaController.saveTimingSession(0, "com.android.test",
578                 createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), true);
579         mQuotaController.saveTimingSession(0, "com.android.test",
580                 createTimingSession(now - (15 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), true);
581         // Test that another user isn't affected.
582         TimingSession one = createTimingSession(
583                 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
584         TimingSession two = createTimingSession(
585                 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
586         List<TimingSession> expectedRegular = new ArrayList<>();
587         List<TimingSession> expectedEJ = new ArrayList<>();
588         // Added in correct (chronological) order.
589         expectedRegular.add(two);
590         expectedRegular.add(one);
591         expectedEJ.add(one);
592         mQuotaController.saveTimingSession(10, "com.android.test", two, false);
593         mQuotaController.saveTimingSession(10, "com.android.test", one, false);
594         mQuotaController.saveTimingSession(10, "com.android.test", one, true);
595 
596         assertNotNull(mQuotaController.getTimingSessions(0, "com.android.test"));
597         assertNotNull(mQuotaController.getEJTimingSessions(0, "com.android.test"));
598         assertNotNull(mQuotaController.getTimingSessions(10, "com.android.test"));
599         assertNotNull(mQuotaController.getEJTimingSessions(10, "com.android.test"));
600 
601         ExecutionStats expectedStats = new ExecutionStats();
602         expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
603         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
604         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
605         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
606         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
607 
608         synchronized (mQuotaController.mLock) {
609             mQuotaController.onUserRemovedLocked(0);
610             assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
611             assertNull(mQuotaController.getEJTimingSessions(0, "com.android.test"));
612             assertEquals(expectedRegular,
613                     mQuotaController.getTimingSessions(10, "com.android.test"));
614             assertEquals(expectedEJ,
615                     mQuotaController.getEJTimingSessions(10, "com.android.test"));
616             assertEquals(expectedStats,
617                     mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
618             assertNotEquals(expectedStats,
619                     mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX));
620         }
621     }
622 
623     @Test
testUpdateExecutionStatsLocked_NoTimer()624     public void testUpdateExecutionStatsLocked_NoTimer() {
625         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
626         // Added in chronological order.
627         mQuotaController.saveTimingSession(0, "com.android.test",
628                 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
629         mQuotaController.saveTimingSession(0, "com.android.test",
630                 createTimingSession(
631                         now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5),
632                 false);
633         mQuotaController.saveTimingSession(0, "com.android.test",
634                 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false);
635         mQuotaController.saveTimingSession(0, "com.android.test",
636                 createTimingSession(
637                         now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1),
638                 false);
639         mQuotaController.saveTimingSession(0, "com.android.test",
640                 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3), false);
641 
642         // Test an app that hasn't had any activity.
643         ExecutionStats expectedStats = new ExecutionStats();
644         ExecutionStats inputStats = new ExecutionStats();
645 
646         inputStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
647         inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
648         inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
649         inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
650         // Invalid time is now +24 hours since there are no sessions at all for the app.
651         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
652         expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
653         synchronized (mQuotaController.mLock) {
654             mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
655         }
656         assertEquals(expectedStats, inputStats);
657 
658         inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
659         // Invalid time is now +18 hours since there are no sessions in the window but the earliest
660         // session is 6 hours ago.
661         expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS;
662         expectedStats.executionTimeInWindowMs = 0;
663         expectedStats.bgJobCountInWindow = 0;
664         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
665         expectedStats.bgJobCountInMaxPeriod = 15;
666         expectedStats.sessionCountInWindow = 0;
667         synchronized (mQuotaController.mLock) {
668             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
669         }
670         assertEquals(expectedStats, inputStats);
671 
672         inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
673         // Invalid time is now since the session straddles the window cutoff time.
674         expectedStats.expirationTimeElapsed = now;
675         expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
676         expectedStats.bgJobCountInWindow = 3;
677         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
678         expectedStats.bgJobCountInMaxPeriod = 15;
679         expectedStats.sessionCountInWindow = 1;
680         synchronized (mQuotaController.mLock) {
681             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
682         }
683         assertEquals(expectedStats, inputStats);
684 
685         inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
686         // Invalid time is now since the start of the session is at the very edge of the window
687         // cutoff time.
688         expectedStats.expirationTimeElapsed = now;
689         expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
690         expectedStats.bgJobCountInWindow = 3;
691         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
692         expectedStats.bgJobCountInMaxPeriod = 15;
693         expectedStats.sessionCountInWindow = 1;
694         synchronized (mQuotaController.mLock) {
695             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
696         }
697         assertEquals(expectedStats, inputStats);
698 
699         inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
700         // Invalid time is now +44 minutes since the earliest session in the window is now-5
701         // minutes.
702         expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
703         expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
704         expectedStats.bgJobCountInWindow = 3;
705         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
706         expectedStats.bgJobCountInMaxPeriod = 15;
707         expectedStats.sessionCountInWindow = 1;
708         synchronized (mQuotaController.mLock) {
709             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
710         }
711         assertEquals(expectedStats, inputStats);
712 
713         inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
714         // Invalid time is now since the session is at the very edge of the window cutoff time.
715         expectedStats.expirationTimeElapsed = now;
716         expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
717         expectedStats.bgJobCountInWindow = 4;
718         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
719         expectedStats.bgJobCountInMaxPeriod = 15;
720         expectedStats.sessionCountInWindow = 2;
721         synchronized (mQuotaController.mLock) {
722             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
723         }
724         assertEquals(expectedStats, inputStats);
725 
726         inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
727         inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 2;
728         // Invalid time is now since the start of the session is at the very edge of the window
729         // cutoff time.
730         expectedStats.expirationTimeElapsed = now;
731         expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
732         expectedStats.bgJobCountInWindow = 5;
733         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
734         expectedStats.bgJobCountInMaxPeriod = 15;
735         expectedStats.sessionCountInWindow = 3;
736         expectedStats.inQuotaTimeElapsed = now + 11 * MINUTE_IN_MILLIS;
737         synchronized (mQuotaController.mLock) {
738             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
739         }
740         assertEquals(expectedStats, inputStats);
741 
742         inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
743         inputStats.jobCountLimit = expectedStats.jobCountLimit = 6;
744         inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
745         // Invalid time is now since the session straddles the window cutoff time.
746         expectedStats.expirationTimeElapsed = now;
747         expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
748         expectedStats.bgJobCountInWindow = 10;
749         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
750         expectedStats.bgJobCountInMaxPeriod = 15;
751         expectedStats.sessionCountInWindow = 4;
752         expectedStats.inQuotaTimeElapsed = now + 5 * MINUTE_IN_MILLIS;
753         synchronized (mQuotaController.mLock) {
754             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
755         }
756         assertEquals(expectedStats, inputStats);
757 
758         inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
759         // Invalid time is now +59 minutes since the earliest session in the window is now-121
760         // minutes.
761         expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
762         expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
763         expectedStats.bgJobCountInWindow = 10;
764         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
765         expectedStats.bgJobCountInMaxPeriod = 15;
766         expectedStats.sessionCountInWindow = 4;
767         // App goes under job execution time limit in ~61 minutes, but will be under job count limit
768         // in 65 minutes.
769         expectedStats.inQuotaTimeElapsed = now + 65 * MINUTE_IN_MILLIS;
770         synchronized (mQuotaController.mLock) {
771             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
772         }
773         assertEquals(expectedStats, inputStats);
774 
775         inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
776         // Invalid time is now since the start of the session is at the very edge of the window
777         // cutoff time.
778         expectedStats.expirationTimeElapsed = now;
779         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
780         expectedStats.bgJobCountInWindow = 15;
781         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
782         expectedStats.bgJobCountInMaxPeriod = 15;
783         expectedStats.sessionCountInWindow = 5;
784         expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS + 5 * MINUTE_IN_MILLIS;
785         synchronized (mQuotaController.mLock) {
786             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
787         }
788         assertEquals(expectedStats, inputStats);
789 
790         // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period.
791         mQuotaController.getTimingSessions(0, "com.android.test")
792                 .add(0,
793                         createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
794         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
795         inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
796         // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
797         // before the end of the max period cutoff time.
798         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
799         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
800         expectedStats.bgJobCountInWindow = 15;
801         expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
802         expectedStats.bgJobCountInMaxPeriod = 18;
803         expectedStats.sessionCountInWindow = 5;
804         expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
805                 + mQcConstants.IN_QUOTA_BUFFER_MS;
806         synchronized (mQuotaController.mLock) {
807             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
808         }
809         assertEquals(expectedStats, inputStats);
810 
811         mQuotaController.getTimingSessions(0, "com.android.test")
812                 .add(0,
813                         createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
814                                 2 * MINUTE_IN_MILLIS, 2));
815         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
816         // Invalid time is now since the earliest session straddles the max period cutoff time.
817         expectedStats.expirationTimeElapsed = now;
818         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
819         expectedStats.bgJobCountInWindow = 15;
820         expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
821         expectedStats.bgJobCountInMaxPeriod = 20;
822         expectedStats.sessionCountInWindow = 5;
823         expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
824                 + mQcConstants.IN_QUOTA_BUFFER_MS;
825         synchronized (mQuotaController.mLock) {
826             mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
827         }
828         assertEquals(expectedStats, inputStats);
829     }
830 
831     @Test
testUpdateExecutionStatsLocked_WithTimer()832     public void testUpdateExecutionStatsLocked_WithTimer() {
833         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
834 
835         ExecutionStats expectedStats = new ExecutionStats();
836         ExecutionStats inputStats = new ExecutionStats();
837         inputStats.allowedTimePerPeriodMs = expectedStats.allowedTimePerPeriodMs =
838                 10 * MINUTE_IN_MILLIS;
839         inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
840         inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
841         inputStats.sessionCountLimit = expectedStats.sessionCountLimit =
842                 mQcConstants.MAX_SESSION_COUNT_RARE;
843         // Active timer isn't counted as session yet.
844         expectedStats.sessionCountInWindow = 0;
845         // Timer only, under quota.
846         for (int i = 1; i < mQcConstants.MAX_JOB_COUNT_RARE; ++i) {
847             JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", i);
848             setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window
849             synchronized (mQuotaController.mLock) {
850                 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
851                 mQuotaController.prepareForExecutionLocked(jobStatus);
852             }
853             advanceElapsedClock(7000);
854 
855             expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis();
856             expectedStats.executionTimeInWindowMs = expectedStats.executionTimeInMaxPeriodMs =
857                     7000 * i;
858             expectedStats.bgJobCountInWindow = expectedStats.bgJobCountInMaxPeriod = i;
859             synchronized (mQuotaController.mLock) {
860                 mQuotaController.updateExecutionStatsLocked(
861                         SOURCE_USER_ID, SOURCE_PACKAGE, inputStats);
862                 assertEquals(expectedStats, inputStats);
863                 assertTrue(mQuotaController.isWithinQuotaLocked(
864                         SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
865             }
866             assertTrue("Job not ready: " + jobStatus, jobStatus.isReady());
867         }
868 
869         // Add old session. Make sure values are combined correctly.
870         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
871                 createTimingSession(sElapsedRealtimeClock.millis() - (6 * HOUR_IN_MILLIS),
872                         10 * MINUTE_IN_MILLIS, 5), false);
873         expectedStats.sessionCountInWindow = 1;
874 
875         expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS;
876         expectedStats.executionTimeInWindowMs += 10 * MINUTE_IN_MILLIS;
877         expectedStats.executionTimeInMaxPeriodMs += 10 * MINUTE_IN_MILLIS;
878         expectedStats.bgJobCountInWindow += 5;
879         expectedStats.bgJobCountInMaxPeriod += 5;
880         // Active timer is under quota, so out of quota due to old session.
881         expectedStats.inQuotaTimeElapsed =
882                 sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS;
883         synchronized (mQuotaController.mLock) {
884             mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats);
885             assertEquals(expectedStats, inputStats);
886             assertFalse(
887                     mQuotaController.isWithinQuotaLocked(
888                             SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
889         }
890 
891         // Quota should be exceeded due to activity in active timer.
892         JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", 0);
893         setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window
894         synchronized (mQuotaController.mLock) {
895             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
896             mQuotaController.prepareForExecutionLocked(jobStatus);
897         }
898         advanceElapsedClock(10000);
899 
900         expectedStats.executionTimeInWindowMs += 10000;
901         expectedStats.executionTimeInMaxPeriodMs += 10000;
902         expectedStats.bgJobCountInWindow++;
903         expectedStats.bgJobCountInMaxPeriod++;
904         // Out of quota due to activity in active timer, so in quota time should be when enough
905         // time has passed since active timer.
906         expectedStats.inQuotaTimeElapsed =
907                 sElapsedRealtimeClock.millis() + expectedStats.windowSizeMs;
908         synchronized (mQuotaController.mLock) {
909             mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats);
910             assertEquals(expectedStats, inputStats);
911             assertFalse(
912                     mQuotaController.isWithinQuotaLocked(
913                             SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
914             assertFalse("Job unexpectedly ready: " + jobStatus, jobStatus.isReady());
915         }
916     }
917 
918     /**
919      * Tests that getExecutionStatsLocked returns the correct stats.
920      */
921     @Test
testGetExecutionStatsLocked_Values()922     public void testGetExecutionStatsLocked_Values() {
923         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
924         mQuotaController.saveTimingSession(0, "com.android.test",
925                 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
926         mQuotaController.saveTimingSession(0, "com.android.test",
927                 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
928         mQuotaController.saveTimingSession(0, "com.android.test",
929                 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
930         mQuotaController.saveTimingSession(0, "com.android.test",
931                 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
932 
933         ExecutionStats expectedStats = new ExecutionStats();
934 
935         // Active
936         expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
937         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
938         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
939         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
940         expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
941         expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
942         expectedStats.bgJobCountInWindow = 5;
943         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
944         expectedStats.bgJobCountInMaxPeriod = 20;
945         expectedStats.sessionCountInWindow = 1;
946         synchronized (mQuotaController.mLock) {
947             assertEquals(expectedStats,
948                     mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
949         }
950 
951         // Working
952         expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
953         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
954         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
955         expectedStats.expirationTimeElapsed = now;
956         expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
957         expectedStats.bgJobCountInWindow = 10;
958         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
959         expectedStats.bgJobCountInMaxPeriod = 20;
960         expectedStats.sessionCountInWindow = 2;
961         expectedStats.inQuotaTimeElapsed = now + 3 * MINUTE_IN_MILLIS
962                 + mQcConstants.IN_QUOTA_BUFFER_MS;
963         synchronized (mQuotaController.mLock) {
964             assertEquals(expectedStats,
965                     mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
966         }
967 
968         // Frequent
969         expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
970         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
971         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
972         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
973         expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
974         expectedStats.bgJobCountInWindow = 15;
975         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
976         expectedStats.bgJobCountInMaxPeriod = 20;
977         expectedStats.sessionCountInWindow = 3;
978         expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
979                 + mQcConstants.IN_QUOTA_BUFFER_MS;
980         synchronized (mQuotaController.mLock) {
981             assertEquals(expectedStats,
982                     mQuotaController.getExecutionStatsLocked(
983                             0, "com.android.test", FREQUENT_INDEX));
984         }
985 
986         // Rare
987         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
988         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
989         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
990         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
991         expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
992         expectedStats.bgJobCountInWindow = 20;
993         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
994         expectedStats.bgJobCountInMaxPeriod = 20;
995         expectedStats.sessionCountInWindow = 4;
996         expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
997                 + mQcConstants.IN_QUOTA_BUFFER_MS;
998         synchronized (mQuotaController.mLock) {
999             assertEquals(expectedStats,
1000                     mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
1001         }
1002     }
1003 
1004     /**
1005      * Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
1006      */
1007     @Test
testGetExecutionStatsLocked_Values_BeginningOfTime()1008     public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
1009         // Set time to 3 minutes after boot.
1010         advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
1011         advanceElapsedClock(3 * MINUTE_IN_MILLIS);
1012 
1013         mQuotaController.saveTimingSession(0, "com.android.test",
1014                 createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), false);
1015 
1016         ExecutionStats expectedStats = new ExecutionStats();
1017 
1018         // Active
1019         expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS;
1020         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
1021         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
1022         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
1023         expectedStats.expirationTimeElapsed = 11 * MINUTE_IN_MILLIS;
1024         expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
1025         expectedStats.bgJobCountInWindow = 2;
1026         expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS;
1027         expectedStats.bgJobCountInMaxPeriod = 2;
1028         expectedStats.sessionCountInWindow = 1;
1029         synchronized (mQuotaController.mLock) {
1030             assertEquals(expectedStats,
1031                     mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
1032         }
1033 
1034         // Working
1035         expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
1036         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
1037         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
1038         expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
1039         synchronized (mQuotaController.mLock) {
1040             assertEquals(expectedStats,
1041                     mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
1042         }
1043 
1044         // Frequent
1045         expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
1046         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
1047         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
1048         expectedStats.expirationTimeElapsed = 8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
1049         synchronized (mQuotaController.mLock) {
1050             assertEquals(expectedStats,
1051                     mQuotaController.getExecutionStatsLocked(
1052                             0, "com.android.test", FREQUENT_INDEX));
1053         }
1054 
1055         // Rare
1056         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
1057         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
1058         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
1059         expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
1060         synchronized (mQuotaController.mLock) {
1061             assertEquals(expectedStats,
1062                     mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
1063         }
1064     }
1065 
1066     /**
1067      * Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing.
1068      */
1069     @Test
testGetExecutionStatsLocked_CoalescingSessions()1070     public void testGetExecutionStatsLocked_CoalescingSessions() {
1071         for (int i = 0; i < 10; ++i) {
1072             mQuotaController.saveTimingSession(0, "com.android.test",
1073                     createTimingSession(
1074                             JobSchedulerService.sElapsedRealtimeClock.millis(),
1075                             5 * MINUTE_IN_MILLIS, 5), false);
1076             advanceElapsedClock(5 * MINUTE_IN_MILLIS);
1077             advanceElapsedClock(5 * MINUTE_IN_MILLIS);
1078             for (int j = 0; j < 5; ++j) {
1079                 mQuotaController.saveTimingSession(0, "com.android.test",
1080                         createTimingSession(
1081                                 JobSchedulerService.sElapsedRealtimeClock.millis(),
1082                                 MINUTE_IN_MILLIS, 2), false);
1083                 advanceElapsedClock(MINUTE_IN_MILLIS);
1084                 advanceElapsedClock(54 * SECOND_IN_MILLIS);
1085                 mQuotaController.saveTimingSession(0, "com.android.test",
1086                         createTimingSession(
1087                                 JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false);
1088                 advanceElapsedClock(500);
1089                 advanceElapsedClock(400);
1090                 mQuotaController.saveTimingSession(0, "com.android.test",
1091                         createTimingSession(
1092                                 JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false);
1093                 advanceElapsedClock(100);
1094                 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1095             }
1096             advanceElapsedClock(40 * MINUTE_IN_MILLIS);
1097         }
1098 
1099         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 0);
1100 
1101         synchronized (mQuotaController.mLock) {
1102             mQuotaController.invalidateAllExecutionStatsLocked();
1103             assertEquals(0, mQuotaController.getExecutionStatsLocked(
1104                     0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
1105             assertEquals(32, mQuotaController.getExecutionStatsLocked(
1106                     0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
1107             assertEquals(128, mQuotaController.getExecutionStatsLocked(
1108                     0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
1109             assertEquals(160, mQuotaController.getExecutionStatsLocked(
1110                     0, "com.android.test", RARE_INDEX).sessionCountInWindow);
1111         }
1112 
1113         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 500);
1114 
1115         synchronized (mQuotaController.mLock) {
1116             mQuotaController.invalidateAllExecutionStatsLocked();
1117             assertEquals(0, mQuotaController.getExecutionStatsLocked(
1118                     0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
1119             assertEquals(22, mQuotaController.getExecutionStatsLocked(
1120                     0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
1121             assertEquals(88, mQuotaController.getExecutionStatsLocked(
1122                     0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
1123             assertEquals(110, mQuotaController.getExecutionStatsLocked(
1124                     0, "com.android.test", RARE_INDEX).sessionCountInWindow);
1125         }
1126 
1127         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1000);
1128 
1129         synchronized (mQuotaController.mLock) {
1130             mQuotaController.invalidateAllExecutionStatsLocked();
1131             assertEquals(0, mQuotaController.getExecutionStatsLocked(
1132                     0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
1133             assertEquals(22, mQuotaController.getExecutionStatsLocked(
1134                     0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
1135             assertEquals(88, mQuotaController.getExecutionStatsLocked(
1136                     0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
1137             assertEquals(110, mQuotaController.getExecutionStatsLocked(
1138                     0, "com.android.test", RARE_INDEX).sessionCountInWindow);
1139         }
1140 
1141         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
1142                 5 * SECOND_IN_MILLIS);
1143 
1144         synchronized (mQuotaController.mLock) {
1145             mQuotaController.invalidateAllExecutionStatsLocked();
1146             assertEquals(0, mQuotaController.getExecutionStatsLocked(
1147                     0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
1148             assertEquals(14, mQuotaController.getExecutionStatsLocked(
1149                     0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
1150             assertEquals(56, mQuotaController.getExecutionStatsLocked(
1151                     0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
1152             assertEquals(70, mQuotaController.getExecutionStatsLocked(
1153                     0, "com.android.test", RARE_INDEX).sessionCountInWindow);
1154         }
1155 
1156         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
1157                 MINUTE_IN_MILLIS);
1158 
1159         synchronized (mQuotaController.mLock) {
1160             mQuotaController.invalidateAllExecutionStatsLocked();
1161             assertEquals(0, mQuotaController.getExecutionStatsLocked(
1162                     0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
1163             assertEquals(4, mQuotaController.getExecutionStatsLocked(
1164                     0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
1165             assertEquals(16, mQuotaController.getExecutionStatsLocked(
1166                     0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
1167             assertEquals(20, mQuotaController.getExecutionStatsLocked(
1168                     0, "com.android.test", RARE_INDEX).sessionCountInWindow);
1169         }
1170 
1171         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
1172                 5 * MINUTE_IN_MILLIS);
1173 
1174         synchronized (mQuotaController.mLock) {
1175             mQuotaController.invalidateAllExecutionStatsLocked();
1176             assertEquals(0, mQuotaController.getExecutionStatsLocked(
1177                     0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
1178             assertEquals(2, mQuotaController.getExecutionStatsLocked(
1179                     0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
1180             assertEquals(8, mQuotaController.getExecutionStatsLocked(
1181                     0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
1182             assertEquals(10, mQuotaController.getExecutionStatsLocked(
1183                     0, "com.android.test", RARE_INDEX).sessionCountInWindow);
1184         }
1185 
1186         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
1187                 15 * MINUTE_IN_MILLIS);
1188 
1189         synchronized (mQuotaController.mLock) {
1190             mQuotaController.invalidateAllExecutionStatsLocked();
1191             assertEquals(0, mQuotaController.getExecutionStatsLocked(
1192                     0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
1193             assertEquals(2, mQuotaController.getExecutionStatsLocked(
1194                     0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
1195             assertEquals(8, mQuotaController.getExecutionStatsLocked(
1196                     0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
1197             assertEquals(10, mQuotaController.getExecutionStatsLocked(
1198                     0, "com.android.test", RARE_INDEX).sessionCountInWindow);
1199         }
1200 
1201         // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference
1202         // between an hour and 15 minutes.
1203         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, HOUR_IN_MILLIS);
1204 
1205         synchronized (mQuotaController.mLock) {
1206             mQuotaController.invalidateAllExecutionStatsLocked();
1207             assertEquals(0, mQuotaController.getExecutionStatsLocked(
1208                     0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
1209             assertEquals(2, mQuotaController.getExecutionStatsLocked(
1210                     0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
1211             assertEquals(8, mQuotaController.getExecutionStatsLocked(
1212                     0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
1213             assertEquals(10, mQuotaController.getExecutionStatsLocked(
1214                     0, "com.android.test", RARE_INDEX).sessionCountInWindow);
1215         }
1216     }
1217 
1218     /**
1219      * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
1220      */
1221     @Test
testGetExecutionStatsLocked_Caching()1222     public void testGetExecutionStatsLocked_Caching() {
1223         spyOn(mQuotaController);
1224         doNothing().when(mQuotaController).invalidateAllExecutionStatsLocked();
1225 
1226         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1227         mQuotaController.saveTimingSession(0, "com.android.test",
1228                 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
1229         mQuotaController.saveTimingSession(0, "com.android.test",
1230                 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
1231         mQuotaController.saveTimingSession(0, "com.android.test",
1232                 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
1233         mQuotaController.saveTimingSession(0, "com.android.test",
1234                 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
1235         final ExecutionStats originalStatsActive;
1236         final ExecutionStats originalStatsWorking;
1237         final ExecutionStats originalStatsFrequent;
1238         final ExecutionStats originalStatsRare;
1239         synchronized (mQuotaController.mLock) {
1240             originalStatsActive = mQuotaController.getExecutionStatsLocked(
1241                     0, "com.android.test", ACTIVE_INDEX);
1242             originalStatsWorking = mQuotaController.getExecutionStatsLocked(
1243                     0, "com.android.test", WORKING_INDEX);
1244             originalStatsFrequent = mQuotaController.getExecutionStatsLocked(
1245                     0, "com.android.test", FREQUENT_INDEX);
1246             originalStatsRare = mQuotaController.getExecutionStatsLocked(
1247                     0, "com.android.test", RARE_INDEX);
1248         }
1249 
1250         // Advance clock so that the working stats shouldn't be the same.
1251         advanceElapsedClock(MINUTE_IN_MILLIS);
1252         // Change frequent bucket size so that the stats need to be recalculated.
1253         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 6 * HOUR_IN_MILLIS);
1254 
1255         ExecutionStats expectedStats = new ExecutionStats();
1256         expectedStats.allowedTimePerPeriodMs = originalStatsActive.allowedTimePerPeriodMs;
1257         expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
1258         expectedStats.jobCountLimit = originalStatsActive.jobCountLimit;
1259         expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit;
1260         expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
1261         expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
1262         expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
1263         expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
1264         expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
1265         expectedStats.sessionCountInWindow = originalStatsActive.sessionCountInWindow;
1266         expectedStats.inQuotaTimeElapsed = originalStatsActive.inQuotaTimeElapsed;
1267         final ExecutionStats newStatsActive;
1268         synchronized (mQuotaController.mLock) {
1269             newStatsActive = mQuotaController.getExecutionStatsLocked(
1270                     0, "com.android.test", ACTIVE_INDEX);
1271         }
1272         // Stats for the same bucket should use the same object.
1273         assertTrue(originalStatsActive == newStatsActive);
1274         assertEquals(expectedStats, newStatsActive);
1275 
1276         expectedStats.allowedTimePerPeriodMs = originalStatsWorking.allowedTimePerPeriodMs;
1277         expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
1278         expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit;
1279         expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit;
1280         expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
1281         expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
1282         expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
1283         expectedStats.sessionCountInWindow = originalStatsWorking.sessionCountInWindow;
1284         expectedStats.inQuotaTimeElapsed = originalStatsWorking.inQuotaTimeElapsed;
1285         final ExecutionStats newStatsWorking;
1286         synchronized (mQuotaController.mLock) {
1287             newStatsWorking = mQuotaController.getExecutionStatsLocked(
1288                     0, "com.android.test", WORKING_INDEX);
1289         }
1290         assertTrue(originalStatsWorking == newStatsWorking);
1291         assertNotEquals(expectedStats, newStatsWorking);
1292 
1293         expectedStats.allowedTimePerPeriodMs = originalStatsFrequent.allowedTimePerPeriodMs;
1294         expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
1295         expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit;
1296         expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit;
1297         expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
1298         expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
1299         expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
1300         expectedStats.sessionCountInWindow = originalStatsFrequent.sessionCountInWindow;
1301         expectedStats.inQuotaTimeElapsed = originalStatsFrequent.inQuotaTimeElapsed;
1302         final ExecutionStats newStatsFrequent;
1303         synchronized (mQuotaController.mLock) {
1304             newStatsFrequent = mQuotaController.getExecutionStatsLocked(
1305                     0, "com.android.test", FREQUENT_INDEX);
1306         }
1307         assertTrue(originalStatsFrequent == newStatsFrequent);
1308         assertNotEquals(expectedStats, newStatsFrequent);
1309 
1310         expectedStats.allowedTimePerPeriodMs = originalStatsRare.allowedTimePerPeriodMs;
1311         expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
1312         expectedStats.jobCountLimit = originalStatsRare.jobCountLimit;
1313         expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit;
1314         expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
1315         expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
1316         expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
1317         expectedStats.sessionCountInWindow = originalStatsRare.sessionCountInWindow;
1318         expectedStats.inQuotaTimeElapsed = originalStatsRare.inQuotaTimeElapsed;
1319         final ExecutionStats newStatsRare;
1320         synchronized (mQuotaController.mLock) {
1321             newStatsRare = mQuotaController.getExecutionStatsLocked(
1322                     0, "com.android.test", RARE_INDEX);
1323         }
1324         assertTrue(originalStatsRare == newStatsRare);
1325         assertEquals(expectedStats, newStatsRare);
1326     }
1327 
1328     @Test
testGetMaxJobExecutionTimeLocked_Regular()1329     public void testGetMaxJobExecutionTimeLocked_Regular() {
1330         mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
1331                 createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
1332                         3 * MINUTE_IN_MILLIS, 5), false);
1333         JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
1334         setStandbyBucket(RARE_INDEX, job);
1335 
1336         setCharging();
1337         synchronized (mQuotaController.mLock) {
1338             assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
1339                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
1340         }
1341 
1342         setDischarging();
1343         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1344         synchronized (mQuotaController.mLock) {
1345             assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
1346                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
1347         }
1348 
1349         // Top-started job
1350         setProcessState(ActivityManager.PROCESS_STATE_TOP);
1351         synchronized (mQuotaController.mLock) {
1352             mQuotaController.maybeStartTrackingJobLocked(job, null);
1353             mQuotaController.prepareForExecutionLocked(job);
1354         }
1355         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1356         synchronized (mQuotaController.mLock) {
1357             assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
1358                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
1359             mQuotaController.maybeStopTrackingJobLocked(job, null, false);
1360         }
1361 
1362         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
1363         synchronized (mQuotaController.mLock) {
1364             assertEquals(7 * MINUTE_IN_MILLIS,
1365                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
1366         }
1367     }
1368 
1369     @Test
testGetMaxJobExecutionTimeLocked_Regular_Active()1370     public void testGetMaxJobExecutionTimeLocked_Regular_Active() {
1371         JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked_Regular_Active", 0);
1372         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
1373                 10 * MINUTE_IN_MILLIS);
1374         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 10 * MINUTE_IN_MILLIS);
1375         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 2 * HOUR_IN_MILLIS);
1376         setDischarging();
1377         setStandbyBucket(ACTIVE_INDEX, job);
1378         setProcessState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
1379 
1380         // ACTIVE apps (where allowed time = window size) should be capped at max execution limit.
1381         synchronized (mQuotaController.mLock) {
1382             assertEquals(2 * HOUR_IN_MILLIS,
1383                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
1384         }
1385 
1386         // Make sure sessions are factored in properly.
1387         mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
1388                 createTimingSession(sElapsedRealtimeClock.millis() - (6 * HOUR_IN_MILLIS),
1389                         30 * MINUTE_IN_MILLIS, 1), false);
1390         synchronized (mQuotaController.mLock) {
1391             assertEquals(90 * MINUTE_IN_MILLIS,
1392                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
1393         }
1394 
1395         mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
1396                 createTimingSession(sElapsedRealtimeClock.millis() - (5 * HOUR_IN_MILLIS),
1397                         30 * MINUTE_IN_MILLIS, 1), false);
1398         mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
1399                 createTimingSession(sElapsedRealtimeClock.millis() - (4 * HOUR_IN_MILLIS),
1400                         30 * MINUTE_IN_MILLIS, 1), false);
1401         mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
1402                 createTimingSession(sElapsedRealtimeClock.millis() - (3 * HOUR_IN_MILLIS),
1403                         25 * MINUTE_IN_MILLIS, 1), false);
1404         synchronized (mQuotaController.mLock) {
1405             assertEquals(5 * MINUTE_IN_MILLIS,
1406                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
1407         }
1408     }
1409 
1410     @Test
testGetMaxJobExecutionTimeLocked_EJ()1411     public void testGetMaxJobExecutionTimeLocked_EJ() {
1412         final long timeUsedMs = 3 * MINUTE_IN_MILLIS;
1413         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1414                 createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
1415                         timeUsedMs, 5), true);
1416         JobStatus job = createExpeditedJobStatus("testGetMaxJobExecutionTimeLocked_EJ", 0);
1417         setStandbyBucket(RARE_INDEX, job);
1418         synchronized (mQuotaController.mLock) {
1419             mQuotaController.maybeStartTrackingJobLocked(job, null);
1420         }
1421 
1422         setCharging();
1423         synchronized (mQuotaController.mLock) {
1424             assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
1425                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
1426         }
1427 
1428         setDischarging();
1429         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1430         synchronized (mQuotaController.mLock) {
1431             assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
1432                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
1433         }
1434 
1435         // Top-started job
1436         setProcessState(ActivityManager.PROCESS_STATE_TOP);
1437         synchronized (mQuotaController.mLock) {
1438             mQuotaController.prepareForExecutionLocked(job);
1439         }
1440         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1441         synchronized (mQuotaController.mLock) {
1442             assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
1443                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
1444             mQuotaController.maybeStopTrackingJobLocked(job, null, false);
1445         }
1446 
1447         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
1448         synchronized (mQuotaController.mLock) {
1449             assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs,
1450                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
1451         }
1452 
1453         // Test used quota rolling out of window.
1454         synchronized (mQuotaController.mLock) {
1455             mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
1456         }
1457         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1458                 createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS,
1459                         timeUsedMs, 5), true);
1460 
1461         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1462         synchronized (mQuotaController.mLock) {
1463             assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2,
1464                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
1465         }
1466 
1467         // Top-started job
1468         setProcessState(ActivityManager.PROCESS_STATE_TOP);
1469         synchronized (mQuotaController.mLock) {
1470             mQuotaController.maybeStartTrackingJobLocked(job, null);
1471             mQuotaController.prepareForExecutionLocked(job);
1472         }
1473         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1474         synchronized (mQuotaController.mLock) {
1475             assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
1476                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
1477             mQuotaController.maybeStopTrackingJobLocked(job, null, false);
1478         }
1479 
1480         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
1481         synchronized (mQuotaController.mLock) {
1482             assertEquals(mQcConstants.EJ_LIMIT_RARE_MS,
1483                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
1484         }
1485     }
1486 
1487     /**
1488      * Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size.
1489      */
1490     @Test
testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow()1491     public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow() {
1492         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1493         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1494                 createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false);
1495         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1496                 createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5),
1497                 false);
1498 
1499         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
1500                 10 * MINUTE_IN_MILLIS);
1501         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 10 * MINUTE_IN_MILLIS);
1502         // window size = allowed time, so jobs can essentially run non-stop until they reach the
1503         // max execution time.
1504         setStandbyBucket(EXEMPTED_INDEX);
1505         synchronized (mQuotaController.mLock) {
1506             assertEquals(0,
1507                     mQuotaController.getRemainingExecutionTimeLocked(
1508                             SOURCE_USER_ID, SOURCE_PACKAGE));
1509             assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS,
1510                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1511                             SOURCE_USER_ID, SOURCE_PACKAGE));
1512         }
1513     }
1514 
1515     /**
1516      * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
1517      * window.
1518      */
1519     @Test
testGetTimeUntilQuotaConsumedLocked_BucketWindow()1520     public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() {
1521         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1522         // Close to RARE boundary.
1523         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1524                 createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
1525                         30 * SECOND_IN_MILLIS, 5), false);
1526         // Far away from FREQUENT boundary.
1527         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1528                 createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
1529         // Overlap WORKING_SET boundary.
1530         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1531                 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
1532                         3 * MINUTE_IN_MILLIS, 5), false);
1533         // Close to ACTIVE boundary.
1534         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1535                 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
1536 
1537         setStandbyBucket(RARE_INDEX);
1538         synchronized (mQuotaController.mLock) {
1539             assertEquals(30 * SECOND_IN_MILLIS,
1540                     mQuotaController.getRemainingExecutionTimeLocked(
1541                             SOURCE_USER_ID, SOURCE_PACKAGE));
1542             assertEquals(MINUTE_IN_MILLIS,
1543                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1544                             SOURCE_USER_ID, SOURCE_PACKAGE));
1545         }
1546 
1547         setStandbyBucket(FREQUENT_INDEX);
1548         synchronized (mQuotaController.mLock) {
1549             assertEquals(MINUTE_IN_MILLIS,
1550                     mQuotaController.getRemainingExecutionTimeLocked(
1551                             SOURCE_USER_ID, SOURCE_PACKAGE));
1552             assertEquals(MINUTE_IN_MILLIS,
1553                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1554                             SOURCE_USER_ID, SOURCE_PACKAGE));
1555         }
1556 
1557         setStandbyBucket(WORKING_INDEX);
1558         synchronized (mQuotaController.mLock) {
1559             assertEquals(5 * MINUTE_IN_MILLIS,
1560                     mQuotaController.getRemainingExecutionTimeLocked(
1561                             SOURCE_USER_ID, SOURCE_PACKAGE));
1562             assertEquals(7 * MINUTE_IN_MILLIS,
1563                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1564                             SOURCE_USER_ID, SOURCE_PACKAGE));
1565         }
1566 
1567         // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
1568         // max execution time.
1569         setStandbyBucket(ACTIVE_INDEX);
1570         synchronized (mQuotaController.mLock) {
1571             assertEquals(7 * MINUTE_IN_MILLIS,
1572                     mQuotaController.getRemainingExecutionTimeLocked(
1573                             SOURCE_USER_ID, SOURCE_PACKAGE));
1574             assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
1575                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1576                             SOURCE_USER_ID, SOURCE_PACKAGE));
1577         }
1578     }
1579 
1580     /**
1581      * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit.
1582      */
1583     @Test
testGetTimeUntilQuotaConsumedLocked_MaxExecution()1584     public void testGetTimeUntilQuotaConsumedLocked_MaxExecution() {
1585         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1586         // Overlap boundary.
1587         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1588                 createTimingSession(
1589                         now - (24 * HOUR_IN_MILLIS + 8 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5),
1590                 false);
1591 
1592         setStandbyBucket(WORKING_INDEX);
1593         synchronized (mQuotaController.mLock) {
1594             assertEquals(8 * MINUTE_IN_MILLIS,
1595                     mQuotaController.getRemainingExecutionTimeLocked(
1596                             SOURCE_USER_ID, SOURCE_PACKAGE));
1597             // Max time will phase out, so should use bucket limit.
1598             assertEquals(10 * MINUTE_IN_MILLIS,
1599                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1600                             SOURCE_USER_ID, SOURCE_PACKAGE));
1601         }
1602 
1603         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1604         // Close to boundary.
1605         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1606                 createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS),
1607                         4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5), false);
1608 
1609         setStandbyBucket(WORKING_INDEX);
1610         synchronized (mQuotaController.mLock) {
1611             assertEquals(5 * MINUTE_IN_MILLIS,
1612                     mQuotaController.getRemainingExecutionTimeLocked(
1613                             SOURCE_USER_ID, SOURCE_PACKAGE));
1614             assertEquals(10 * MINUTE_IN_MILLIS,
1615                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1616                             SOURCE_USER_ID, SOURCE_PACKAGE));
1617         }
1618 
1619         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1620         // Far from boundary.
1621         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1622                 createTimingSession(
1623                         now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5),
1624                 false);
1625 
1626         setStandbyBucket(WORKING_INDEX);
1627         synchronized (mQuotaController.mLock) {
1628             assertEquals(3 * MINUTE_IN_MILLIS,
1629                     mQuotaController.getRemainingExecutionTimeLocked(
1630                             SOURCE_USER_ID, SOURCE_PACKAGE));
1631             assertEquals(3 * MINUTE_IN_MILLIS,
1632                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1633                             SOURCE_USER_ID, SOURCE_PACKAGE));
1634         }
1635     }
1636 
1637     /**
1638      * Test getTimeUntilQuotaConsumedLocked when the max execution time and bucket window time
1639      * remaining are equal.
1640      */
1641     @Test
testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining()1642     public void testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining() {
1643         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1644         setStandbyBucket(FREQUENT_INDEX);
1645 
1646         // Overlap boundary.
1647         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1648                 createTimingSession(
1649                         now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS),
1650                         4 * HOUR_IN_MILLIS,
1651                         5), false);
1652         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1653                 createTimingSession(
1654                         now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5),
1655                 false);
1656 
1657         synchronized (mQuotaController.mLock) {
1658             // Both max and bucket time have 8 minutes left.
1659             assertEquals(8 * MINUTE_IN_MILLIS,
1660                     mQuotaController.getRemainingExecutionTimeLocked(
1661                             SOURCE_USER_ID, SOURCE_PACKAGE));
1662             // Max time essentially free. Bucket time has 2 min phase out plus original 8 minute
1663             // window time.
1664             assertEquals(10 * MINUTE_IN_MILLIS,
1665                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1666                             SOURCE_USER_ID, SOURCE_PACKAGE));
1667         }
1668 
1669         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1670         // Overlap boundary.
1671         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1672                 createTimingSession(
1673                         now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5),
1674                 false);
1675         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1676                 createTimingSession(
1677                         now - (20 * HOUR_IN_MILLIS),
1678                         3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS,
1679                         5), false);
1680         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1681                 createTimingSession(
1682                         now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5),
1683                 false);
1684 
1685         synchronized (mQuotaController.mLock) {
1686             // Both max and bucket time have 8 minutes left.
1687             assertEquals(8 * MINUTE_IN_MILLIS,
1688                     mQuotaController.getRemainingExecutionTimeLocked(
1689                             SOURCE_USER_ID, SOURCE_PACKAGE));
1690             // Max time only has one minute phase out. Bucket time has 2 minute phase out.
1691             assertEquals(9 * MINUTE_IN_MILLIS,
1692                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1693                             SOURCE_USER_ID, SOURCE_PACKAGE));
1694         }
1695     }
1696 
1697     /**
1698      * Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size.
1699      */
1700     @Test
testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow()1701     public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow() {
1702         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1703         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1704                 createTimingSession(now - (24 * HOUR_IN_MILLIS),
1705                         mQcConstants.MAX_EXECUTION_TIME_MS - 10 * MINUTE_IN_MILLIS, 5),
1706                 false);
1707         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1708                 createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5),
1709                 false);
1710 
1711         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
1712                 10 * MINUTE_IN_MILLIS);
1713         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 10 * MINUTE_IN_MILLIS);
1714         // window size = allowed time, so jobs can essentially run non-stop until they reach the
1715         // max execution time.
1716         setStandbyBucket(EXEMPTED_INDEX);
1717         synchronized (mQuotaController.mLock) {
1718             assertEquals(0,
1719                     mQuotaController.getRemainingExecutionTimeLocked(
1720                             SOURCE_USER_ID, SOURCE_PACKAGE));
1721             assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 10 * MINUTE_IN_MILLIS,
1722                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1723                             SOURCE_USER_ID, SOURCE_PACKAGE));
1724         }
1725     }
1726 
1727     /**
1728      * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
1729      * window and the session is rolling out of the window.
1730      */
1731     @Test
testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow()1732     public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow() {
1733         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1734 
1735         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1736                 createTimingSession(now - (24 * HOUR_IN_MILLIS),
1737                         10 * MINUTE_IN_MILLIS, 5), false);
1738         setStandbyBucket(RARE_INDEX);
1739         synchronized (mQuotaController.mLock) {
1740             assertEquals(0,
1741                     mQuotaController.getRemainingExecutionTimeLocked(
1742                             SOURCE_USER_ID, SOURCE_PACKAGE));
1743             assertEquals(10 * MINUTE_IN_MILLIS,
1744                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1745                             SOURCE_USER_ID, SOURCE_PACKAGE));
1746         }
1747 
1748         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1749                 createTimingSession(now - (8 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false);
1750         setStandbyBucket(FREQUENT_INDEX);
1751         synchronized (mQuotaController.mLock) {
1752             assertEquals(0,
1753                     mQuotaController.getRemainingExecutionTimeLocked(
1754                             SOURCE_USER_ID, SOURCE_PACKAGE));
1755             assertEquals(10 * MINUTE_IN_MILLIS,
1756                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1757                             SOURCE_USER_ID, SOURCE_PACKAGE));
1758         }
1759 
1760         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1761                 createTimingSession(now - (2 * HOUR_IN_MILLIS),
1762                         10 * MINUTE_IN_MILLIS, 5), false);
1763         setStandbyBucket(WORKING_INDEX);
1764         synchronized (mQuotaController.mLock) {
1765             assertEquals(0,
1766                     mQuotaController.getRemainingExecutionTimeLocked(
1767                             SOURCE_USER_ID, SOURCE_PACKAGE));
1768             assertEquals(10 * MINUTE_IN_MILLIS,
1769                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1770                             SOURCE_USER_ID, SOURCE_PACKAGE));
1771         }
1772 
1773         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1774                 createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5),
1775                 false);
1776         // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
1777         // max execution time.
1778         setStandbyBucket(ACTIVE_INDEX);
1779         synchronized (mQuotaController.mLock) {
1780             assertEquals(0,
1781                     mQuotaController.getRemainingExecutionTimeLocked(
1782                             SOURCE_USER_ID, SOURCE_PACKAGE));
1783             assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS,
1784                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1785                             SOURCE_USER_ID, SOURCE_PACKAGE));
1786         }
1787     }
1788 
1789     /**
1790      * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
1791      * window and there are valid QuotaBumps in the history.
1792      */
1793     @Test
testGetTimeUntilQuotaConsumedLocked_QuotaBump()1794     public void testGetTimeUntilQuotaConsumedLocked_QuotaBump() {
1795         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS);
1796         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
1797 
1798         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1799         // Close to RARE boundary.
1800         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1801                 createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
1802                         30 * SECOND_IN_MILLIS, 5), false);
1803         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
1804                 .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS));
1805         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
1806                 .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS));
1807         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
1808                 .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS));
1809         // Far away from FREQUENT boundary.
1810         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1811                 createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
1812         // Overlap WORKING_SET boundary.
1813         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
1814                 .add(new QuotaBump(now - 2 * HOUR_IN_MILLIS));
1815         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1816                 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
1817                         3 * MINUTE_IN_MILLIS, 5), false);
1818         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
1819                 .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS));
1820         // Close to ACTIVE boundary.
1821         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1822                 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
1823 
1824         setStandbyBucket(RARE_INDEX);
1825         synchronized (mQuotaController.mLock) {
1826             assertEquals(3 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
1827                     mQuotaController.getRemainingExecutionTimeLocked(
1828                             SOURCE_USER_ID, SOURCE_PACKAGE));
1829             assertEquals(4 * MINUTE_IN_MILLIS,
1830                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1831                             SOURCE_USER_ID, SOURCE_PACKAGE));
1832         }
1833 
1834         setStandbyBucket(FREQUENT_INDEX);
1835         synchronized (mQuotaController.mLock) {
1836             assertEquals(4 * MINUTE_IN_MILLIS,
1837                     mQuotaController.getRemainingExecutionTimeLocked(
1838                             SOURCE_USER_ID, SOURCE_PACKAGE));
1839             assertEquals(4 * MINUTE_IN_MILLIS,
1840                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1841                             SOURCE_USER_ID, SOURCE_PACKAGE));
1842         }
1843 
1844         setStandbyBucket(WORKING_INDEX);
1845         synchronized (mQuotaController.mLock) {
1846             assertEquals(8 * MINUTE_IN_MILLIS,
1847                     mQuotaController.getRemainingExecutionTimeLocked(
1848                             SOURCE_USER_ID, SOURCE_PACKAGE));
1849             assertEquals(10 * MINUTE_IN_MILLIS,
1850                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1851                             SOURCE_USER_ID, SOURCE_PACKAGE));
1852         }
1853 
1854         // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
1855         // max execution time.
1856         setStandbyBucket(ACTIVE_INDEX);
1857         synchronized (mQuotaController.mLock) {
1858             assertEquals(10 * MINUTE_IN_MILLIS,
1859                     mQuotaController.getRemainingExecutionTimeLocked(
1860                             SOURCE_USER_ID, SOURCE_PACKAGE));
1861             assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
1862                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1863                             SOURCE_USER_ID, SOURCE_PACKAGE));
1864         }
1865     }
1866 
1867     /**
1868      * Test getTimeUntilQuotaConsumedLocked when there are valid QuotaBumps in recent history that
1869      * provide enough additional quota to bridge gaps between sessions.
1870      */
1871     @Test
testGetTimeUntilQuotaConsumedLocked_QuotaBump_CrucialBumps()1872     public void testGetTimeUntilQuotaConsumedLocked_QuotaBump_CrucialBumps() {
1873         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS);
1874         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
1875 
1876         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1877         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1878                 createTimingSession(now - (25 * HOUR_IN_MILLIS),
1879                         30 * MINUTE_IN_MILLIS, 25), false);
1880         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
1881                 .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS));
1882         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
1883                 .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS));
1884         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
1885                 .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS));
1886         // Without the valid quota bumps, the app would only 3 minutes until the quota was consumed.
1887         // The quota bumps provide enough quota to bridge the gap between the two earliest sessions.
1888         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1889                 createTimingSession(now - (8 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
1890         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1891                 createTimingSession(now - (8 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS),
1892                         2 * MINUTE_IN_MILLIS, 5), false);
1893         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1894                 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
1895                         3 * MINUTE_IN_MILLIS, 1), false);
1896         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)
1897                 .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS));
1898         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1899                 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 1), false);
1900 
1901         setStandbyBucket(FREQUENT_INDEX);
1902         synchronized (mQuotaController.mLock) {
1903             assertEquals(2 * MINUTE_IN_MILLIS,
1904                     mQuotaController.getRemainingExecutionTimeLocked(
1905                             SOURCE_USER_ID, SOURCE_PACKAGE));
1906             assertEquals(7 * MINUTE_IN_MILLIS,
1907                     mQuotaController.getTimeUntilQuotaConsumedLocked(
1908                             SOURCE_USER_ID, SOURCE_PACKAGE));
1909         }
1910     }
1911 
1912     @Test
testIsWithinQuotaLocked_NeverApp()1913     public void testIsWithinQuotaLocked_NeverApp() {
1914         synchronized (mQuotaController.mLock) {
1915             assertFalse(
1916                     mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
1917         }
1918     }
1919 
1920     @Test
testIsWithinQuotaLocked_Charging()1921     public void testIsWithinQuotaLocked_Charging() {
1922         setCharging();
1923         synchronized (mQuotaController.mLock) {
1924             assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
1925         }
1926     }
1927 
1928     @Test
testIsWithinQuotaLocked_UnderDuration_UnderJobCount()1929     public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() {
1930         setDischarging();
1931         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1932         mQuotaController.saveTimingSession(0, "com.android.test",
1933                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
1934         mQuotaController.saveTimingSession(0, "com.android.test",
1935                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
1936         synchronized (mQuotaController.mLock) {
1937             mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
1938             assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
1939         }
1940     }
1941 
1942     @Test
testIsWithinQuotaLocked_UnderDuration_OverJobCount()1943     public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
1944         setDischarging();
1945         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1946         final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
1947         mQuotaController.saveTimingSession(0, "com.android.test.spam",
1948                 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25), false);
1949         mQuotaController.saveTimingSession(0, "com.android.test.spam",
1950                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount),
1951                 false);
1952         synchronized (mQuotaController.mLock) {
1953             mQuotaController.incrementJobCountLocked(0, "com.android.test.spam", jobCount);
1954             assertFalse(mQuotaController.isWithinQuotaLocked(
1955                     0, "com.android.test.spam", WORKING_INDEX));
1956         }
1957 
1958         mQuotaController.saveTimingSession(0, "com.android.test.frequent",
1959                 createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000),
1960                 false);
1961         mQuotaController.saveTimingSession(0, "com.android.test.frequent",
1962                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500), false);
1963         synchronized (mQuotaController.mLock) {
1964             assertFalse(mQuotaController.isWithinQuotaLocked(
1965                     0, "com.android.test.frequent", FREQUENT_INDEX));
1966         }
1967     }
1968 
1969     @Test
testIsWithinQuotaLocked_OverDuration_UnderJobCount()1970     public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() {
1971         setDischarging();
1972         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1973         mQuotaController.saveTimingSession(0, "com.android.test",
1974                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
1975         mQuotaController.saveTimingSession(0, "com.android.test",
1976                 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false);
1977         mQuotaController.saveTimingSession(0, "com.android.test",
1978                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), false);
1979         synchronized (mQuotaController.mLock) {
1980             mQuotaController.incrementJobCountLocked(0, "com.android.test", 5);
1981             assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
1982         }
1983     }
1984 
1985     @Test
testIsWithinQuotaLocked_OverDuration_OverJobCount()1986     public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
1987         setDischarging();
1988         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1989         final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
1990         mQuotaController.saveTimingSession(0, "com.android.test",
1991                 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25), false);
1992         mQuotaController.saveTimingSession(0, "com.android.test",
1993                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount),
1994                 false);
1995         synchronized (mQuotaController.mLock) {
1996             mQuotaController.incrementJobCountLocked(0, "com.android.test", jobCount);
1997             assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
1998         }
1999     }
2000 
2001     @Test
testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS()2002     public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS() {
2003         setDischarging();
2004 
2005         JobStatus jobStatus = createJobStatus(
2006                 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS", 1);
2007         setStandbyBucket(ACTIVE_INDEX, jobStatus);
2008         setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
2009 
2010         synchronized (mQuotaController.mLock) {
2011             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2012             mQuotaController.prepareForExecutionLocked(jobStatus);
2013         }
2014         for (int i = 0; i < 20; ++i) {
2015             advanceElapsedClock(SECOND_IN_MILLIS);
2016             setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
2017             setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
2018         }
2019         synchronized (mQuotaController.mLock) {
2020             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
2021         }
2022 
2023         advanceElapsedClock(15 * SECOND_IN_MILLIS);
2024 
2025         synchronized (mQuotaController.mLock) {
2026             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2027             mQuotaController.prepareForExecutionLocked(jobStatus);
2028         }
2029         for (int i = 0; i < 20; ++i) {
2030             advanceElapsedClock(SECOND_IN_MILLIS);
2031             setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
2032             setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
2033         }
2034         synchronized (mQuotaController.mLock) {
2035             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
2036         }
2037 
2038         advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS);
2039 
2040         synchronized (mQuotaController.mLock) {
2041             assertEquals(2, mQuotaController.getExecutionStatsLocked(
2042                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInRateLimitingWindow);
2043             assertTrue(mQuotaController.isWithinQuotaLocked(jobStatus));
2044             assertTrue(jobStatus.isReady());
2045         }
2046     }
2047 
2048     @Test
testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps()2049     public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps()
2050             throws Exception {
2051         setDischarging();
2052 
2053         final String unaffectedPkgName = "com.android.unaffected";
2054         final int unaffectedUid = 10987;
2055         JobInfo unaffectedJobInfo = new JobInfo.Builder(1,
2056                 new ComponentName(unaffectedPkgName, "foo"))
2057                 .build();
2058         JobStatus unaffected = createJobStatus(
2059                 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps",
2060                 unaffectedPkgName, unaffectedUid, unaffectedJobInfo);
2061         setStandbyBucket(FREQUENT_INDEX, unaffected);
2062         setProcessState(ActivityManager.PROCESS_STATE_SERVICE, unaffectedUid);
2063 
2064         final String fgChangerPkgName = "com.android.foreground.changer";
2065         final int fgChangerUid = 10234;
2066         JobInfo fgChangerJobInfo = new JobInfo.Builder(2,
2067                 new ComponentName(fgChangerPkgName, "foo"))
2068                 .build();
2069         JobStatus fgStateChanger = createJobStatus(
2070                 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps",
2071                 fgChangerPkgName, fgChangerUid, fgChangerJobInfo);
2072         setStandbyBucket(ACTIVE_INDEX, fgStateChanger);
2073         setProcessState(ActivityManager.PROCESS_STATE_BACKUP, fgChangerUid);
2074 
2075         doReturn(new ArraySet<>(new String[]{unaffectedPkgName}))
2076                 .when(mJobSchedulerService).getPackagesForUidLocked(unaffectedUid);
2077         doReturn(new ArraySet<>(new String[]{fgChangerPkgName}))
2078                 .when(mJobSchedulerService).getPackagesForUidLocked(fgChangerUid);
2079 
2080         synchronized (mQuotaController.mLock) {
2081             mQuotaController.maybeStartTrackingJobLocked(unaffected, null);
2082             mQuotaController.prepareForExecutionLocked(unaffected);
2083 
2084             mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null);
2085             mQuotaController.prepareForExecutionLocked(fgStateChanger);
2086         }
2087         for (int i = 0; i < 20; ++i) {
2088             advanceElapsedClock(SECOND_IN_MILLIS);
2089             setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid);
2090             setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
2091         }
2092         synchronized (mQuotaController.mLock) {
2093             mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
2094         }
2095 
2096         advanceElapsedClock(15 * SECOND_IN_MILLIS);
2097 
2098         synchronized (mQuotaController.mLock) {
2099             mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null);
2100             mQuotaController.prepareForExecutionLocked(fgStateChanger);
2101         }
2102         for (int i = 0; i < 20; ++i) {
2103             advanceElapsedClock(SECOND_IN_MILLIS);
2104             setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid);
2105             setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
2106         }
2107         synchronized (mQuotaController.mLock) {
2108             mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
2109 
2110             mQuotaController.maybeStopTrackingJobLocked(unaffected, null, false);
2111 
2112             assertTrue(mQuotaController.isWithinQuotaLocked(unaffected));
2113             assertTrue(unaffected.isReady());
2114             assertFalse(mQuotaController.isWithinQuotaLocked(fgStateChanger));
2115             assertFalse(fgStateChanger.isReady());
2116         }
2117         assertEquals(1,
2118                 mQuotaController.getTimingSessions(SOURCE_USER_ID, unaffectedPkgName).size());
2119         assertEquals(42,
2120                 mQuotaController.getTimingSessions(SOURCE_USER_ID, fgChangerPkgName).size());
2121         synchronized (mQuotaController.mLock) {
2122             for (int i = ACTIVE_INDEX; i < RARE_INDEX; ++i) {
2123                 assertEquals(42, mQuotaController.getExecutionStatsLocked(
2124                         SOURCE_USER_ID, fgChangerPkgName, i).jobCountInRateLimitingWindow);
2125                 assertEquals(1, mQuotaController.getExecutionStatsLocked(
2126                         SOURCE_USER_ID, unaffectedPkgName, i).jobCountInRateLimitingWindow);
2127             }
2128         }
2129     }
2130 
2131     @Test
testIsWithinQuotaLocked_TimingSession()2132     public void testIsWithinQuotaLocked_TimingSession() {
2133         setDischarging();
2134         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2135         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 3);
2136         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 4);
2137         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 5);
2138         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 6);
2139 
2140         for (int i = 0; i < 7; ++i) {
2141             mQuotaController.saveTimingSession(0, "com.android.test",
2142                     createTimingSession(now - ((10 - i) * MINUTE_IN_MILLIS), 30 * SECOND_IN_MILLIS,
2143                             2), false);
2144 
2145             synchronized (mQuotaController.mLock) {
2146                 mQuotaController.incrementJobCountLocked(0, "com.android.test", 2);
2147 
2148                 assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions",
2149                         i < 2,
2150                         mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
2151                 assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions",
2152                         i < 3,
2153                         mQuotaController.isWithinQuotaLocked(
2154                                 0, "com.android.test", FREQUENT_INDEX));
2155                 assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions",
2156                         i < 4,
2157                         mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
2158                 assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions",
2159                         i < 5,
2160                         mQuotaController.isWithinQuotaLocked(0, "com.android.test", ACTIVE_INDEX));
2161             }
2162         }
2163     }
2164 
2165     @Test
testIsWithinQuotaLocked_WithQuotaBump_Duration()2166     public void testIsWithinQuotaLocked_WithQuotaBump_Duration() {
2167         setDischarging();
2168         int standbyBucket = WORKING_INDEX;
2169         setStandbyBucket(standbyBucket);
2170         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
2171                 5 * MINUTE_IN_MILLIS);
2172         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
2173         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS);
2174         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0);
2175         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0);
2176         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
2177         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5);
2178 
2179         long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2180         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2181                 createTimingSession(
2182                         now - (HOUR_IN_MILLIS - 2 * MINUTE_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1),
2183                 false);
2184         final ExecutionStats stats;
2185         synchronized (mQuotaController.mLock) {
2186             stats = mQuotaController.getExecutionStatsLocked(
2187                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2188             mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1);
2189             assertFalse(mQuotaController
2190                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2191             assertEquals(5 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
2192         }
2193         mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
2194         synchronized (mQuotaController.mLock) {
2195             assertTrue(mQuotaController
2196                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2197             assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
2198         }
2199 
2200         advanceElapsedClock(HOUR_IN_MILLIS);
2201 
2202         synchronized (mQuotaController.mLock) {
2203             assertTrue(mQuotaController
2204                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2205             assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
2206         }
2207 
2208         // Emulate a quota bump while some jobs are executing
2209         JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 1);
2210         JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 2);
2211 
2212         synchronized (mQuotaController.mLock) {
2213             mQuotaController.maybeStartTrackingJobLocked(job1, null);
2214             mQuotaController.prepareForExecutionLocked(job1);
2215         }
2216 
2217         advanceElapsedClock(MINUTE_IN_MILLIS);
2218         mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
2219         synchronized (mQuotaController.mLock) {
2220             assertTrue(mQuotaController
2221                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2222             assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
2223             mQuotaController.maybeStartTrackingJobLocked(job2, null);
2224             mQuotaController.prepareForExecutionLocked(job2);
2225         }
2226 
2227         advanceElapsedClock(MINUTE_IN_MILLIS);
2228         synchronized (mQuotaController.mLock) {
2229             mQuotaController.maybeStopTrackingJobLocked(job1, null, false);
2230             mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
2231             assertFalse(mQuotaController
2232                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2233             assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
2234         }
2235 
2236         // Phase out the first session
2237         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
2238         synchronized (mQuotaController.mLock) {
2239             assertTrue(mQuotaController
2240                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2241             assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
2242         }
2243 
2244         // Phase out the first quota bump
2245         advanceElapsedClock(7 * HOUR_IN_MILLIS);
2246         synchronized (mQuotaController.mLock) {
2247             assertTrue(mQuotaController
2248                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2249             assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
2250         }
2251     }
2252 
2253     @Test
testIsWithinQuotaLocked_WithQuotaBump_JobCount()2254     public void testIsWithinQuotaLocked_WithQuotaBump_JobCount() {
2255         setDischarging();
2256         int standbyBucket = WORKING_INDEX;
2257         setStandbyBucket(standbyBucket);
2258         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
2259                 20 * MINUTE_IN_MILLIS);
2260         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10);
2261         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0);
2262         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 1);
2263         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0);
2264         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
2265         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5);
2266 
2267         long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2268         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2269                 createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 10), false);
2270         final ExecutionStats stats;
2271         synchronized (mQuotaController.mLock) {
2272             stats = mQuotaController.getExecutionStatsLocked(
2273                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2274             mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 10);
2275             assertFalse(mQuotaController
2276                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2277             assertEquals(10, stats.jobCountLimit);
2278         }
2279         mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
2280         synchronized (mQuotaController.mLock) {
2281             assertTrue(mQuotaController
2282                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2283             assertEquals(11, stats.jobCountLimit);
2284         }
2285 
2286         advanceElapsedClock(HOUR_IN_MILLIS);
2287 
2288         synchronized (mQuotaController.mLock) {
2289             assertTrue(mQuotaController
2290                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2291             assertEquals(11, stats.jobCountLimit);
2292         }
2293 
2294         // Emulate a quota bump while some jobs are executing
2295         JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1);
2296         JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2);
2297 
2298         synchronized (mQuotaController.mLock) {
2299             mQuotaController.maybeStartTrackingJobLocked(job1, null);
2300             mQuotaController.prepareForExecutionLocked(job1);
2301         }
2302 
2303         advanceElapsedClock(MINUTE_IN_MILLIS);
2304         mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
2305         synchronized (mQuotaController.mLock) {
2306             assertTrue(mQuotaController
2307                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2308             assertEquals(12, stats.jobCountLimit);
2309             mQuotaController.maybeStartTrackingJobLocked(job2, null);
2310             mQuotaController.prepareForExecutionLocked(job2);
2311         }
2312 
2313         advanceElapsedClock(MINUTE_IN_MILLIS);
2314         synchronized (mQuotaController.mLock) {
2315             mQuotaController.maybeStopTrackingJobLocked(job1, null, false);
2316             mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
2317             assertFalse(mQuotaController
2318                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2319             assertEquals(12, stats.jobCountLimit);
2320         }
2321 
2322         // Phase out the first session
2323         advanceElapsedClock(3 * MINUTE_IN_MILLIS);
2324         synchronized (mQuotaController.mLock) {
2325             assertTrue(mQuotaController
2326                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2327             assertEquals(12, stats.jobCountLimit);
2328         }
2329 
2330         // Phase out the first quota bump
2331         advanceElapsedClock(7 * HOUR_IN_MILLIS);
2332         synchronized (mQuotaController.mLock) {
2333             assertTrue(mQuotaController
2334                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2335             assertEquals(11, stats.jobCountLimit);
2336         }
2337     }
2338 
2339     @Test
testIsWithinQuotaLocked_WithQuotaBump_SessionCount()2340     public void testIsWithinQuotaLocked_WithQuotaBump_SessionCount() {
2341         setDischarging();
2342         int standbyBucket = WORKING_INDEX;
2343         setStandbyBucket(standbyBucket);
2344         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
2345                 20 * MINUTE_IN_MILLIS);
2346         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 2);
2347         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0);
2348         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0);
2349         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 1);
2350         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS);
2351         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5);
2352 
2353         long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2354         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2355                 createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1), false);
2356         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2357                 createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), false);
2358         final ExecutionStats stats;
2359         synchronized (mQuotaController.mLock) {
2360             stats = mQuotaController.getExecutionStatsLocked(
2361                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2362             assertFalse(mQuotaController
2363                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2364             assertEquals(2, stats.sessionCountLimit);
2365         }
2366         mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
2367         synchronized (mQuotaController.mLock) {
2368             assertTrue(mQuotaController
2369                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2370             assertEquals(3, stats.sessionCountLimit);
2371         }
2372 
2373         advanceElapsedClock(HOUR_IN_MILLIS);
2374 
2375         synchronized (mQuotaController.mLock) {
2376             assertTrue(mQuotaController
2377                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2378             assertEquals(3, stats.sessionCountLimit);
2379         }
2380 
2381         // Emulate a quota bump while some jobs are executing
2382         JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1);
2383         JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2);
2384 
2385         synchronized (mQuotaController.mLock) {
2386             mQuotaController.maybeStartTrackingJobLocked(job1, null);
2387             mQuotaController.prepareForExecutionLocked(job1);
2388         }
2389 
2390         advanceElapsedClock(MINUTE_IN_MILLIS);
2391         mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
2392         synchronized (mQuotaController.mLock) {
2393             mQuotaController.maybeStopTrackingJobLocked(job1, null, false);
2394             assertTrue(mQuotaController
2395                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2396             assertEquals(4, stats.sessionCountLimit);
2397         }
2398 
2399         advanceElapsedClock(MINUTE_IN_MILLIS);
2400         synchronized (mQuotaController.mLock) {
2401             mQuotaController.maybeStartTrackingJobLocked(job2, null);
2402             mQuotaController.prepareForExecutionLocked(job2);
2403         }
2404 
2405         advanceElapsedClock(MINUTE_IN_MILLIS);
2406         synchronized (mQuotaController.mLock) {
2407             mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
2408             assertFalse(mQuotaController
2409                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2410             assertEquals(4, stats.sessionCountLimit);
2411         }
2412 
2413         // Phase out the first session
2414         advanceElapsedClock(2 * MINUTE_IN_MILLIS);
2415         synchronized (mQuotaController.mLock) {
2416             assertTrue(mQuotaController
2417                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2418             assertEquals(4, stats.sessionCountLimit);
2419         }
2420 
2421         // Phase out the first quota bump
2422         advanceElapsedClock(7 * HOUR_IN_MILLIS);
2423         synchronized (mQuotaController.mLock) {
2424             assertTrue(mQuotaController
2425                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
2426             assertEquals(3, stats.sessionCountLimit);
2427         }
2428     }
2429 
2430     @Test
testIsWithinEJQuotaLocked_NeverApp()2431     public void testIsWithinEJQuotaLocked_NeverApp() {
2432         JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1);
2433         setStandbyBucket(NEVER_INDEX, js);
2434         synchronized (mQuotaController.mLock) {
2435             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2436         }
2437     }
2438 
2439     @Test
testIsWithinEJQuotaLocked_Charging()2440     public void testIsWithinEJQuotaLocked_Charging() {
2441         setCharging();
2442         JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_Charging", 1);
2443         synchronized (mQuotaController.mLock) {
2444             assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
2445         }
2446     }
2447 
2448     @Test
testIsWithinEJQuotaLocked_UnderDuration()2449     public void testIsWithinEJQuotaLocked_UnderDuration() {
2450         setDischarging();
2451         JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_UnderDuration", 1);
2452         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2453         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2454                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2455         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2456                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2457         synchronized (mQuotaController.mLock) {
2458             assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
2459         }
2460     }
2461 
2462     @Test
testIsWithinEJQuotaLocked_OverDuration()2463     public void testIsWithinEJQuotaLocked_OverDuration() {
2464         setDischarging();
2465         JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_OverDuration", 1);
2466         setStandbyBucket(FREQUENT_INDEX, js);
2467         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
2468         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2469         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2470                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2471         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2472                 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2473         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2474                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
2475         synchronized (mQuotaController.mLock) {
2476             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2477         }
2478     }
2479 
2480     @Test
testIsWithinEJQuotaLocked_TimingSession()2481     public void testIsWithinEJQuotaLocked_TimingSession() {
2482         setDischarging();
2483         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
2484         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2485         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS);
2486         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS);
2487         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS);
2488         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
2489         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 8 * MINUTE_IN_MILLIS);
2490 
2491         JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TimingSession", 1);
2492         for (int i = 0; i < 25; ++i) {
2493             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2494                     createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS,
2495                             2), true);
2496 
2497             synchronized (mQuotaController.mLock) {
2498                 setStandbyBucket(ACTIVE_INDEX, js);
2499                 assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions",
2500                         i < 19, mQuotaController.isWithinEJQuotaLocked(js));
2501 
2502                 setStandbyBucket(WORKING_INDEX, js);
2503                 assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions",
2504                         i < 14, mQuotaController.isWithinEJQuotaLocked(js));
2505 
2506                 setStandbyBucket(FREQUENT_INDEX, js);
2507                 assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions",
2508                         i < 12, mQuotaController.isWithinEJQuotaLocked(js));
2509 
2510                 setStandbyBucket(RARE_INDEX, js);
2511                 assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions",
2512                         i < 9, mQuotaController.isWithinEJQuotaLocked(js));
2513 
2514                 setStandbyBucket(RESTRICTED_INDEX, js);
2515                 assertEquals("Restricted has incorrect quota status with " + (i + 1) + " sessions",
2516                         i < 7, mQuotaController.isWithinEJQuotaLocked(js));
2517             }
2518         }
2519     }
2520 
2521     /**
2522      * Tests that Timers properly track sessions when an app is added and removed from the temp
2523      * allowlist.
2524      */
2525     @Test
testIsWithinEJQuotaLocked_TempAllowlisting()2526     public void testIsWithinEJQuotaLocked_TempAllowlisting() {
2527         setDischarging();
2528         JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting", 1);
2529         setStandbyBucket(FREQUENT_INDEX, js);
2530         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
2531         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2532         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2533                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2534         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2535                 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2536         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2537                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
2538         synchronized (mQuotaController.mLock) {
2539             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2540         }
2541 
2542         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
2543         final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
2544         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
2545         Handler handler = mQuotaController.getHandler();
2546         spyOn(handler);
2547 
2548         // Apps on the temp allowlist should be able to schedule & start EJs, even if they're out
2549         // of quota (as long as they are in the temp allowlist grace period).
2550         mTempAllowlistListener.onAppAdded(mSourceUid);
2551         synchronized (mQuotaController.mLock) {
2552             assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
2553         }
2554         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2555         mTempAllowlistListener.onAppRemoved(mSourceUid);
2556         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2557         // Still in grace period
2558         synchronized (mQuotaController.mLock) {
2559             assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
2560         }
2561         advanceElapsedClock(6 * SECOND_IN_MILLIS);
2562         // Out of grace period.
2563         synchronized (mQuotaController.mLock) {
2564             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2565         }
2566     }
2567 
2568     @Test
testIsWithinEJQuotaLocked_TempAllowlisting_Restricted()2569     public void testIsWithinEJQuotaLocked_TempAllowlisting_Restricted() {
2570         setDischarging();
2571         JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting_Restricted", 1);
2572         setStandbyBucket(RESTRICTED_INDEX, js);
2573         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
2574         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2575         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2576                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2577         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2578                 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2579         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2580                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
2581         synchronized (mQuotaController.mLock) {
2582             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2583         }
2584 
2585         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
2586         final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
2587         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
2588         Handler handler = mQuotaController.getHandler();
2589         spyOn(handler);
2590 
2591         // The temp allowlist should not enable RESTRICTED apps' to schedule & start EJs if they're
2592         // out of quota.
2593         mTempAllowlistListener.onAppAdded(mSourceUid);
2594         synchronized (mQuotaController.mLock) {
2595             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2596         }
2597         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2598         mTempAllowlistListener.onAppRemoved(mSourceUid);
2599         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2600         // Still in grace period
2601         synchronized (mQuotaController.mLock) {
2602             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2603         }
2604         advanceElapsedClock(6 * SECOND_IN_MILLIS);
2605         // Out of grace period.
2606         synchronized (mQuotaController.mLock) {
2607             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2608         }
2609     }
2610 
2611     /**
2612      * Tests that Timers properly track sessions when an app becomes top and is closed.
2613      */
2614     @Test
testIsWithinEJQuotaLocked_TopApp()2615     public void testIsWithinEJQuotaLocked_TopApp() {
2616         setDischarging();
2617         JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TopApp", 1);
2618         setStandbyBucket(FREQUENT_INDEX, js);
2619         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS);
2620         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2621         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2622                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2623         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2624                 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true);
2625         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2626                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true);
2627         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
2628         synchronized (mQuotaController.mLock) {
2629             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2630         }
2631 
2632         final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
2633         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, gracePeriodMs);
2634         Handler handler = mQuotaController.getHandler();
2635         spyOn(handler);
2636 
2637         // Apps on top should be able to schedule & start EJs, even if they're out
2638         // of quota (as long as they are in the top grace period).
2639         setProcessState(ActivityManager.PROCESS_STATE_TOP);
2640         synchronized (mQuotaController.mLock) {
2641             assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
2642         }
2643         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2644         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
2645         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2646         // Still in grace period
2647         synchronized (mQuotaController.mLock) {
2648             assertTrue(mQuotaController.isWithinEJQuotaLocked(js));
2649         }
2650         advanceElapsedClock(6 * SECOND_IN_MILLIS);
2651         // Out of grace period.
2652         synchronized (mQuotaController.mLock) {
2653             assertFalse(mQuotaController.isWithinEJQuotaLocked(js));
2654         }
2655     }
2656 
2657     @Test
testMaybeScheduleCleanupAlarmLocked()2658     public void testMaybeScheduleCleanupAlarmLocked() {
2659         // No sessions saved yet.
2660         synchronized (mQuotaController.mLock) {
2661             mQuotaController.maybeScheduleCleanupAlarmLocked();
2662         }
2663         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
2664 
2665         // Test with only one timing session saved.
2666         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2667         final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
2668         mQuotaController.saveTimingSession(0, "com.android.test",
2669                 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1), false);
2670         synchronized (mQuotaController.mLock) {
2671             mQuotaController.maybeScheduleCleanupAlarmLocked();
2672         }
2673         verify(mAlarmManager, times(1))
2674                 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
2675 
2676         // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
2677         mQuotaController.saveTimingSession(0, "com.android.test",
2678                 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
2679         mQuotaController.saveTimingSession(0, "com.android.test",
2680                 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
2681         synchronized (mQuotaController.mLock) {
2682             mQuotaController.maybeScheduleCleanupAlarmLocked();
2683         }
2684         verify(mAlarmManager, times(1))
2685                 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
2686     }
2687 
2688     @Test
testMaybeScheduleStartAlarmLocked_Active()2689     public void testMaybeScheduleStartAlarmLocked_Active() {
2690         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
2691         // because it schedules an alarm too. Prevent it from doing so.
2692         spyOn(mQuotaController);
2693         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
2694 
2695         // Active window size is 10 minutes.
2696         final int standbyBucket = ACTIVE_INDEX;
2697         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
2698 
2699         JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
2700         setStandbyBucket(standbyBucket, jobStatus);
2701         synchronized (mQuotaController.mLock) {
2702             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2703         }
2704 
2705         // No sessions saved yet.
2706         synchronized (mQuotaController.mLock) {
2707             mQuotaController.maybeScheduleStartAlarmLocked(
2708                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2709         }
2710         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2711                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2712 
2713         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2714         // Test with timing sessions out of window but still under max execution limit.
2715         final long expectedAlarmTime =
2716                 (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
2717         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2718                 createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1), false);
2719         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2720                 createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1), false);
2721         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2722                 createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1), false);
2723         synchronized (mQuotaController.mLock) {
2724             mQuotaController.maybeScheduleStartAlarmLocked(
2725                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2726         }
2727         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2728                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2729 
2730         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2731                 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1), false);
2732         synchronized (mQuotaController.mLock) {
2733             mQuotaController.maybeScheduleStartAlarmLocked(
2734                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2735         }
2736         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2737                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2738 
2739         synchronized (mQuotaController.mLock) {
2740             mQuotaController.prepareForExecutionLocked(jobStatus);
2741         }
2742         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
2743         synchronized (mQuotaController.mLock) {
2744             // Timer has only been going for 5 minutes in the past 10 minutes, which is under the
2745             // window size limit, but the total execution time for the past 24 hours is 6 hours, so
2746             // the job no longer has quota.
2747             assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
2748             mQuotaController.maybeScheduleStartAlarmLocked(
2749                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2750         }
2751         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
2752                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2753     }
2754 
2755     @Test
testMaybeScheduleStartAlarmLocked_WorkingSet()2756     public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
2757         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
2758         // because it schedules an alarm too. Prevent it from doing so.
2759         spyOn(mQuotaController);
2760         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
2761 
2762         // Working set window size is 2 hours.
2763         final int standbyBucket = WORKING_INDEX;
2764 
2765         JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_WorkingSet", 1);
2766         setStandbyBucket(standbyBucket, jobStatus);
2767         synchronized (mQuotaController.mLock) {
2768             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2769             // No sessions saved yet.
2770             mQuotaController.maybeScheduleStartAlarmLocked(
2771                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2772         }
2773         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2774                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2775 
2776         // Test with timing sessions out of window.
2777         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2778         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2779                 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
2780         synchronized (mQuotaController.mLock) {
2781             mQuotaController.maybeScheduleStartAlarmLocked(
2782                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2783         }
2784         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2785                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2786 
2787         // Test with timing sessions in window but still in quota.
2788         final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
2789         // Counting backwards, the quota will come back one minute before the end.
2790         final long expectedAlarmTime =
2791                 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
2792         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2793                 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1), false);
2794         synchronized (mQuotaController.mLock) {
2795             mQuotaController.maybeScheduleStartAlarmLocked(
2796                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2797         }
2798         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2799                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2800 
2801         // Add some more sessions, but still in quota.
2802         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2803                 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
2804         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2805                 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false);
2806         synchronized (mQuotaController.mLock) {
2807             mQuotaController.maybeScheduleStartAlarmLocked(
2808                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2809         }
2810         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2811                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2812 
2813         // Test when out of quota.
2814         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2815                 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
2816         synchronized (mQuotaController.mLock) {
2817             mQuotaController.maybeScheduleStartAlarmLocked(
2818                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2819         }
2820         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
2821                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2822 
2823         // Alarm already scheduled, so make sure it's not scheduled again.
2824         synchronized (mQuotaController.mLock) {
2825             mQuotaController.maybeScheduleStartAlarmLocked(
2826                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2827         }
2828         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
2829                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2830     }
2831 
2832     @Test
testMaybeScheduleStartAlarmLocked_Frequent()2833     public void testMaybeScheduleStartAlarmLocked_Frequent() {
2834         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
2835         // because it schedules an alarm too. Prevent it from doing so.
2836         spyOn(mQuotaController);
2837         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
2838 
2839         synchronized (mQuotaController.mLock) {
2840             mQuotaController.maybeStartTrackingJobLocked(
2841                     createJobStatus("testMaybeScheduleStartAlarmLocked_Frequent", 1), null);
2842         }
2843 
2844         // Frequent window size is 8 hours.
2845         final int standbyBucket = FREQUENT_INDEX;
2846 
2847         // No sessions saved yet.
2848         synchronized (mQuotaController.mLock) {
2849             mQuotaController.maybeScheduleStartAlarmLocked(
2850                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2851         }
2852         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2853                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2854 
2855         // Test with timing sessions out of window.
2856         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2857         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2858                 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
2859         synchronized (mQuotaController.mLock) {
2860             mQuotaController.maybeScheduleStartAlarmLocked(
2861                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2862         }
2863         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2864                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2865 
2866         // Test with timing sessions in window but still in quota.
2867         final long start = now - (6 * HOUR_IN_MILLIS);
2868         final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
2869         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2870                 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
2871         synchronized (mQuotaController.mLock) {
2872             mQuotaController.maybeScheduleStartAlarmLocked(
2873                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2874         }
2875         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2876                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2877 
2878         // Add some more sessions, but still in quota.
2879         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2880                 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
2881         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2882                 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
2883         synchronized (mQuotaController.mLock) {
2884             mQuotaController.maybeScheduleStartAlarmLocked(
2885                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2886         }
2887         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2888                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2889 
2890         // Test when out of quota.
2891         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2892                 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
2893         synchronized (mQuotaController.mLock) {
2894             mQuotaController.maybeScheduleStartAlarmLocked(
2895                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2896         }
2897         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
2898                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2899 
2900         // Alarm already scheduled, so make sure it's not scheduled again.
2901         synchronized (mQuotaController.mLock) {
2902             mQuotaController.maybeScheduleStartAlarmLocked(
2903                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
2904         }
2905         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
2906                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2907     }
2908 
2909     /**
2910      * Test that QC handles invalid cases where an app is in the NEVER bucket but has still run
2911      * jobs.
2912      */
2913     @Test
testMaybeScheduleStartAlarmLocked_Never_EffectiveNotNever()2914     public void testMaybeScheduleStartAlarmLocked_Never_EffectiveNotNever() {
2915         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
2916         // because it schedules an alarm too. Prevent it from doing so.
2917         spyOn(mQuotaController);
2918         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
2919 
2920         synchronized (mQuotaController.mLock) {
2921             mQuotaController.maybeStartTrackingJobLocked(
2922                     createJobStatus("testMaybeScheduleStartAlarmLocked_Never", 1), null);
2923         }
2924 
2925         // The app is really in the NEVER bucket but is elevated somehow (eg via uidActive).
2926         setStandbyBucket(NEVER_INDEX);
2927         final int effectiveStandbyBucket = FREQUENT_INDEX;
2928 
2929         // No sessions saved yet.
2930         synchronized (mQuotaController.mLock) {
2931             mQuotaController.maybeScheduleStartAlarmLocked(
2932                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
2933         }
2934         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2935                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2936 
2937         // Test with timing sessions out of window.
2938         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2939         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2940                 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
2941         synchronized (mQuotaController.mLock) {
2942             mQuotaController.maybeScheduleStartAlarmLocked(
2943                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
2944         }
2945         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2946                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2947 
2948         // Test with timing sessions in window but still in quota.
2949         final long start = now - (6 * HOUR_IN_MILLIS);
2950         final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
2951         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2952                 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
2953         synchronized (mQuotaController.mLock) {
2954             mQuotaController.maybeScheduleStartAlarmLocked(
2955                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
2956         }
2957         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2958                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2959 
2960         // Add some more sessions, but still in quota.
2961         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2962                 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
2963         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2964                 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
2965         synchronized (mQuotaController.mLock) {
2966             mQuotaController.maybeScheduleStartAlarmLocked(
2967                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
2968         }
2969         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
2970                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2971 
2972         // Test when out of quota.
2973         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2974                 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
2975         synchronized (mQuotaController.mLock) {
2976             mQuotaController.maybeScheduleStartAlarmLocked(
2977                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
2978         }
2979         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
2980                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2981 
2982         // Alarm already scheduled, so make sure it's not scheduled again.
2983         synchronized (mQuotaController.mLock) {
2984             mQuotaController.maybeScheduleStartAlarmLocked(
2985                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
2986         }
2987         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
2988                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2989     }
2990 
2991     @Test
testMaybeScheduleStartAlarmLocked_Rare()2992     public void testMaybeScheduleStartAlarmLocked_Rare() {
2993         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
2994         // because it schedules an alarm too. Prevent it from doing so.
2995         spyOn(mQuotaController);
2996         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
2997 
2998         // Rare window size is 24 hours.
2999         final int standbyBucket = RARE_INDEX;
3000 
3001         JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Rare", 1);
3002         setStandbyBucket(standbyBucket, jobStatus);
3003         synchronized (mQuotaController.mLock) {
3004             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3005         }
3006 
3007         // Prevent timing session throttling from affecting the test.
3008         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 50);
3009 
3010         // No sessions saved yet.
3011         synchronized (mQuotaController.mLock) {
3012             mQuotaController.maybeScheduleStartAlarmLocked(
3013                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3014         }
3015         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
3016                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3017 
3018         // Test with timing sessions out of window.
3019         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
3020         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3021                 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false);
3022         synchronized (mQuotaController.mLock) {
3023             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
3024         }
3025         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
3026                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3027 
3028         // Test with timing sessions in window but still in quota.
3029         final long start = now - (6 * HOUR_IN_MILLIS);
3030         // Counting backwards, the first minute in the session is over the allowed time, so it
3031         // needs to be excluded.
3032         final long expectedAlarmTime =
3033                 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
3034                         + mQcConstants.IN_QUOTA_BUFFER_MS;
3035         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3036                 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false);
3037         synchronized (mQuotaController.mLock) {
3038             mQuotaController.maybeScheduleStartAlarmLocked(
3039                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3040         }
3041         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
3042                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3043 
3044         // Add some more sessions, but still in quota.
3045         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3046                 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false);
3047         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3048                 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false);
3049         synchronized (mQuotaController.mLock) {
3050             mQuotaController.maybeScheduleStartAlarmLocked(
3051                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3052         }
3053         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
3054                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3055 
3056         // Test when out of quota.
3057         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3058                 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1), false);
3059         synchronized (mQuotaController.mLock) {
3060             mQuotaController.maybeScheduleStartAlarmLocked(
3061                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3062         }
3063         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3064                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3065 
3066         // Alarm already scheduled, so make sure it's not scheduled again.
3067         synchronized (mQuotaController.mLock) {
3068             mQuotaController.maybeScheduleStartAlarmLocked(
3069                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3070         }
3071         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3072                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3073     }
3074 
3075     /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
3076     @Test
testMaybeScheduleStartAlarmLocked_BucketChange()3077     public void testMaybeScheduleStartAlarmLocked_BucketChange() {
3078         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
3079         // because it schedules an alarm too. Prevent it from doing so.
3080         spyOn(mQuotaController);
3081         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
3082 
3083         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
3084 
3085         // Affects rare bucket
3086         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3087                 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3), false);
3088         // Affects frequent and rare buckets
3089         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3090                 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3), false);
3091         // Affects working, frequent, and rare buckets
3092         final long outOfQuotaTime = now - HOUR_IN_MILLIS;
3093         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3094                 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10), false);
3095         // Affects all buckets
3096         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3097                 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3), false);
3098 
3099         InOrder inOrder = inOrder(mAlarmManager);
3100 
3101         JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_BucketChange", 1);
3102 
3103         // Start in ACTIVE bucket.
3104         setStandbyBucket(ACTIVE_INDEX, jobStatus);
3105         synchronized (mQuotaController.mLock) {
3106             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3107             mQuotaController.maybeScheduleStartAlarmLocked(
3108                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
3109         }
3110         inOrder.verify(mAlarmManager, timeout(1000).times(0))
3111                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3112         inOrder.verify(mAlarmManager, timeout(1000).times(0))
3113                 .cancel(any(AlarmManager.OnAlarmListener.class));
3114 
3115         // And down from there.
3116         final long expectedWorkingAlarmTime =
3117                 outOfQuotaTime + (2 * HOUR_IN_MILLIS)
3118                         + mQcConstants.IN_QUOTA_BUFFER_MS;
3119         setStandbyBucket(WORKING_INDEX, jobStatus);
3120         synchronized (mQuotaController.mLock) {
3121             mQuotaController.maybeScheduleStartAlarmLocked(
3122                     SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
3123         }
3124         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3125                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
3126                 eq(TAG_QUOTA_CHECK), any(), any());
3127 
3128         final long expectedFrequentAlarmTime =
3129                 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
3130                         + mQcConstants.IN_QUOTA_BUFFER_MS;
3131         setStandbyBucket(FREQUENT_INDEX, jobStatus);
3132         synchronized (mQuotaController.mLock) {
3133             mQuotaController.maybeScheduleStartAlarmLocked(
3134                     SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
3135         }
3136         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3137                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
3138                 eq(TAG_QUOTA_CHECK), any(), any());
3139 
3140         final long expectedRareAlarmTime =
3141                 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
3142                         + mQcConstants.IN_QUOTA_BUFFER_MS;
3143         setStandbyBucket(RARE_INDEX, jobStatus);
3144         synchronized (mQuotaController.mLock) {
3145             mQuotaController.maybeScheduleStartAlarmLocked(
3146                     SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
3147         }
3148         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3149                 anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3150 
3151         // And back up again.
3152         setStandbyBucket(FREQUENT_INDEX, jobStatus);
3153         synchronized (mQuotaController.mLock) {
3154             mQuotaController.maybeScheduleStartAlarmLocked(
3155                     SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
3156         }
3157         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3158                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
3159                 eq(TAG_QUOTA_CHECK), any(), any());
3160 
3161         setStandbyBucket(WORKING_INDEX, jobStatus);
3162         synchronized (mQuotaController.mLock) {
3163             mQuotaController.maybeScheduleStartAlarmLocked(
3164                     SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
3165         }
3166         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3167                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
3168                 eq(TAG_QUOTA_CHECK), any(), any());
3169 
3170         setStandbyBucket(ACTIVE_INDEX, jobStatus);
3171         synchronized (mQuotaController.mLock) {
3172             mQuotaController.maybeScheduleStartAlarmLocked(
3173                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
3174         }
3175         inOrder.verify(mAlarmManager, timeout(1000).times(0))
3176                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3177         inOrder.verify(mAlarmManager, timeout(1000).times(1))
3178                 .cancel(any(AlarmManager.OnAlarmListener.class));
3179     }
3180 
3181     @Test
testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow()3182     public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() {
3183         // Set rate limiting period different from allowed time to confirm code sets based on
3184         // the former.
3185         final int standbyBucket = WORKING_INDEX;
3186         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
3187                 10 * MINUTE_IN_MILLIS);
3188         setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 5 * MINUTE_IN_MILLIS);
3189 
3190         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
3191 
3192         JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1);
3193         setStandbyBucket(standbyBucket, jobStatus);
3194         synchronized (mQuotaController.mLock) {
3195             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3196         }
3197 
3198         ExecutionStats stats;
3199         synchronized (mQuotaController.mLock) {
3200             stats = mQuotaController.getExecutionStatsLocked(
3201                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3202         }
3203         stats.jobCountInRateLimitingWindow =
3204                 mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW + 2;
3205 
3206         // Invalid time in the past, so the count shouldn't be used.
3207         stats.jobRateLimitExpirationTimeElapsed = now - 5 * MINUTE_IN_MILLIS / 2;
3208         synchronized (mQuotaController.mLock) {
3209             mQuotaController.maybeScheduleStartAlarmLocked(
3210                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3211         }
3212         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
3213                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3214 
3215         // Valid time in the future, so the count should be used.
3216         stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2;
3217         final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
3218         synchronized (mQuotaController.mLock) {
3219             mQuotaController.maybeScheduleStartAlarmLocked(
3220                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3221         }
3222         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3223                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
3224                 eq(TAG_QUOTA_CHECK), any(), any());
3225     }
3226 
3227     /**
3228      * Tests that the start alarm is properly rescheduled if the earliest session that contributes
3229      * to the app being out of quota contributes less than the quota buffer time.
3230      */
3231     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues()3232     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() {
3233         // Use the default values
3234         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
3235         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
3236         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
3237     }
3238 
3239     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize()3240     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
3241         // Make sure any new value is used correctly.
3242         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS,
3243                 mQcConstants.IN_QUOTA_BUFFER_MS * 2);
3244 
3245         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
3246         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
3247         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
3248     }
3249 
3250     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime()3251     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
3252         // Make sure any new value is used correctly.
3253         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
3254                 mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2);
3255 
3256         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
3257         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
3258         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
3259     }
3260 
3261     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime()3262     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
3263         // Make sure any new value is used correctly.
3264         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS,
3265                 mQcConstants.MAX_EXECUTION_TIME_MS / 2);
3266 
3267         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
3268         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
3269         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
3270     }
3271 
3272     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything()3273     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
3274         // Make sure any new value is used correctly.
3275         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS,
3276                 mQcConstants.IN_QUOTA_BUFFER_MS * 2);
3277         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
3278                 mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2);
3279         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS,
3280                 mQcConstants.MAX_EXECUTION_TIME_MS / 2);
3281 
3282         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
3283         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
3284         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
3285     }
3286 
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck()3287     private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() {
3288         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
3289         // because it schedules an alarm too. Prevent it from doing so.
3290         spyOn(mQuotaController);
3291         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
3292 
3293         synchronized (mQuotaController.mLock) {
3294             mQuotaController.maybeStartTrackingJobLocked(
3295                     createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null);
3296         }
3297 
3298         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
3299         // Working set window size is 2 hours.
3300         final int standbyBucket = WORKING_INDEX;
3301         final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
3302         final long remainingTimeMs =
3303                 mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS - contributionMs;
3304 
3305         // Session straddles edge of bucket window. Only the contribution should be counted towards
3306         // the quota.
3307         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3308                 createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
3309                         3 * MINUTE_IN_MILLIS + contributionMs, 3), false);
3310         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3311                 createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2), false);
3312         // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
3313         // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
3314         final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
3315                 + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs);
3316         synchronized (mQuotaController.mLock) {
3317             mQuotaController.maybeScheduleStartAlarmLocked(
3318                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3319         }
3320         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3321                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3322     }
3323 
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck()3324     private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
3325         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
3326         // because it schedules an alarm too. Prevent it from doing so.
3327         spyOn(mQuotaController);
3328         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
3329 
3330         synchronized (mQuotaController.mLock) {
3331             mQuotaController.maybeStartTrackingJobLocked(
3332                     createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null);
3333         }
3334 
3335         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
3336         // Working set window size is 2 hours.
3337         final int standbyBucket = WORKING_INDEX;
3338         final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
3339         final long remainingTimeMs = mQcConstants.MAX_EXECUTION_TIME_MS - contributionMs;
3340 
3341         // Session straddles edge of 24 hour window. Only the contribution should be counted towards
3342         // the quota.
3343         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3344                 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
3345                         3 * MINUTE_IN_MILLIS + contributionMs, 3), false);
3346         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
3347                 createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300), false);
3348         // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
3349         // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
3350         final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS
3351                 + 24 * HOUR_IN_MILLIS
3352                 + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs);
3353         synchronized (mQuotaController.mLock) {
3354             mQuotaController.maybeScheduleStartAlarmLocked(
3355                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
3356         }
3357         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
3358                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
3359     }
3360 
3361     @Test
testConstantsUpdating_ValidValues()3362     public void testConstantsUpdating_ValidValues() {
3363         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
3364                 8 * MINUTE_IN_MILLIS);
3365         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
3366                 5 * MINUTE_IN_MILLIS);
3367         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
3368                 7 * MINUTE_IN_MILLIS);
3369         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
3370                 2 * MINUTE_IN_MILLIS);
3371         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 4 * MINUTE_IN_MILLIS);
3372         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
3373                 11 * MINUTE_IN_MILLIS);
3374         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS);
3375         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 99 * MINUTE_IN_MILLIS);
3376         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS);
3377         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS);
3378         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS);
3379         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 60 * MINUTE_IN_MILLIS);
3380         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 120 * MINUTE_IN_MILLIS);
3381         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 3 * HOUR_IN_MILLIS);
3382         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, 6000);
3383         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, 5000);
3384         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 4000);
3385         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 3000);
3386         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RARE, 2000);
3387         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, 2000);
3388         setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * MINUTE_IN_MILLIS);
3389         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 500);
3390         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, 600);
3391         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 500);
3392         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 400);
3393         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 300);
3394         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 200);
3395         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RESTRICTED, 100);
3396         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 50);
3397         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
3398                 10 * SECOND_IN_MILLIS);
3399         setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 7 * MINUTE_IN_MILLIS);
3400         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 3 * HOUR_IN_MILLIS);
3401         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 2 * HOUR_IN_MILLIS);
3402         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 90 * MINUTE_IN_MILLIS);
3403         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 1 * HOUR_IN_MILLIS);
3404         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 30 * MINUTE_IN_MILLIS);
3405         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 27 * MINUTE_IN_MILLIS);
3406         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, 7 * HOUR_IN_MILLIS);
3407         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, 10 * HOUR_IN_MILLIS);
3408         setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 12 * HOUR_IN_MILLIS);
3409         setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 10 * MINUTE_IN_MILLIS);
3410         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 87 * SECOND_IN_MILLIS);
3411         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, 86 * SECOND_IN_MILLIS);
3412         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 85 * SECOND_IN_MILLIS);
3413         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS,
3414                 84 * SECOND_IN_MILLIS);
3415         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS);
3416         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS,
3417                 93 * SECOND_IN_MILLIS);
3418         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 92);
3419         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 91);
3420         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 90 * MINUTE_IN_MILLIS);
3421         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 89);
3422 
3423         assertEquals(8 * MINUTE_IN_MILLIS,
3424                 mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
3425         assertEquals(5 * MINUTE_IN_MILLIS,
3426                 mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]);
3427         assertEquals(7 * MINUTE_IN_MILLIS,
3428                 mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]);
3429         assertEquals(2 * MINUTE_IN_MILLIS,
3430                 mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
3431         assertEquals(4 * MINUTE_IN_MILLIS,
3432                 mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]);
3433         assertEquals(11 * MINUTE_IN_MILLIS,
3434                 mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]);
3435         assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
3436         assertEquals(99 * MINUTE_IN_MILLIS,
3437                 mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]);
3438         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
3439         assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
3440         assertEquals(45 * MINUTE_IN_MILLIS,
3441                 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
3442         assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
3443         assertEquals(120 * MINUTE_IN_MILLIS,
3444                 mQuotaController.getBucketWindowSizes()[RESTRICTED_INDEX]);
3445         assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
3446         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
3447         assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow());
3448         assertEquals(6000, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]);
3449         assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
3450         assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
3451         assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
3452         assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
3453         assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]);
3454         assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
3455         assertEquals(600, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]);
3456         assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
3457         assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
3458         assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
3459         assertEquals(200, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
3460         assertEquals(100, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]);
3461         assertEquals(10 * SECOND_IN_MILLIS,
3462                 mQuotaController.getTimingSessionCoalescingDurationMs());
3463         assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs());
3464         assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]);
3465         assertEquals(2 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]);
3466         assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
3467         assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
3468         assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
3469         assertEquals(27 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
3470         assertEquals(7 * HOUR_IN_MILLIS, mQuotaController.getEjLimitAdditionInstallerMs());
3471         assertEquals(10 * HOUR_IN_MILLIS, mQuotaController.getEjLimitAdditionSpecialMs());
3472         assertEquals(12 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
3473         assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs());
3474         assertEquals(87 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
3475         assertEquals(86 * SECOND_IN_MILLIS, mQuotaController.getEJRewardInteractionMs());
3476         assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
3477         assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
3478         assertEquals(83 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());
3479         assertEquals(93 * SECOND_IN_MILLIS, mQuotaController.getQuotaBumpAdditionDurationMs());
3480         assertEquals(92, mQuotaController.getQuotaBumpAdditionJobCount());
3481         assertEquals(91, mQuotaController.getQuotaBumpAdditionSessionCount());
3482         assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
3483         assertEquals(89, mQuotaController.getQuotaBumpLimit());
3484     }
3485 
3486     @Test
testConstantsUpdating_InvalidValues()3487     public void testConstantsUpdating_InvalidValues() {
3488         // Test negatives/too low.
3489         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, -MINUTE_IN_MILLIS);
3490         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, -MINUTE_IN_MILLIS);
3491         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, -MINUTE_IN_MILLIS);
3492         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, -MINUTE_IN_MILLIS);
3493         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, -MINUTE_IN_MILLIS);
3494         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
3495                 -MINUTE_IN_MILLIS);
3496         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS);
3497         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, -MINUTE_IN_MILLIS);
3498         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS);
3499         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS);
3500         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS);
3501         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, -MINUTE_IN_MILLIS);
3502         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, -MINUTE_IN_MILLIS);
3503         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, -MINUTE_IN_MILLIS);
3504         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, -1);
3505         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, -1);
3506         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 1);
3507         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 1);
3508         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RARE, 1);
3509         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, -1);
3510         setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * SECOND_IN_MILLIS);
3511         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 0);
3512         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, -1);
3513         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, -1);
3514         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 0);
3515         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, -3);
3516         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 0);
3517         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RESTRICTED, -5);
3518         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 0);
3519         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, -1);
3520         setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, -1);
3521         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, -1);
3522         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, -1);
3523         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, -1);
3524         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, -1);
3525         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, -1);
3526         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, -1);
3527         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, -1);
3528         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, -1);
3529         setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, -1);
3530         setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, -1);
3531         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, -1);
3532         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, -1);
3533         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, -1);
3534         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1);
3535         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1);
3536         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, -1);
3537         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, -1);
3538         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, -1);
3539         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 59 * MINUTE_IN_MILLIS);
3540         setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, -1);
3541 
3542         assertEquals(MINUTE_IN_MILLIS,
3543                 mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
3544         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]);
3545         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]);
3546         assertEquals(MINUTE_IN_MILLIS,
3547                 mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
3548         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]);
3549         assertEquals(MINUTE_IN_MILLIS,
3550                 mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]);
3551         assertEquals(0, mQuotaController.getInQuotaBufferMs());
3552         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]);
3553         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
3554         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
3555         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
3556         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
3557         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RESTRICTED_INDEX]);
3558         assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
3559         assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
3560         assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow());
3561         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]);
3562         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
3563         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
3564         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
3565         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
3566         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]);
3567         assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
3568         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]);
3569         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
3570         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
3571         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
3572         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
3573         assertEquals(0, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]);
3574         assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs());
3575         assertEquals(0, mQuotaController.getMinQuotaCheckDelayMs());
3576         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]);
3577         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]);
3578         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
3579         assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
3580         assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
3581         assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
3582         assertEquals(0, mQuotaController.getEjLimitAdditionInstallerMs());
3583         assertEquals(0, mQuotaController.getEjLimitAdditionSpecialMs());
3584         assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
3585         assertEquals(1, mQuotaController.getEJTopAppTimeChunkSizeMs());
3586         assertEquals(10 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
3587         assertEquals(5 * SECOND_IN_MILLIS, mQuotaController.getEJRewardInteractionMs());
3588         assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs());
3589         assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs());
3590         assertEquals(0, mQuotaController.getEJGracePeriodTopAppMs());
3591         assertEquals(0, mQuotaController.getQuotaBumpAdditionDurationMs());
3592         assertEquals(0, mQuotaController.getQuotaBumpAdditionJobCount());
3593         assertEquals(0, mQuotaController.getQuotaBumpAdditionSessionCount());
3594         assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
3595         assertEquals(0, mQuotaController.getQuotaBumpLimit());
3596 
3597         // Invalid configurations.
3598         // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
3599         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
3600                 10 * MINUTE_IN_MILLIS);
3601         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS,
3602                 10 * MINUTE_IN_MILLIS);
3603         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
3604                 10 * MINUTE_IN_MILLIS);
3605         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
3606                 2 * MINUTE_IN_MILLIS);
3607         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 10 * MINUTE_IN_MILLIS);
3608         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
3609                 10 * MINUTE_IN_MILLIS);
3610         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 5 * MINUTE_IN_MILLIS);
3611 
3612         assertTrue(mQuotaController.getInQuotaBufferMs()
3613                 <= mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
3614 
3615         // Test larger than a day. Controller should cap at one day.
3616         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS,
3617                 25 * HOUR_IN_MILLIS);
3618         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
3619         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS,
3620                 25 * HOUR_IN_MILLIS);
3621         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS,
3622                 25 * HOUR_IN_MILLIS);
3623         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 25 * HOUR_IN_MILLIS);
3624         setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS,
3625                 25 * HOUR_IN_MILLIS);
3626         setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS);
3627         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 25 * HOUR_IN_MILLIS);
3628         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
3629         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS);
3630         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
3631         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 25 * HOUR_IN_MILLIS);
3632         setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 30 * 24 * HOUR_IN_MILLIS);
3633         setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 25 * HOUR_IN_MILLIS);
3634         setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 25 * HOUR_IN_MILLIS);
3635         setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS,
3636                 25 * HOUR_IN_MILLIS);
3637         setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 25 * HOUR_IN_MILLIS);
3638         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 25 * HOUR_IN_MILLIS);
3639         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 25 * HOUR_IN_MILLIS);
3640         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 25 * HOUR_IN_MILLIS);
3641         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 25 * HOUR_IN_MILLIS);
3642         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 25 * HOUR_IN_MILLIS);
3643         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 25 * HOUR_IN_MILLIS);
3644         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, 25 * HOUR_IN_MILLIS);
3645         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, 25 * HOUR_IN_MILLIS);
3646         setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS);
3647         setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 25 * HOUR_IN_MILLIS);
3648         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
3649         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, 25 * HOUR_IN_MILLIS);
3650         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 25 * HOUR_IN_MILLIS);
3651         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS);
3652         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS);
3653         setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS);
3654 
3655         assertEquals(24 * HOUR_IN_MILLIS,
3656                 mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]);
3657         assertEquals(24 * HOUR_IN_MILLIS,
3658                 mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]);
3659         assertEquals(24 * HOUR_IN_MILLIS,
3660                 mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]);
3661         assertEquals(24 * HOUR_IN_MILLIS,
3662                 mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]);
3663         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]);
3664         assertEquals(24 * HOUR_IN_MILLIS,
3665                 mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]);
3666         assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
3667         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]);
3668         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
3669         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
3670         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
3671         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
3672         assertEquals(7 * 24 * HOUR_IN_MILLIS,
3673                 mQuotaController.getBucketWindowSizes()[RESTRICTED_INDEX]);
3674         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
3675         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
3676         assertEquals(15 * MINUTE_IN_MILLIS,
3677                 mQuotaController.getTimingSessionCoalescingDurationMs());
3678         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs());
3679         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]);
3680         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]);
3681         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]);
3682         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]);
3683         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]);
3684         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]);
3685         assertEquals(0, mQuotaController.getEjLimitAdditionInstallerMs());
3686         assertEquals(0, mQuotaController.getEjLimitAdditionSpecialMs());
3687         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs());
3688         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs());
3689         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardTopAppMs());
3690         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardInteractionMs());
3691         assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs());
3692         assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs());
3693         assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs());
3694         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs());
3695     }
3696 
3697     /** Tests that TimingSessions aren't saved when the device is charging. */
3698     @Test
testTimerTracking_Charging()3699     public void testTimerTracking_Charging() {
3700         setCharging();
3701 
3702         JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1);
3703         synchronized (mQuotaController.mLock) {
3704             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3705         }
3706 
3707         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3708 
3709         synchronized (mQuotaController.mLock) {
3710             mQuotaController.prepareForExecutionLocked(jobStatus);
3711         }
3712         advanceElapsedClock(5 * SECOND_IN_MILLIS);
3713         synchronized (mQuotaController.mLock) {
3714             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
3715         }
3716         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3717     }
3718 
3719     /** Tests that TimingSessions are saved properly when the device is discharging. */
3720     @Test
testTimerTracking_Discharging()3721     public void testTimerTracking_Discharging() {
3722         setDischarging();
3723         setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
3724 
3725         JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
3726         synchronized (mQuotaController.mLock) {
3727             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3728         }
3729 
3730         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3731 
3732         List<TimingSession> expected = new ArrayList<>();
3733 
3734         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
3735         synchronized (mQuotaController.mLock) {
3736             mQuotaController.prepareForExecutionLocked(jobStatus);
3737         }
3738         advanceElapsedClock(5 * SECOND_IN_MILLIS);
3739         synchronized (mQuotaController.mLock) {
3740             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
3741         }
3742         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
3743         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3744 
3745         // Test overlapping jobs.
3746         JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2);
3747         synchronized (mQuotaController.mLock) {
3748             mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
3749         }
3750 
3751         JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3);
3752         synchronized (mQuotaController.mLock) {
3753             mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
3754         }
3755 
3756         advanceElapsedClock(SECOND_IN_MILLIS);
3757 
3758         start = JobSchedulerService.sElapsedRealtimeClock.millis();
3759         synchronized (mQuotaController.mLock) {
3760             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3761             mQuotaController.prepareForExecutionLocked(jobStatus);
3762         }
3763         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3764         synchronized (mQuotaController.mLock) {
3765             mQuotaController.prepareForExecutionLocked(jobStatus2);
3766         }
3767         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3768         synchronized (mQuotaController.mLock) {
3769             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
3770         }
3771         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3772         synchronized (mQuotaController.mLock) {
3773             mQuotaController.prepareForExecutionLocked(jobStatus3);
3774         }
3775         advanceElapsedClock(20 * SECOND_IN_MILLIS);
3776         synchronized (mQuotaController.mLock) {
3777             mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
3778         }
3779         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3780         synchronized (mQuotaController.mLock) {
3781             mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
3782         }
3783         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
3784         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3785     }
3786 
3787     /**
3788      * Tests that TimingSessions are saved properly when the device alternates between
3789      * charging and discharging.
3790      */
3791     @Test
testTimerTracking_ChargingAndDischarging()3792     public void testTimerTracking_ChargingAndDischarging() {
3793         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
3794 
3795         JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
3796         synchronized (mQuotaController.mLock) {
3797             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3798         }
3799         JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
3800         synchronized (mQuotaController.mLock) {
3801             mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
3802         }
3803         JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3);
3804         synchronized (mQuotaController.mLock) {
3805             mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
3806         }
3807         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3808         List<TimingSession> expected = new ArrayList<>();
3809 
3810         // A job starting while charging. Only the portion that runs during the discharging period
3811         // should be counted.
3812         setCharging();
3813 
3814         synchronized (mQuotaController.mLock) {
3815             mQuotaController.prepareForExecutionLocked(jobStatus);
3816         }
3817         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3818         setDischarging();
3819         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
3820         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3821         synchronized (mQuotaController.mLock) {
3822             mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
3823         }
3824         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
3825         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3826 
3827         advanceElapsedClock(SECOND_IN_MILLIS);
3828 
3829         // One job starts while discharging, spans a charging session, and ends after the charging
3830         // session. Only the portions during the discharging periods should be counted. This should
3831         // result in two TimingSessions. A second job starts while discharging and ends within the
3832         // charging session. Only the portion during the first discharging portion should be
3833         // counted. A third job starts and ends within the charging session. The third job
3834         // shouldn't be included in either job count.
3835         setDischarging();
3836         start = JobSchedulerService.sElapsedRealtimeClock.millis();
3837         synchronized (mQuotaController.mLock) {
3838             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3839             mQuotaController.prepareForExecutionLocked(jobStatus);
3840         }
3841         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3842         synchronized (mQuotaController.mLock) {
3843             mQuotaController.prepareForExecutionLocked(jobStatus2);
3844         }
3845         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3846         setCharging();
3847         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
3848         synchronized (mQuotaController.mLock) {
3849             mQuotaController.prepareForExecutionLocked(jobStatus3);
3850         }
3851         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3852         synchronized (mQuotaController.mLock) {
3853             mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
3854         }
3855         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3856         synchronized (mQuotaController.mLock) {
3857             mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
3858         }
3859         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3860         setDischarging();
3861         start = JobSchedulerService.sElapsedRealtimeClock.millis();
3862         advanceElapsedClock(20 * SECOND_IN_MILLIS);
3863         synchronized (mQuotaController.mLock) {
3864             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
3865         }
3866         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
3867         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3868 
3869         // A job starting while discharging and ending while charging. Only the portion that runs
3870         // during the discharging period should be counted.
3871         setDischarging();
3872         start = JobSchedulerService.sElapsedRealtimeClock.millis();
3873         synchronized (mQuotaController.mLock) {
3874             mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
3875             mQuotaController.prepareForExecutionLocked(jobStatus2);
3876         }
3877         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3878         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
3879         setCharging();
3880         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3881         synchronized (mQuotaController.mLock) {
3882             mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
3883         }
3884         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3885     }
3886 
3887     /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
3888     @Test
testTimerTracking_AllBackground()3889     public void testTimerTracking_AllBackground() {
3890         setDischarging();
3891         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
3892 
3893         JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
3894         synchronized (mQuotaController.mLock) {
3895             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3896         }
3897         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3898 
3899         List<TimingSession> expected = new ArrayList<>();
3900 
3901         // Test single job.
3902         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
3903         synchronized (mQuotaController.mLock) {
3904             mQuotaController.prepareForExecutionLocked(jobStatus);
3905         }
3906         advanceElapsedClock(5 * SECOND_IN_MILLIS);
3907         synchronized (mQuotaController.mLock) {
3908             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
3909         }
3910         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
3911         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3912 
3913         // Test overlapping jobs.
3914         JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
3915         synchronized (mQuotaController.mLock) {
3916             mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
3917         }
3918 
3919         JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
3920         synchronized (mQuotaController.mLock) {
3921             mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
3922         }
3923 
3924         advanceElapsedClock(SECOND_IN_MILLIS);
3925 
3926         start = JobSchedulerService.sElapsedRealtimeClock.millis();
3927         synchronized (mQuotaController.mLock) {
3928             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3929             mQuotaController.prepareForExecutionLocked(jobStatus);
3930         }
3931         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3932         synchronized (mQuotaController.mLock) {
3933             mQuotaController.prepareForExecutionLocked(jobStatus2);
3934         }
3935         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3936         synchronized (mQuotaController.mLock) {
3937             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
3938         }
3939         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3940         synchronized (mQuotaController.mLock) {
3941             mQuotaController.prepareForExecutionLocked(jobStatus3);
3942         }
3943         advanceElapsedClock(20 * SECOND_IN_MILLIS);
3944         synchronized (mQuotaController.mLock) {
3945             mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
3946         }
3947         advanceElapsedClock(10 * SECOND_IN_MILLIS);
3948         synchronized (mQuotaController.mLock) {
3949             mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
3950         }
3951         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
3952         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3953     }
3954 
3955     /** Tests that Timers don't count foreground jobs. */
3956     @Test
testTimerTracking_AllForeground()3957     public void testTimerTracking_AllForeground() {
3958         setDischarging();
3959 
3960         JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
3961         setProcessState(ActivityManager.PROCESS_STATE_TOP);
3962         synchronized (mQuotaController.mLock) {
3963             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
3964         }
3965 
3966         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3967 
3968         synchronized (mQuotaController.mLock) {
3969             mQuotaController.prepareForExecutionLocked(jobStatus);
3970         }
3971         advanceElapsedClock(5 * SECOND_IN_MILLIS);
3972         // Change to a state that should still be considered foreground.
3973         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
3974         advanceElapsedClock(5 * SECOND_IN_MILLIS);
3975         synchronized (mQuotaController.mLock) {
3976             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
3977         }
3978         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3979     }
3980 
3981     /**
3982      * Tests that Timers properly track sessions when switching between foreground and background
3983      * states.
3984      */
3985     @Test
testTimerTracking_ForegroundAndBackground()3986     public void testTimerTracking_ForegroundAndBackground() {
3987         setDischarging();
3988 
3989         JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
3990         JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
3991         JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
3992         synchronized (mQuotaController.mLock) {
3993             mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
3994             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
3995             mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
3996         }
3997         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
3998         List<TimingSession> expected = new ArrayList<>();
3999 
4000         // UID starts out inactive.
4001         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
4002         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
4003         synchronized (mQuotaController.mLock) {
4004             mQuotaController.prepareForExecutionLocked(jobBg1);
4005         }
4006         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4007         synchronized (mQuotaController.mLock) {
4008             mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
4009         }
4010         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
4011         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4012 
4013         advanceElapsedClock(SECOND_IN_MILLIS);
4014 
4015         // Bg job starts while inactive, spans an entire active session, and ends after the
4016         // active session.
4017         // App switching to foreground state then fg job starts.
4018         // App remains in foreground state after coming to foreground, so there should only be one
4019         // session.
4020         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4021         synchronized (mQuotaController.mLock) {
4022             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
4023             mQuotaController.prepareForExecutionLocked(jobBg2);
4024         }
4025         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4026         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
4027         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
4028         synchronized (mQuotaController.mLock) {
4029             mQuotaController.prepareForExecutionLocked(jobFg3);
4030         }
4031         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4032         synchronized (mQuotaController.mLock) {
4033             mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
4034         }
4035         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4036         synchronized (mQuotaController.mLock) {
4037             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
4038         }
4039         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4040 
4041         advanceElapsedClock(SECOND_IN_MILLIS);
4042 
4043         // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
4044         // "inactive" and then bg job 2 starts. Then fg job ends.
4045         // This should result in two TimingSessions:
4046         //  * The first should have a count of 1
4047         //  * The second should have a count of 2 since it will include both jobs
4048         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4049         synchronized (mQuotaController.mLock) {
4050             mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
4051             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
4052             mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
4053         }
4054         setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
4055         synchronized (mQuotaController.mLock) {
4056             mQuotaController.prepareForExecutionLocked(jobBg1);
4057         }
4058         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4059         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
4060         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
4061         synchronized (mQuotaController.mLock) {
4062             mQuotaController.prepareForExecutionLocked(jobFg3);
4063         }
4064         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4065         synchronized (mQuotaController.mLock) {
4066             mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
4067         }
4068         advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
4069         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4070         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
4071         synchronized (mQuotaController.mLock) {
4072             mQuotaController.prepareForExecutionLocked(jobBg2);
4073         }
4074         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4075         synchronized (mQuotaController.mLock) {
4076             mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
4077         }
4078         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4079         synchronized (mQuotaController.mLock) {
4080             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
4081         }
4082         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
4083         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4084     }
4085 
4086     /**
4087      * Tests that Timers don't track job counts while in the foreground.
4088      */
4089     @Test
testTimerTracking_JobCount_Foreground()4090     public void testTimerTracking_JobCount_Foreground() {
4091         setDischarging();
4092 
4093         final int standbyBucket = ACTIVE_INDEX;
4094         JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1);
4095         JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2);
4096 
4097         synchronized (mQuotaController.mLock) {
4098             mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
4099             mQuotaController.maybeStartTrackingJobLocked(jobFg2, null);
4100         }
4101         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4102         ExecutionStats stats;
4103         synchronized (mQuotaController.mLock) {
4104             stats = mQuotaController.getExecutionStatsLocked(
4105                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
4106         }
4107         assertEquals(0, stats.jobCountInRateLimitingWindow);
4108 
4109         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
4110         synchronized (mQuotaController.mLock) {
4111             mQuotaController.prepareForExecutionLocked(jobFg1);
4112         }
4113         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4114         synchronized (mQuotaController.mLock) {
4115             mQuotaController.prepareForExecutionLocked(jobFg2);
4116         }
4117         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4118         synchronized (mQuotaController.mLock) {
4119             mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
4120         }
4121         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4122         synchronized (mQuotaController.mLock) {
4123             mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
4124         }
4125         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4126 
4127         assertEquals(0, stats.jobCountInRateLimitingWindow);
4128     }
4129 
4130     /**
4131      * Tests that Timers properly track job counts while in the background.
4132      */
4133     @Test
testTimerTracking_JobCount_Background()4134     public void testTimerTracking_JobCount_Background() {
4135         final int standbyBucket = WORKING_INDEX;
4136         JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1);
4137         JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2);
4138         ExecutionStats stats;
4139         synchronized (mQuotaController.mLock) {
4140             mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
4141             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
4142 
4143             stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
4144                     SOURCE_PACKAGE, standbyBucket);
4145         }
4146         assertEquals(0, stats.jobCountInRateLimitingWindow);
4147 
4148         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
4149         synchronized (mQuotaController.mLock) {
4150             mQuotaController.prepareForExecutionLocked(jobBg1);
4151         }
4152         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4153         synchronized (mQuotaController.mLock) {
4154             mQuotaController.prepareForExecutionLocked(jobBg2);
4155         }
4156         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4157         synchronized (mQuotaController.mLock) {
4158             mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
4159         }
4160         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4161         synchronized (mQuotaController.mLock) {
4162             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
4163         }
4164 
4165         assertEquals(2, stats.jobCountInRateLimitingWindow);
4166     }
4167 
4168     /**
4169      * Tests that Timers properly track overlapping top and background jobs.
4170      */
4171     @Test
testTimerTracking_TopAndNonTop()4172     public void testTimerTracking_TopAndNonTop() {
4173         setDischarging();
4174 
4175         JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1);
4176         JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2);
4177         JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3);
4178         JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4);
4179         synchronized (mQuotaController.mLock) {
4180             mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
4181             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
4182             mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
4183             mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
4184         }
4185         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4186         List<TimingSession> expected = new ArrayList<>();
4187 
4188         // UID starts out inactive.
4189         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
4190         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
4191         synchronized (mQuotaController.mLock) {
4192             mQuotaController.prepareForExecutionLocked(jobBg1);
4193         }
4194         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4195         synchronized (mQuotaController.mLock) {
4196             mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
4197         }
4198         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
4199         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4200 
4201         advanceElapsedClock(SECOND_IN_MILLIS);
4202 
4203         // Bg job starts while inactive, spans an entire active session, and ends after the
4204         // active session.
4205         // App switching to top state then fg job starts.
4206         // App remains in top state after coming to top, so there should only be one
4207         // session.
4208         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4209         synchronized (mQuotaController.mLock) {
4210             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
4211             mQuotaController.prepareForExecutionLocked(jobBg2);
4212         }
4213         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4214         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
4215         setProcessState(ActivityManager.PROCESS_STATE_TOP);
4216         synchronized (mQuotaController.mLock) {
4217             mQuotaController.prepareForExecutionLocked(jobTop);
4218         }
4219         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4220         synchronized (mQuotaController.mLock) {
4221             mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
4222         }
4223         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4224         synchronized (mQuotaController.mLock) {
4225             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
4226         }
4227         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4228 
4229         advanceElapsedClock(SECOND_IN_MILLIS);
4230 
4231         // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
4232         // foreground_service and a new job starts. Shortly after, uid goes
4233         // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
4234         // This should result in two TimingSessions:
4235         //  * The first should have a count of 1
4236         //  * The second should have a count of 2, which accounts for the bg2 and fg, but not top
4237         //    jobs.
4238         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4239         synchronized (mQuotaController.mLock) {
4240             mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
4241             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
4242             mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
4243         }
4244         setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
4245         synchronized (mQuotaController.mLock) {
4246             mQuotaController.prepareForExecutionLocked(jobBg1);
4247         }
4248         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4249         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
4250         setProcessState(ActivityManager.PROCESS_STATE_TOP);
4251         synchronized (mQuotaController.mLock) {
4252             mQuotaController.prepareForExecutionLocked(jobTop);
4253         }
4254         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4255         synchronized (mQuotaController.mLock) {
4256             mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
4257         }
4258         advanceElapsedClock(5 * SECOND_IN_MILLIS);
4259         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
4260         synchronized (mQuotaController.mLock) {
4261             mQuotaController.prepareForExecutionLocked(jobFg1);
4262         }
4263         advanceElapsedClock(5 * SECOND_IN_MILLIS);
4264         setProcessState(ActivityManager.PROCESS_STATE_TOP);
4265         advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
4266         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4267         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
4268         synchronized (mQuotaController.mLock) {
4269             mQuotaController.prepareForExecutionLocked(jobBg2);
4270         }
4271         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4272         synchronized (mQuotaController.mLock) {
4273             mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
4274         }
4275         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4276         synchronized (mQuotaController.mLock) {
4277             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
4278             mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
4279         }
4280         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
4281         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4282     }
4283 
4284     /**
4285      * Tests that Timers properly track regular sessions when an app is added and removed from the
4286      * temp allowlist.
4287      */
4288     @Test
testTimerTracking_TempAllowlisting()4289     public void testTimerTracking_TempAllowlisting() {
4290         // None of these should be affected purely by the temp allowlist changing.
4291         setDischarging();
4292         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
4293         final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
4294         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
4295         Handler handler = mQuotaController.getHandler();
4296         spyOn(handler);
4297 
4298         JobStatus job1 = createJobStatus("testTimerTracking_TempAllowlisting", 1);
4299         JobStatus job2 = createJobStatus("testTimerTracking_TempAllowlisting", 2);
4300         JobStatus job3 = createJobStatus("testTimerTracking_TempAllowlisting", 3);
4301         JobStatus job4 = createJobStatus("testTimerTracking_TempAllowlisting", 4);
4302         JobStatus job5 = createJobStatus("testTimerTracking_TempAllowlisting", 5);
4303         synchronized (mQuotaController.mLock) {
4304             mQuotaController.maybeStartTrackingJobLocked(job1, null);
4305         }
4306         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4307         List<TimingSession> expected = new ArrayList<>();
4308 
4309         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
4310         synchronized (mQuotaController.mLock) {
4311             mQuotaController.prepareForExecutionLocked(job1);
4312         }
4313         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4314         synchronized (mQuotaController.mLock) {
4315             mQuotaController.maybeStopTrackingJobLocked(job1, job1, true);
4316         }
4317         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
4318         assertEquals(expected,
4319                 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4320 
4321         advanceElapsedClock(SECOND_IN_MILLIS);
4322 
4323         // Job starts after app is added to temp allowlist and stops before removal.
4324         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4325         mTempAllowlistListener.onAppAdded(mSourceUid);
4326         synchronized (mQuotaController.mLock) {
4327             mQuotaController.maybeStartTrackingJobLocked(job2, null);
4328             mQuotaController.prepareForExecutionLocked(job2);
4329         }
4330         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4331         synchronized (mQuotaController.mLock) {
4332             mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
4333         }
4334         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
4335         assertEquals(expected,
4336                 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4337 
4338         // Job starts after app is added to temp allowlist and stops after removal,
4339         // before grace period ends.
4340         mTempAllowlistListener.onAppAdded(mSourceUid);
4341         synchronized (mQuotaController.mLock) {
4342             mQuotaController.maybeStartTrackingJobLocked(job3, null);
4343             mQuotaController.prepareForExecutionLocked(job3);
4344         }
4345         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4346         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4347         mTempAllowlistListener.onAppRemoved(mSourceUid);
4348         long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
4349         advanceElapsedClock(elapsedGracePeriodMs);
4350         synchronized (mQuotaController.mLock) {
4351             mQuotaController.maybeStopTrackingJobLocked(job3, null, false);
4352         }
4353         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + elapsedGracePeriodMs, 1));
4354         assertEquals(expected,
4355                 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4356 
4357         advanceElapsedClock(SECOND_IN_MILLIS);
4358         elapsedGracePeriodMs += SECOND_IN_MILLIS;
4359 
4360         // Job starts during grace period and ends after grace period ends
4361         synchronized (mQuotaController.mLock) {
4362             mQuotaController.maybeStartTrackingJobLocked(job4, null);
4363             mQuotaController.prepareForExecutionLocked(job4);
4364         }
4365         final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
4366         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4367         advanceElapsedClock(remainingGracePeriod);
4368         // Wait for handler to update Timer
4369         // Can't directly evaluate the message because for some reason, the captured message returns
4370         // the wrong 'what' even though the correct message goes to the handler and the correct
4371         // path executes.
4372         verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
4373         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4374         expected.add(createTimingSession(start, remainingGracePeriod + 10 * SECOND_IN_MILLIS, 1));
4375         synchronized (mQuotaController.mLock) {
4376             mQuotaController.maybeStopTrackingJobLocked(job4, job4, true);
4377         }
4378         assertEquals(expected,
4379                 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4380 
4381         // Job starts and runs completely after temp allowlist grace period.
4382         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4383         start = JobSchedulerService.sElapsedRealtimeClock.millis();
4384         synchronized (mQuotaController.mLock) {
4385             mQuotaController.maybeStartTrackingJobLocked(job5, null);
4386             mQuotaController.prepareForExecutionLocked(job5);
4387         }
4388         advanceElapsedClock(10 * SECOND_IN_MILLIS);
4389         synchronized (mQuotaController.mLock) {
4390             mQuotaController.maybeStopTrackingJobLocked(job5, job5, true);
4391         }
4392         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
4393         assertEquals(expected,
4394                 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
4395     }
4396 
4397     /**
4398      * Tests that TOP jobs aren't stopped when an app runs out of quota.
4399      */
4400     @Test
testTracking_OutOfQuota_ForegroundAndBackground()4401     public void testTracking_OutOfQuota_ForegroundAndBackground() {
4402         setDischarging();
4403 
4404         JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
4405         JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
4406         trackJobs(jobBg, jobTop);
4407         setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
4408         // Now the package only has 20 seconds to run.
4409         final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
4410         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4411                 createTimingSession(
4412                         JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
4413                         10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false);
4414 
4415         InOrder inOrder = inOrder(mJobSchedulerService);
4416 
4417         // UID starts out inactive.
4418         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
4419         // Start the job.
4420         synchronized (mQuotaController.mLock) {
4421             mQuotaController.prepareForExecutionLocked(jobBg);
4422         }
4423         advanceElapsedClock(remainingTimeMs / 2);
4424         // New job starts after UID is in the foreground. Since the app is now in the foreground, it
4425         // should continue to have remainingTimeMs / 2 time remaining.
4426         setProcessState(ActivityManager.PROCESS_STATE_TOP);
4427         synchronized (mQuotaController.mLock) {
4428             mQuotaController.prepareForExecutionLocked(jobTop);
4429         }
4430         advanceElapsedClock(remainingTimeMs);
4431 
4432         // Wait for some extra time to allow for job processing.
4433         inOrder.verify(mJobSchedulerService,
4434                         timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
4435                 .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
4436         synchronized (mQuotaController.mLock) {
4437             assertEquals(remainingTimeMs / 2,
4438                     mQuotaController.getRemainingExecutionTimeLocked(jobBg));
4439             assertEquals(remainingTimeMs / 2,
4440                     mQuotaController.getRemainingExecutionTimeLocked(jobTop));
4441         }
4442         // Go to a background state.
4443         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
4444         advanceElapsedClock(remainingTimeMs / 2 + 1);
4445         inOrder.verify(mJobSchedulerService,
4446                         timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
4447                 .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
4448         // Top job should still be allowed to run.
4449         assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4450         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4451 
4452         // New jobs to run.
4453         JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
4454         JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
4455         JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
4456         setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
4457 
4458         advanceElapsedClock(20 * SECOND_IN_MILLIS);
4459         setProcessState(ActivityManager.PROCESS_STATE_TOP);
4460         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
4461                 .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
4462         trackJobs(jobFg, jobTop);
4463         synchronized (mQuotaController.mLock) {
4464             mQuotaController.prepareForExecutionLocked(jobTop);
4465         }
4466         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4467         assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4468         assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4469 
4470         // App still in foreground so everything should be in quota.
4471         advanceElapsedClock(20 * SECOND_IN_MILLIS);
4472         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
4473         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4474         assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4475         assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4476 
4477         advanceElapsedClock(20 * SECOND_IN_MILLIS);
4478         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
4479         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
4480                 .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
4481         // App is now in background and out of quota. Fg should now change to out of quota since it
4482         // wasn't started. Top should remain in quota since it started when the app was in TOP.
4483         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4484         assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4485         assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4486         trackJobs(jobBg2);
4487         assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4488     }
4489 
4490     /**
4491      * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
4492      * its quota.
4493      */
4494     @Test
testTracking_OutOfQuota()4495     public void testTracking_OutOfQuota() {
4496         JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
4497         synchronized (mQuotaController.mLock) {
4498             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
4499         }
4500         setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
4501         setProcessState(ActivityManager.PROCESS_STATE_HOME);
4502         // Now the package only has two seconds to run.
4503         final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
4504         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4505                 createTimingSession(
4506                         JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
4507                         10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false);
4508 
4509         // Start the job.
4510         synchronized (mQuotaController.mLock) {
4511             mQuotaController.prepareForExecutionLocked(jobStatus);
4512         }
4513         advanceElapsedClock(remainingTimeMs);
4514 
4515         // Wait for some extra time to allow for job processing.
4516         verify(mJobSchedulerService,
4517                 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
4518                 .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
4519         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4520         assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(),
4521                 jobStatus.getWhenStandbyDeferred());
4522     }
4523 
4524     /**
4525      * Tests that a job is properly handled when it's at the edge of its quota and the old quota is
4526      * being phased out.
4527      */
4528     @Test
testTracking_RollingQuota()4529     public void testTracking_RollingQuota() {
4530         JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
4531         synchronized (mQuotaController.mLock) {
4532             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
4533         }
4534         setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
4535         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
4536         Handler handler = mQuotaController.getHandler();
4537         spyOn(handler);
4538 
4539         long now = JobSchedulerService.sElapsedRealtimeClock.millis();
4540         final long remainingTimeMs = SECOND_IN_MILLIS;
4541         // The package only has one second to run, but this session is at the edge of the rolling
4542         // window, so as the package "reaches its quota" it will have more to keep running.
4543         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4544                 createTimingSession(now - 2 * HOUR_IN_MILLIS,
4545                         10 * SECOND_IN_MILLIS - remainingTimeMs, 1), false);
4546         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4547                 createTimingSession(now - HOUR_IN_MILLIS,
4548                         9 * MINUTE_IN_MILLIS + 50 * SECOND_IN_MILLIS, 1), false);
4549 
4550         synchronized (mQuotaController.mLock) {
4551             assertEquals(remainingTimeMs,
4552                     mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
4553 
4554             // Start the job.
4555             mQuotaController.prepareForExecutionLocked(jobStatus);
4556         }
4557         advanceElapsedClock(remainingTimeMs);
4558 
4559         // Wait for some extra time to allow for job processing.
4560         verify(mJobSchedulerService,
4561                 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
4562                 .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
4563         assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4564         // The job used up the remaining quota, but in that time, the same amount of time in the
4565         // old TimingSession also fell out of the quota window, so it should still have the same
4566         // amount of remaining time left its quota.
4567         synchronized (mQuotaController.mLock) {
4568             assertEquals(remainingTimeMs,
4569                     mQuotaController.getRemainingExecutionTimeLocked(
4570                             SOURCE_USER_ID, SOURCE_PACKAGE));
4571         }
4572         // Handler is told to check when the quota will be consumed, not when the initial
4573         // remaining time is over.
4574         verify(handler, atLeast(1)).sendMessageDelayed(
4575                 argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA),
4576                 eq(10 * SECOND_IN_MILLIS));
4577         verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
4578 
4579         // After 10 seconds, the job should finally be out of quota.
4580         advanceElapsedClock(10 * SECOND_IN_MILLIS - remainingTimeMs);
4581         // Wait for some extra time to allow for job processing.
4582         verify(mJobSchedulerService,
4583                 timeout(12 * SECOND_IN_MILLIS).times(1))
4584                 .onControllerStateChanged(argThat(jobs -> jobs.size() == 1));
4585         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4586         verify(handler, never()).sendMessageDelayed(any(), anyInt());
4587     }
4588 
4589     /**
4590      * Tests that the start alarm is properly scheduled when a job has been throttled due to the job
4591      * count rate limiting.
4592      */
4593     @Test
testStartAlarmScheduled_JobCount_RateLimitingWindow()4594     public void testStartAlarmScheduled_JobCount_RateLimitingWindow() {
4595         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
4596         // because it schedules an alarm too. Prevent it from doing so.
4597         spyOn(mQuotaController);
4598         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
4599 
4600         // Essentially disable session throttling.
4601         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, Integer.MAX_VALUE);
4602         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW,
4603                 Integer.MAX_VALUE);
4604 
4605         final int standbyBucket = WORKING_INDEX;
4606         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
4607 
4608         // No sessions saved yet.
4609         synchronized (mQuotaController.mLock) {
4610             mQuotaController.maybeScheduleStartAlarmLocked(
4611                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
4612         }
4613         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
4614                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
4615 
4616         // Ran jobs up to the job limit. All of them should be allowed to run.
4617         for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
4618             JobStatus job = createJobStatus("testStartAlarmScheduled_JobCount_AllowedTime", i);
4619             setStandbyBucket(WORKING_INDEX, job);
4620             synchronized (mQuotaController.mLock) {
4621                 mQuotaController.maybeStartTrackingJobLocked(job, null);
4622                 assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4623                 mQuotaController.prepareForExecutionLocked(job);
4624             }
4625             advanceElapsedClock(SECOND_IN_MILLIS);
4626             synchronized (mQuotaController.mLock) {
4627                 mQuotaController.maybeStopTrackingJobLocked(job, null, false);
4628             }
4629             advanceElapsedClock(SECOND_IN_MILLIS);
4630         }
4631         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
4632         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
4633                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
4634 
4635         // The app is now out of job count quota
4636         JobStatus throttledJob = createJobStatus(
4637                 "testStartAlarmScheduled_JobCount_AllowedTime", 42);
4638         setStandbyBucket(WORKING_INDEX, throttledJob);
4639         synchronized (mQuotaController.mLock) {
4640             mQuotaController.maybeStartTrackingJobLocked(throttledJob, null);
4641         }
4642         assertFalse(throttledJob.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4643 
4644         ExecutionStats stats;
4645         synchronized (mQuotaController.mLock) {
4646             stats = mQuotaController.getExecutionStatsLocked(
4647                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
4648         }
4649         final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
4650         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
4651                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
4652                 eq(TAG_QUOTA_CHECK), any(), any());
4653     }
4654 
4655     /**
4656      * Tests that the start alarm is properly scheduled when a job has been throttled due to the
4657      * session count rate limiting.
4658      */
4659     @Test
testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow()4660     public void testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow() {
4661         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
4662         // because it schedules an alarm too. Prevent it from doing so.
4663         spyOn(mQuotaController);
4664         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
4665 
4666         // Essentially disable job count throttling.
4667         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, Integer.MAX_VALUE);
4668         setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW,
4669                 Integer.MAX_VALUE);
4670         // Make sure throttling is because of COUNT_PER_RATE_LIMITING_WINDOW.
4671         setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT,
4672                 mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW + 1);
4673 
4674         final int standbyBucket = FREQUENT_INDEX;
4675         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
4676 
4677         // No sessions saved yet.
4678         synchronized (mQuotaController.mLock) {
4679             mQuotaController.maybeScheduleStartAlarmLocked(
4680                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
4681         }
4682         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
4683                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
4684 
4685         // Ran jobs up to the job limit. All of them should be allowed to run.
4686         for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
4687             JobStatus job = createJobStatus(
4688                     "testStartAlarmScheduled_TimingSessionCount_AllowedTime", i);
4689             setStandbyBucket(FREQUENT_INDEX, job);
4690             synchronized (mQuotaController.mLock) {
4691                 mQuotaController.maybeStartTrackingJobLocked(job, null);
4692                 assertTrue("Constraint not satisfied for job #" + (i + 1),
4693                         job.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4694                 mQuotaController.prepareForExecutionLocked(job);
4695             }
4696             advanceElapsedClock(SECOND_IN_MILLIS);
4697             synchronized (mQuotaController.mLock) {
4698                 mQuotaController.maybeStopTrackingJobLocked(job, null, false);
4699             }
4700             advanceElapsedClock(SECOND_IN_MILLIS);
4701         }
4702         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
4703         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
4704                 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
4705 
4706         // The app is now out of session count quota
4707         JobStatus throttledJob = createJobStatus(
4708                 "testStartAlarmScheduled_TimingSessionCount_AllowedTime", 42);
4709         synchronized (mQuotaController.mLock) {
4710             mQuotaController.maybeStartTrackingJobLocked(throttledJob, null);
4711         }
4712         assertFalse(throttledJob.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
4713         assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(),
4714                 throttledJob.getWhenStandbyDeferred());
4715 
4716         ExecutionStats stats;
4717         synchronized (mQuotaController.mLock) {
4718             stats = mQuotaController.getExecutionStatsLocked(
4719                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
4720         }
4721         final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed;
4722         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
4723                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
4724                 eq(TAG_QUOTA_CHECK), any(), any());
4725     }
4726 
4727     @Test
testGetRemainingEJExecutionTimeLocked_NoHistory()4728     public void testGetRemainingEJExecutionTimeLocked_NoHistory() {
4729         final long[] limits = mQuotaController.getEJLimitsMs();
4730         for (int i = 0; i < limits.length; ++i) {
4731             setStandbyBucket(i);
4732             assertEquals("Got wrong remaining EJ execution time for bucket #" + i,
4733                     limits[i],
4734                     mQuotaController.getRemainingEJExecutionTimeLocked(
4735                             SOURCE_USER_ID, SOURCE_PACKAGE));
4736         }
4737     }
4738 
4739     @Test
testGetRemainingEJExecutionTimeLocked_AllSessionsWithinWindow()4740     public void testGetRemainingEJExecutionTimeLocked_AllSessionsWithinWindow() {
4741         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
4742         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4743                 createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS, MINUTE_IN_MILLIS, 5),
4744                 true);
4745         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4746                 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4747         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4748                 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4749         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4750                 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4751         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4752                 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4753 
4754         final long[] limits = mQuotaController.getEJLimitsMs();
4755         for (int i = 0; i < limits.length; ++i) {
4756             setStandbyBucket(i);
4757             assertEquals("Got wrong remaining EJ execution time for bucket #" + i,
4758                     i == NEVER_INDEX ? 0 : (limits[i] - 5 * MINUTE_IN_MILLIS),
4759                     mQuotaController.getRemainingEJExecutionTimeLocked(
4760                             SOURCE_USER_ID, SOURCE_PACKAGE));
4761         }
4762     }
4763 
4764     @Test
testGetRemainingEJExecutionTimeLocked_Installer()4765     public void testGetRemainingEJExecutionTimeLocked_Installer() {
4766         PackageInfo pi = new PackageInfo();
4767         pi.packageName = SOURCE_PACKAGE;
4768         pi.requestedPermissions = new String[]{Manifest.permission.INSTALL_PACKAGES};
4769         pi.requestedPermissionsFlags = new int[]{PackageInfo.REQUESTED_PERMISSION_GRANTED};
4770         pi.applicationInfo = new ApplicationInfo();
4771         pi.applicationInfo.uid = mSourceUid;
4772         doReturn(List.of(pi)).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt());
4773         doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission(
4774                 eq(Manifest.permission.INSTALL_PACKAGES), anyInt(), eq(mSourceUid));
4775         mQuotaController.onSystemServicesReady();
4776 
4777         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
4778         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4779                 createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS, MINUTE_IN_MILLIS, 5),
4780                 true);
4781         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4782                 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4783         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4784                 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4785         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4786                 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4787         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4788                 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4789 
4790         final long[] limits = mQuotaController.getEJLimitsMs();
4791         for (int i = 0; i < limits.length; ++i) {
4792             setStandbyBucket(i);
4793             assertEquals("Got wrong remaining EJ execution time for bucket #" + i,
4794                     i == NEVER_INDEX ? 0
4795                             : (limits[i] + mQuotaController.getEjLimitAdditionInstallerMs()
4796                                     - 5 * MINUTE_IN_MILLIS),
4797                     mQuotaController.getRemainingEJExecutionTimeLocked(
4798                             SOURCE_USER_ID, SOURCE_PACKAGE));
4799         }
4800     }
4801 
4802     @Test
testGetRemainingEJExecutionTimeLocked_OneSessionStraddlesEdge()4803     public void testGetRemainingEJExecutionTimeLocked_OneSessionStraddlesEdge() {
4804         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
4805         final long[] limits = mQuotaController.getEJLimitsMs();
4806         for (int i = 0; i < limits.length; ++i) {
4807             synchronized (mQuotaController.mLock) {
4808                 mQuotaController.onUserRemovedLocked(SOURCE_USER_ID);
4809             }
4810             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4811                     createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + MINUTE_IN_MILLIS),
4812                             2 * MINUTE_IN_MILLIS, 5), true);
4813             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4814                     createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4815             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4816                     createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4817             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4818                     createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4819             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4820                     createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4821 
4822             setStandbyBucket(i);
4823             assertEquals("Got wrong remaining EJ execution time for bucket #" + i,
4824                     i == NEVER_INDEX ? 0 : (limits[i] - 5 * MINUTE_IN_MILLIS),
4825                     mQuotaController.getRemainingEJExecutionTimeLocked(
4826                             SOURCE_USER_ID, SOURCE_PACKAGE));
4827         }
4828     }
4829 
4830     @Test
testGetRemainingEJExecutionTimeLocked_WithStaleSessions()4831     public void testGetRemainingEJExecutionTimeLocked_WithStaleSessions() {
4832         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
4833 
4834         final long[] limits = mQuotaController.getEJLimitsMs();
4835         for (int i = 0; i < limits.length; ++i) {
4836             synchronized (mQuotaController.mLock) {
4837                 mQuotaController.onUserRemovedLocked(SOURCE_USER_ID);
4838             }
4839             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4840                     createTimingSession(
4841                             now - (mQcConstants.EJ_WINDOW_SIZE_MS + 10 * MINUTE_IN_MILLIS),
4842                             2 * MINUTE_IN_MILLIS, 5), true);
4843             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4844                     createTimingSession(
4845                             now - (mQcConstants.EJ_WINDOW_SIZE_MS + 5 * MINUTE_IN_MILLIS),
4846                             MINUTE_IN_MILLIS, 5), true);
4847             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4848                     createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + MINUTE_IN_MILLIS),
4849                             2 * MINUTE_IN_MILLIS, 5), true);
4850             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4851                     createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4852             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4853                     createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4854             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4855                     createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4856             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4857                     createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4858 
4859             setStandbyBucket(i);
4860             assertEquals("Got wrong remaining EJ execution time for bucket #" + i,
4861                     i == NEVER_INDEX ? 0 : (limits[i] - 5 * MINUTE_IN_MILLIS),
4862                     mQuotaController.getRemainingEJExecutionTimeLocked(
4863                             SOURCE_USER_ID, SOURCE_PACKAGE));
4864         }
4865     }
4866 
4867     /**
4868      * Tests that getRemainingEJExecutionTimeLocked returns the correct stats soon after device
4869      * startup.
4870      */
4871     @Test
testGetRemainingEJExecutionTimeLocked_BeginningOfTime()4872     public void testGetRemainingEJExecutionTimeLocked_BeginningOfTime() {
4873         // Set time to 3 minutes after boot.
4874         advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
4875         advanceElapsedClock(3 * MINUTE_IN_MILLIS);
4876 
4877         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4878                 createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), true);
4879         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4880                 createTimingSession(150 * SECOND_IN_MILLIS, 15 * SECOND_IN_MILLIS, 5), true);
4881 
4882         final long[] limits = mQuotaController.getEJLimitsMs();
4883         for (int i = 0; i < limits.length; ++i) {
4884             setStandbyBucket(i);
4885             assertEquals("Got wrong remaining EJ execution time for bucket #" + i,
4886                     i == NEVER_INDEX ? 0 : (limits[i] - 75 * SECOND_IN_MILLIS),
4887                     mQuotaController.getRemainingEJExecutionTimeLocked(
4888                             SOURCE_USER_ID, SOURCE_PACKAGE));
4889         }
4890     }
4891 
4892     @Test
testGetRemainingEJExecutionTimeLocked_IncrementalTimingSessions()4893     public void testGetRemainingEJExecutionTimeLocked_IncrementalTimingSessions() {
4894         setDischarging();
4895         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
4896         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS);
4897         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS);
4898         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS);
4899         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
4900         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS);
4901 
4902         for (int i = 1; i <= 25; ++i) {
4903             mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4904                     createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS,
4905                             2), true);
4906 
4907             synchronized (mQuotaController.mLock) {
4908                 setStandbyBucket(ACTIVE_INDEX);
4909                 assertEquals("Active has incorrect remaining EJ time with " + i + " sessions",
4910                         (20 - i) * MINUTE_IN_MILLIS,
4911                         mQuotaController.getRemainingEJExecutionTimeLocked(
4912                                 SOURCE_USER_ID, SOURCE_PACKAGE));
4913 
4914                 setStandbyBucket(WORKING_INDEX);
4915                 assertEquals("Working has incorrect remaining EJ time with " + i + " sessions",
4916                         (15 - i) * MINUTE_IN_MILLIS,
4917                         mQuotaController.getRemainingEJExecutionTimeLocked(
4918                                 SOURCE_USER_ID, SOURCE_PACKAGE));
4919 
4920                 setStandbyBucket(FREQUENT_INDEX);
4921                 assertEquals("Frequent has incorrect remaining EJ time with " + i + " sessions",
4922                         (13 - i) * MINUTE_IN_MILLIS,
4923                         mQuotaController.getRemainingEJExecutionTimeLocked(
4924                                 SOURCE_USER_ID, SOURCE_PACKAGE));
4925 
4926                 setStandbyBucket(RARE_INDEX);
4927                 assertEquals("Rare has incorrect remaining EJ time with " + i + " sessions",
4928                         (10 - i) * MINUTE_IN_MILLIS,
4929                         mQuotaController.getRemainingEJExecutionTimeLocked(
4930                                 SOURCE_USER_ID, SOURCE_PACKAGE));
4931 
4932                 setStandbyBucket(RESTRICTED_INDEX);
4933                 assertEquals("Restricted has incorrect remaining EJ time with " + i + " sessions",
4934                         (5 - i) * MINUTE_IN_MILLIS,
4935                         mQuotaController.getRemainingEJExecutionTimeLocked(
4936                                 SOURCE_USER_ID, SOURCE_PACKAGE));
4937             }
4938         }
4939     }
4940 
4941     @Test
testGetTimeUntilEJQuotaConsumedLocked_NoHistory()4942     public void testGetTimeUntilEJQuotaConsumedLocked_NoHistory() {
4943         final long[] limits = mQuotaController.getEJLimitsMs();
4944         for (int i = 0; i < limits.length; ++i) {
4945             setStandbyBucket(i);
4946             assertEquals("Got wrong time until EJ quota consumed for bucket #" + i,
4947                     limits[i], mQuotaController.getTimeUntilEJQuotaConsumedLocked(
4948                             SOURCE_USER_ID, SOURCE_PACKAGE));
4949         }
4950     }
4951 
4952     @Test
testGetTimeUntilEJQuotaConsumedLocked_AllSessionsWithinWindow()4953     public void testGetTimeUntilEJQuotaConsumedLocked_AllSessionsWithinWindow() {
4954         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
4955         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4956                 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4957         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4958                 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4959         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4960                 createTimingSession(now - 20 * MINUTE_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 5), true);
4961         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4962                 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4963 
4964         final long[] limits = mQuotaController.getEJLimitsMs();
4965         for (int i = 0; i < limits.length; ++i) {
4966             setStandbyBucket(i);
4967             assertEquals("Got wrong time until EJ quota consumed for bucket #" + i,
4968                     i == NEVER_INDEX ? 0 : (limits[i] - 5 * MINUTE_IN_MILLIS),
4969                     mQuotaController.getTimeUntilEJQuotaConsumedLocked(
4970                             SOURCE_USER_ID, SOURCE_PACKAGE));
4971         }
4972     }
4973 
4974     @Test
testGetTimeUntilEJQuotaConsumedLocked_SessionsAtEdgeOfWindow()4975     public void testGetTimeUntilEJQuotaConsumedLocked_SessionsAtEdgeOfWindow() {
4976         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
4977         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4978                 createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS, MINUTE_IN_MILLIS, 5),
4979                 true);
4980         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4981                 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS - 2 * MINUTE_IN_MILLIS),
4982                         MINUTE_IN_MILLIS, 5), true);
4983         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4984                 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS - 10 * MINUTE_IN_MILLIS),
4985                         MINUTE_IN_MILLIS, 5), true);
4986         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4987                 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4988         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
4989                 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
4990 
4991         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS);
4992         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
4993         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS);
4994         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
4995         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS);
4996 
4997         setStandbyBucket(ACTIVE_INDEX);
4998         assertEquals("Got wrong time until EJ quota consumed for bucket #" + ACTIVE_INDEX,
4999                 28 * MINUTE_IN_MILLIS,
5000                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5001                         SOURCE_USER_ID, SOURCE_PACKAGE));
5002 
5003         setStandbyBucket(WORKING_INDEX);
5004         assertEquals("Got wrong time until EJ quota consumed for bucket #" + WORKING_INDEX,
5005                 18 * MINUTE_IN_MILLIS,
5006                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5007                         SOURCE_USER_ID, SOURCE_PACKAGE));
5008 
5009         setStandbyBucket(FREQUENT_INDEX);
5010         assertEquals("Got wrong time until EJ quota consumed for bucket #" + FREQUENT_INDEX,
5011                 13 * MINUTE_IN_MILLIS,
5012                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5013                         SOURCE_USER_ID, SOURCE_PACKAGE));
5014 
5015         setStandbyBucket(RARE_INDEX);
5016         assertEquals("Got wrong time until EJ quota consumed for bucket #" + RARE_INDEX,
5017                 7 * MINUTE_IN_MILLIS,
5018                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5019                         SOURCE_USER_ID, SOURCE_PACKAGE));
5020 
5021         setStandbyBucket(RESTRICTED_INDEX);
5022         assertEquals("Got wrong time until EJ quota consumed for bucket #" + RESTRICTED_INDEX,
5023                 MINUTE_IN_MILLIS,
5024                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5025                         SOURCE_USER_ID, SOURCE_PACKAGE));
5026     }
5027 
5028     @Test
testGetTimeUntilEJQuotaConsumedLocked_OneSessionStraddlesEdge()5029     public void testGetTimeUntilEJQuotaConsumedLocked_OneSessionStraddlesEdge() {
5030         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
5031 
5032         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5033                 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + MINUTE_IN_MILLIS),
5034                         2 * MINUTE_IN_MILLIS, 5), true);
5035         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5036                 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
5037         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5038                 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
5039         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5040                 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
5041         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5042                 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true);
5043 
5044         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS);
5045         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
5046         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS);
5047         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
5048         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS);
5049 
5050         setStandbyBucket(ACTIVE_INDEX);
5051         assertEquals("Got wrong time until EJ quota consumed for bucket #" + ACTIVE_INDEX,
5052                 26 * MINUTE_IN_MILLIS,
5053                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5054                         SOURCE_USER_ID, SOURCE_PACKAGE));
5055 
5056         setStandbyBucket(WORKING_INDEX);
5057         assertEquals("Got wrong time until EJ quota consumed for bucket #" + WORKING_INDEX,
5058                 16 * MINUTE_IN_MILLIS,
5059                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5060                         SOURCE_USER_ID, SOURCE_PACKAGE));
5061 
5062         setStandbyBucket(FREQUENT_INDEX);
5063         assertEquals("Got wrong time until EJ quota consumed for bucket #" + FREQUENT_INDEX,
5064                 11 * MINUTE_IN_MILLIS,
5065                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5066                         SOURCE_USER_ID, SOURCE_PACKAGE));
5067 
5068         setStandbyBucket(RARE_INDEX);
5069         assertEquals("Got wrong time until EJ quota consumed for bucket #" + RARE_INDEX,
5070                 6 * MINUTE_IN_MILLIS,
5071                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5072                         SOURCE_USER_ID, SOURCE_PACKAGE));
5073 
5074         setStandbyBucket(RESTRICTED_INDEX);
5075         assertEquals("Got wrong time until EJ quota consumed for bucket #" + RESTRICTED_INDEX,
5076                 MINUTE_IN_MILLIS,
5077                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5078                         SOURCE_USER_ID, SOURCE_PACKAGE));
5079     }
5080 
5081     @Test
testGetTimeUntilEJQuotaConsumedLocked_WithStaleSessions()5082     public void testGetTimeUntilEJQuotaConsumedLocked_WithStaleSessions() {
5083         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
5084 
5085         List<TimingSession> timingSessions = new ArrayList<>();
5086         timingSessions.add(
5087                 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + 10 * MINUTE_IN_MILLIS),
5088                         2 * MINUTE_IN_MILLIS, 5));
5089         timingSessions.add(
5090                 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + 5 * MINUTE_IN_MILLIS),
5091                         MINUTE_IN_MILLIS, 5));
5092         timingSessions.add(
5093                 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + MINUTE_IN_MILLIS),
5094                         2 * MINUTE_IN_MILLIS, 5));
5095         timingSessions.add(
5096                 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5));
5097         timingSessions.add(
5098                 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5));
5099         timingSessions.add(
5100                 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5));
5101         timingSessions.add(
5102                 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5));
5103 
5104         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS);
5105         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
5106         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS);
5107         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
5108         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS);
5109 
5110         runTestGetTimeUntilEJQuotaConsumedLocked(
5111                 timingSessions, ACTIVE_INDEX, 26 * MINUTE_IN_MILLIS);
5112         runTestGetTimeUntilEJQuotaConsumedLocked(
5113                 timingSessions, WORKING_INDEX, 16 * MINUTE_IN_MILLIS);
5114         runTestGetTimeUntilEJQuotaConsumedLocked(
5115                 timingSessions, FREQUENT_INDEX, 11 * MINUTE_IN_MILLIS);
5116         runTestGetTimeUntilEJQuotaConsumedLocked(timingSessions, RARE_INDEX, 6 * MINUTE_IN_MILLIS);
5117         runTestGetTimeUntilEJQuotaConsumedLocked(
5118                 timingSessions, RESTRICTED_INDEX, MINUTE_IN_MILLIS);
5119     }
5120 
5121     /**
5122      * Tests that getTimeUntilEJQuotaConsumedLocked returns the correct stats soon after device
5123      * startup.
5124      */
5125     @Test
testGetTimeUntilEJQuotaConsumedLocked_BeginningOfTime()5126     public void testGetTimeUntilEJQuotaConsumedLocked_BeginningOfTime() {
5127         // Set time to 3 minutes after boot.
5128         advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
5129         advanceElapsedClock(3 * MINUTE_IN_MILLIS);
5130 
5131         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5132                 createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), true);
5133         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5134                 createTimingSession(150 * SECOND_IN_MILLIS, 15 * SECOND_IN_MILLIS, 5), true);
5135 
5136         final long[] limits = mQuotaController.getEJLimitsMs();
5137         for (int i = 0; i < limits.length; ++i) {
5138             setStandbyBucket(i);
5139             assertEquals("Got wrong time until EJ quota consumed for bucket #" + i,
5140                     limits[i], // All existing sessions will phase out
5141                     mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5142                             SOURCE_USER_ID, SOURCE_PACKAGE));
5143         }
5144     }
5145 
runTestGetTimeUntilEJQuotaConsumedLocked( List<TimingSession> timingSessions, int bucketIndex, long expectedValue)5146     private void runTestGetTimeUntilEJQuotaConsumedLocked(
5147             List<TimingSession> timingSessions, int bucketIndex, long expectedValue) {
5148         synchronized (mQuotaController.mLock) {
5149             mQuotaController.onUserRemovedLocked(SOURCE_USER_ID);
5150         }
5151         if (timingSessions != null) {
5152             for (TimingSession session : timingSessions) {
5153                 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, session, true);
5154             }
5155         }
5156 
5157         setStandbyBucket(bucketIndex);
5158         assertEquals("Got wrong time until EJ quota consumed for bucket #" + bucketIndex,
5159                 expectedValue,
5160                 mQuotaController.getTimeUntilEJQuotaConsumedLocked(
5161                         SOURCE_USER_ID, SOURCE_PACKAGE));
5162     }
5163 
5164     @Test
testMaybeScheduleStartAlarmLocked_EJ()5165     public void testMaybeScheduleStartAlarmLocked_EJ() {
5166         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
5167         // because it schedules an alarm too. Prevent it from doing so.
5168         spyOn(mQuotaController);
5169         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
5170 
5171         synchronized (mQuotaController.mLock) {
5172             mQuotaController.maybeStartTrackingJobLocked(
5173                     createJobStatus("testMaybeScheduleStartAlarmLocked_EJ", 1), null);
5174         }
5175 
5176         final int standbyBucket = WORKING_INDEX;
5177         setStandbyBucket(standbyBucket);
5178         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
5179 
5180         InOrder inOrder = inOrder(mAlarmManager);
5181 
5182         synchronized (mQuotaController.mLock) {
5183             // No sessions saved yet.
5184             mQuotaController.maybeScheduleStartAlarmLocked(
5185                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
5186         }
5187         inOrder.verify(mAlarmManager, timeout(1000).times(0))
5188                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5189 
5190         // Test with timing sessions out of window.
5191         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
5192         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5193                 createTimingSession(now - 25 * HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS, 1), true);
5194         synchronized (mQuotaController.mLock) {
5195             mQuotaController.maybeScheduleStartAlarmLocked(
5196                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
5197         }
5198         inOrder.verify(mAlarmManager, timeout(1000).times(0))
5199                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5200 
5201         // Test with timing sessions in window but still in quota.
5202         final long end = now - (22 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
5203         final long expectedAlarmTime = now + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
5204         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5205                 new TimingSession(now - 22 * HOUR_IN_MILLIS, end, 1), true);
5206         synchronized (mQuotaController.mLock) {
5207             mQuotaController.maybeScheduleStartAlarmLocked(
5208                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
5209         }
5210         inOrder.verify(mAlarmManager, timeout(1000).times(0))
5211                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5212 
5213         // Add some more sessions, but still in quota.
5214         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5215                 createTimingSession(now - HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), true);
5216         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5217                 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 1), true);
5218         synchronized (mQuotaController.mLock) {
5219             mQuotaController.maybeScheduleStartAlarmLocked(
5220                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
5221         }
5222         inOrder.verify(mAlarmManager, timeout(1000).times(0))
5223                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5224 
5225         // Test when out of quota.
5226         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5227                 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 6 * MINUTE_IN_MILLIS, 1), true);
5228         synchronized (mQuotaController.mLock) {
5229             mQuotaController.maybeScheduleStartAlarmLocked(
5230                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
5231         }
5232         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
5233                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5234 
5235         // Alarm already scheduled, so make sure it's not scheduled again.
5236         synchronized (mQuotaController.mLock) {
5237             mQuotaController.maybeScheduleStartAlarmLocked(
5238                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
5239         }
5240         inOrder.verify(mAlarmManager, timeout(1000).times(0))
5241                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5242     }
5243 
5244     /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
5245     @Test
testMaybeScheduleStartAlarmLocked_Ej_BucketChange()5246     public void testMaybeScheduleStartAlarmLocked_Ej_BucketChange() {
5247         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
5248         // because it schedules an alarm too. Prevent it from doing so.
5249         spyOn(mQuotaController);
5250         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
5251 
5252         synchronized (mQuotaController.mLock) {
5253             mQuotaController.maybeStartTrackingJobLocked(
5254                     createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_BucketChange", 1), null);
5255         }
5256 
5257         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS);
5258         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS);
5259         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS);
5260         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
5261 
5262         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
5263         // Affects active bucket
5264         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5265                 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3), true);
5266         // Affects active and working buckets
5267         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5268                 createTimingSession(now - 4 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 3), true);
5269         // Affects active, working, and frequent buckets
5270         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5271                 createTimingSession(now - HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 10), true);
5272         // Affects all buckets
5273         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5274                 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 10 * MINUTE_IN_MILLIS, 3), true);
5275 
5276         InOrder inOrder = inOrder(mAlarmManager);
5277 
5278         // Start in ACTIVE bucket.
5279         setStandbyBucket(ACTIVE_INDEX);
5280         synchronized (mQuotaController.mLock) {
5281             mQuotaController.maybeScheduleStartAlarmLocked(
5282                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
5283         }
5284         inOrder.verify(mAlarmManager, timeout(1000).times(0))
5285                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5286         inOrder.verify(mAlarmManager, timeout(1000).times(0))
5287                 .cancel(any(AlarmManager.OnAlarmListener.class));
5288 
5289         // And down from there.
5290         setStandbyBucket(WORKING_INDEX);
5291         final long expectedWorkingAlarmTime =
5292                 (now - 4 * HOUR_IN_MILLIS) + (24 * HOUR_IN_MILLIS)
5293                         + mQcConstants.IN_QUOTA_BUFFER_MS;
5294         synchronized (mQuotaController.mLock) {
5295             mQuotaController.maybeScheduleStartAlarmLocked(
5296                     SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
5297         }
5298         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
5299                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
5300                 eq(TAG_QUOTA_CHECK), any(), any());
5301 
5302         setStandbyBucket(FREQUENT_INDEX);
5303         final long expectedFrequentAlarmTime =
5304                 (now - HOUR_IN_MILLIS) + (24 * HOUR_IN_MILLIS) + mQcConstants.IN_QUOTA_BUFFER_MS;
5305         synchronized (mQuotaController.mLock) {
5306             mQuotaController.maybeScheduleStartAlarmLocked(
5307                     SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
5308         }
5309         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
5310                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
5311                 eq(TAG_QUOTA_CHECK), any(), any());
5312 
5313         setStandbyBucket(RARE_INDEX);
5314         final long expectedRareAlarmTime =
5315                 (now - 5 * MINUTE_IN_MILLIS) + (24 * HOUR_IN_MILLIS)
5316                         + mQcConstants.IN_QUOTA_BUFFER_MS;
5317         synchronized (mQuotaController.mLock) {
5318             mQuotaController.maybeScheduleStartAlarmLocked(
5319                     SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
5320         }
5321         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
5322                 anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5323 
5324         // And back up again.
5325         setStandbyBucket(FREQUENT_INDEX);
5326         synchronized (mQuotaController.mLock) {
5327             mQuotaController.maybeScheduleStartAlarmLocked(
5328                     SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX);
5329         }
5330         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
5331                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
5332                 eq(TAG_QUOTA_CHECK), any(), any());
5333 
5334         setStandbyBucket(WORKING_INDEX);
5335         synchronized (mQuotaController.mLock) {
5336             mQuotaController.maybeScheduleStartAlarmLocked(
5337                     SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
5338         }
5339         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
5340                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
5341                 eq(TAG_QUOTA_CHECK), any(), any());
5342 
5343         setStandbyBucket(ACTIVE_INDEX);
5344         synchronized (mQuotaController.mLock) {
5345             mQuotaController.maybeScheduleStartAlarmLocked(
5346                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
5347         }
5348         inOrder.verify(mAlarmManager, timeout(1000).times(0))
5349                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5350         inOrder.verify(mAlarmManager, timeout(1000).times(1))
5351                 .cancel(any(AlarmManager.OnAlarmListener.class));
5352     }
5353 
5354     /**
5355      * Tests that the start alarm is properly rescheduled if the earliest session that contributes
5356      * to the app being out of quota contributes less than the quota buffer time.
5357      */
5358     @Test
testMaybeScheduleStartAlarmLocked_Ej_SmallRollingQuota()5359     public void testMaybeScheduleStartAlarmLocked_Ej_SmallRollingQuota() {
5360         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
5361         // because it schedules an alarm too. Prevent it from doing so.
5362         spyOn(mQuotaController);
5363         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
5364 
5365         synchronized (mQuotaController.mLock) {
5366             mQuotaController.maybeStartTrackingJobLocked(
5367                     createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_SRQ", 1), null);
5368         }
5369 
5370         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
5371         setStandbyBucket(WORKING_INDEX);
5372         final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
5373         final long remainingTimeMs = mQcConstants.EJ_LIMIT_WORKING_MS - contributionMs;
5374 
5375         // Session straddles edge of bucket window. Only the contribution should be counted towards
5376         // the quota.
5377         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5378                 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
5379                         3 * MINUTE_IN_MILLIS + contributionMs, 3), true);
5380         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
5381                 createTimingSession(now - 23 * HOUR_IN_MILLIS, remainingTimeMs, 2), true);
5382         // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
5383         // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
5384         final long expectedAlarmTime =
5385                 now + HOUR_IN_MILLIS + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs);
5386         synchronized (mQuotaController.mLock) {
5387             mQuotaController.maybeScheduleStartAlarmLocked(
5388                     SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
5389         }
5390         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
5391                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
5392     }
5393 
5394     /** Tests that TimingSessions aren't saved when the device is charging. */
5395     @Test
testEJTimerTracking_Charging()5396     public void testEJTimerTracking_Charging() {
5397         setCharging();
5398 
5399         JobStatus jobStatus = createExpeditedJobStatus("testEJTimerTracking_Charging", 1);
5400         synchronized (mQuotaController.mLock) {
5401             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
5402         }
5403 
5404         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5405 
5406         synchronized (mQuotaController.mLock) {
5407             mQuotaController.prepareForExecutionLocked(jobStatus);
5408         }
5409         advanceElapsedClock(5 * SECOND_IN_MILLIS);
5410         synchronized (mQuotaController.mLock) {
5411             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
5412         }
5413         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5414     }
5415 
5416     /** Tests that TimingSessions are saved properly when the device is discharging. */
5417     @Test
testEJTimerTracking_Discharging()5418     public void testEJTimerTracking_Discharging() {
5419         setDischarging();
5420         setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
5421 
5422         JobStatus jobStatus = createExpeditedJobStatus("testEJTimerTracking_Discharging", 1);
5423         synchronized (mQuotaController.mLock) {
5424             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
5425         }
5426 
5427         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5428 
5429         List<TimingSession> expected = new ArrayList<>();
5430 
5431         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
5432         synchronized (mQuotaController.mLock) {
5433             mQuotaController.prepareForExecutionLocked(jobStatus);
5434         }
5435         advanceElapsedClock(5 * SECOND_IN_MILLIS);
5436         synchronized (mQuotaController.mLock) {
5437             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
5438         }
5439         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
5440         assertEquals(expected,
5441                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5442 
5443         // Test overlapping jobs.
5444         JobStatus jobStatus2 = createExpeditedJobStatus("testEJTimerTracking_Discharging", 2);
5445         synchronized (mQuotaController.mLock) {
5446             mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
5447         }
5448 
5449         JobStatus jobStatus3 = createExpeditedJobStatus("testEJTimerTracking_Discharging", 3);
5450         synchronized (mQuotaController.mLock) {
5451             mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
5452         }
5453 
5454         advanceElapsedClock(SECOND_IN_MILLIS);
5455 
5456         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5457         synchronized (mQuotaController.mLock) {
5458             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
5459             mQuotaController.prepareForExecutionLocked(jobStatus);
5460         }
5461         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5462         synchronized (mQuotaController.mLock) {
5463             mQuotaController.prepareForExecutionLocked(jobStatus2);
5464         }
5465         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5466         synchronized (mQuotaController.mLock) {
5467             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
5468         }
5469         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5470         synchronized (mQuotaController.mLock) {
5471             mQuotaController.prepareForExecutionLocked(jobStatus3);
5472         }
5473         advanceElapsedClock(20 * SECOND_IN_MILLIS);
5474         synchronized (mQuotaController.mLock) {
5475             mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
5476         }
5477         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5478         synchronized (mQuotaController.mLock) {
5479             mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
5480         }
5481         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
5482         assertEquals(expected,
5483                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5484     }
5485 
5486     /**
5487      * Tests that TimingSessions are saved properly when the device alternates between
5488      * charging and discharging.
5489      */
5490     @Test
testEJTimerTracking_ChargingAndDischarging()5491     public void testEJTimerTracking_ChargingAndDischarging() {
5492         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
5493 
5494         JobStatus jobStatus =
5495                 createExpeditedJobStatus("testEJTimerTracking_ChargingAndDischarging", 1);
5496         synchronized (mQuotaController.mLock) {
5497             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
5498         }
5499         JobStatus jobStatus2 =
5500                 createExpeditedJobStatus("testEJTimerTracking_ChargingAndDischarging", 2);
5501         synchronized (mQuotaController.mLock) {
5502             mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
5503         }
5504         JobStatus jobStatus3 =
5505                 createExpeditedJobStatus("testEJTimerTracking_ChargingAndDischarging", 3);
5506         synchronized (mQuotaController.mLock) {
5507             mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
5508         }
5509         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5510         List<TimingSession> expected = new ArrayList<>();
5511 
5512         // A job starting while charging. Only the portion that runs during the discharging period
5513         // should be counted.
5514         setCharging();
5515 
5516         synchronized (mQuotaController.mLock) {
5517             mQuotaController.prepareForExecutionLocked(jobStatus);
5518         }
5519         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5520         setDischarging();
5521         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
5522         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5523         synchronized (mQuotaController.mLock) {
5524             mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
5525         }
5526         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
5527         assertEquals(expected,
5528                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5529 
5530         advanceElapsedClock(SECOND_IN_MILLIS);
5531 
5532         // One job starts while discharging, spans a charging session, and ends after the charging
5533         // session. Only the portions during the discharging periods should be counted. This should
5534         // result in two TimingSessions. A second job starts while discharging and ends within the
5535         // charging session. Only the portion during the first discharging portion should be
5536         // counted. A third job starts and ends within the charging session. The third job
5537         // shouldn't be included in either job count.
5538         setDischarging();
5539         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5540         synchronized (mQuotaController.mLock) {
5541             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
5542             mQuotaController.prepareForExecutionLocked(jobStatus);
5543         }
5544         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5545         synchronized (mQuotaController.mLock) {
5546             mQuotaController.prepareForExecutionLocked(jobStatus2);
5547         }
5548         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5549         setCharging();
5550         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
5551         synchronized (mQuotaController.mLock) {
5552             mQuotaController.prepareForExecutionLocked(jobStatus3);
5553         }
5554         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5555         synchronized (mQuotaController.mLock) {
5556             mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
5557         }
5558         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5559         synchronized (mQuotaController.mLock) {
5560             mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
5561         }
5562         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5563         setDischarging();
5564         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5565         advanceElapsedClock(20 * SECOND_IN_MILLIS);
5566         synchronized (mQuotaController.mLock) {
5567             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
5568         }
5569         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
5570         assertEquals(expected,
5571                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5572 
5573         // A job starting while discharging and ending while charging. Only the portion that runs
5574         // during the discharging period should be counted.
5575         setDischarging();
5576         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5577         synchronized (mQuotaController.mLock) {
5578             mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
5579             mQuotaController.prepareForExecutionLocked(jobStatus2);
5580         }
5581         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5582         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
5583         setCharging();
5584         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5585         synchronized (mQuotaController.mLock) {
5586             mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
5587         }
5588         assertEquals(expected,
5589                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5590     }
5591 
5592     /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
5593     @Test
testEJTimerTracking_AllBackground()5594     public void testEJTimerTracking_AllBackground() {
5595         setDischarging();
5596         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
5597 
5598         JobStatus jobStatus = createExpeditedJobStatus("testEJTimerTracking_AllBackground", 1);
5599         synchronized (mQuotaController.mLock) {
5600             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
5601         }
5602         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5603 
5604         List<TimingSession> expected = new ArrayList<>();
5605 
5606         // Test single job.
5607         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
5608         synchronized (mQuotaController.mLock) {
5609             mQuotaController.prepareForExecutionLocked(jobStatus);
5610         }
5611         advanceElapsedClock(5 * SECOND_IN_MILLIS);
5612         synchronized (mQuotaController.mLock) {
5613             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
5614         }
5615         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
5616         assertEquals(expected,
5617                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5618 
5619         // Test overlapping jobs.
5620         JobStatus jobStatus2 = createExpeditedJobStatus("testEJTimerTracking_AllBackground", 2);
5621         synchronized (mQuotaController.mLock) {
5622             mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
5623         }
5624 
5625         JobStatus jobStatus3 = createExpeditedJobStatus("testEJTimerTracking_AllBackground", 3);
5626         synchronized (mQuotaController.mLock) {
5627             mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
5628         }
5629 
5630         advanceElapsedClock(SECOND_IN_MILLIS);
5631 
5632         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5633         synchronized (mQuotaController.mLock) {
5634             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
5635             mQuotaController.prepareForExecutionLocked(jobStatus);
5636         }
5637         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5638         synchronized (mQuotaController.mLock) {
5639             mQuotaController.prepareForExecutionLocked(jobStatus2);
5640         }
5641         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5642         synchronized (mQuotaController.mLock) {
5643             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
5644         }
5645         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5646         synchronized (mQuotaController.mLock) {
5647             mQuotaController.prepareForExecutionLocked(jobStatus3);
5648         }
5649         advanceElapsedClock(20 * SECOND_IN_MILLIS);
5650         synchronized (mQuotaController.mLock) {
5651             mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
5652         }
5653         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5654         synchronized (mQuotaController.mLock) {
5655             mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
5656         }
5657         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
5658         assertEquals(expected,
5659                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5660     }
5661 
5662     /** Tests that Timers don't count foreground jobs. */
5663     @Test
testEJTimerTracking_AllForeground()5664     public void testEJTimerTracking_AllForeground() {
5665         setDischarging();
5666 
5667         JobStatus jobStatus = createExpeditedJobStatus("testEJTimerTracking_AllForeground", 1);
5668         setProcessState(ActivityManager.PROCESS_STATE_TOP);
5669         synchronized (mQuotaController.mLock) {
5670             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
5671         }
5672 
5673         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5674 
5675         synchronized (mQuotaController.mLock) {
5676             mQuotaController.prepareForExecutionLocked(jobStatus);
5677         }
5678         advanceElapsedClock(5 * SECOND_IN_MILLIS);
5679         // Change to a state that should still be considered foreground.
5680         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
5681         advanceElapsedClock(5 * SECOND_IN_MILLIS);
5682         synchronized (mQuotaController.mLock) {
5683             mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
5684         }
5685         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5686     }
5687 
5688     /**
5689      * Tests that Timers properly track sessions when switching between foreground and background
5690      * states.
5691      */
5692     @Test
testEJTimerTracking_ForegroundAndBackground()5693     public void testEJTimerTracking_ForegroundAndBackground() {
5694         setDischarging();
5695 
5696         JobStatus jobBg1 =
5697                 createExpeditedJobStatus("testEJTimerTracking_ForegroundAndBackground", 1);
5698         JobStatus jobBg2 =
5699                 createExpeditedJobStatus("testEJTimerTracking_ForegroundAndBackground", 2);
5700         JobStatus jobFg3 =
5701                 createExpeditedJobStatus("testEJTimerTracking_ForegroundAndBackground", 3);
5702         synchronized (mQuotaController.mLock) {
5703             mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
5704             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
5705             mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
5706         }
5707         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5708         List<TimingSession> expected = new ArrayList<>();
5709 
5710         // UID starts out inactive.
5711         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
5712         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
5713         synchronized (mQuotaController.mLock) {
5714             mQuotaController.prepareForExecutionLocked(jobBg1);
5715         }
5716         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5717         synchronized (mQuotaController.mLock) {
5718             mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
5719         }
5720         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
5721         assertEquals(expected,
5722                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5723 
5724         advanceElapsedClock(SECOND_IN_MILLIS);
5725 
5726         // Bg job starts while inactive, spans an entire active session, and ends after the
5727         // active session.
5728         // App switching to foreground state then fg job starts.
5729         // App remains in foreground state after coming to foreground, so there should only be one
5730         // session.
5731         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5732         synchronized (mQuotaController.mLock) {
5733             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
5734             mQuotaController.prepareForExecutionLocked(jobBg2);
5735         }
5736         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5737         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
5738         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
5739         synchronized (mQuotaController.mLock) {
5740             mQuotaController.prepareForExecutionLocked(jobFg3);
5741         }
5742         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5743         synchronized (mQuotaController.mLock) {
5744             mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
5745         }
5746         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5747         synchronized (mQuotaController.mLock) {
5748             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
5749         }
5750         assertEquals(expected,
5751                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5752 
5753         advanceElapsedClock(SECOND_IN_MILLIS);
5754 
5755         // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
5756         // "inactive" and then bg job 2 starts. Then fg job ends.
5757         // This should result in two TimingSessions:
5758         //  * The first should have a count of 1
5759         //  * The second should have a count of 2 since it will include both jobs
5760         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5761         synchronized (mQuotaController.mLock) {
5762             mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
5763             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
5764             mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
5765         }
5766         setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
5767         synchronized (mQuotaController.mLock) {
5768             mQuotaController.prepareForExecutionLocked(jobBg1);
5769         }
5770         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5771         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
5772         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
5773         synchronized (mQuotaController.mLock) {
5774             mQuotaController.prepareForExecutionLocked(jobFg3);
5775         }
5776         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5777         synchronized (mQuotaController.mLock) {
5778             mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
5779         }
5780         advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
5781         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5782         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
5783         synchronized (mQuotaController.mLock) {
5784             mQuotaController.prepareForExecutionLocked(jobBg2);
5785         }
5786         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5787         synchronized (mQuotaController.mLock) {
5788             mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
5789         }
5790         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5791         synchronized (mQuotaController.mLock) {
5792             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
5793         }
5794         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
5795         assertEquals(expected,
5796                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5797     }
5798 
5799     /**
5800      * Tests that Timers properly track overlapping top and background jobs.
5801      */
5802     @Test
testEJTimerTracking_TopAndNonTop()5803     public void testEJTimerTracking_TopAndNonTop() {
5804         setDischarging();
5805         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
5806 
5807         JobStatus jobBg1 = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 1);
5808         JobStatus jobBg2 = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 2);
5809         JobStatus jobFg1 = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 3);
5810         JobStatus jobTop = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 4);
5811         synchronized (mQuotaController.mLock) {
5812             mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
5813             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
5814             mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
5815             mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
5816         }
5817         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5818         List<TimingSession> expected = new ArrayList<>();
5819 
5820         // UID starts out inactive.
5821         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
5822         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
5823         synchronized (mQuotaController.mLock) {
5824             mQuotaController.prepareForExecutionLocked(jobBg1);
5825         }
5826         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5827         synchronized (mQuotaController.mLock) {
5828             mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
5829         }
5830         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
5831         assertEquals(expected,
5832                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5833 
5834         advanceElapsedClock(SECOND_IN_MILLIS);
5835 
5836         // Bg job starts while inactive, spans an entire active session, and ends after the
5837         // active session.
5838         // App switching to top state then fg job starts.
5839         // App remains in top state after coming to top, so there should only be one
5840         // session.
5841         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5842         synchronized (mQuotaController.mLock) {
5843             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
5844             mQuotaController.prepareForExecutionLocked(jobBg2);
5845         }
5846         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5847         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
5848         setProcessState(ActivityManager.PROCESS_STATE_TOP);
5849         synchronized (mQuotaController.mLock) {
5850             mQuotaController.prepareForExecutionLocked(jobTop);
5851         }
5852         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5853         synchronized (mQuotaController.mLock) {
5854             mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
5855         }
5856         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5857         synchronized (mQuotaController.mLock) {
5858             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
5859         }
5860         assertEquals(expected,
5861                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5862 
5863         advanceElapsedClock(SECOND_IN_MILLIS);
5864 
5865         // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
5866         // foreground_service and a new job starts. Shortly after, uid goes
5867         // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
5868         // This should result in two TimingSessions:
5869         //  * The first should have a count of 1
5870         //  * The second should have a count of 2, which accounts for the bg2 and fg, but not top
5871         //    jobs.
5872         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5873         synchronized (mQuotaController.mLock) {
5874             mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
5875             mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
5876             mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
5877         }
5878         setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
5879         synchronized (mQuotaController.mLock) {
5880             mQuotaController.prepareForExecutionLocked(jobBg1);
5881         }
5882         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5883         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
5884         setProcessState(ActivityManager.PROCESS_STATE_TOP);
5885         synchronized (mQuotaController.mLock) {
5886             mQuotaController.prepareForExecutionLocked(jobTop);
5887         }
5888         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5889         synchronized (mQuotaController.mLock) {
5890             mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
5891         }
5892         advanceElapsedClock(5 * SECOND_IN_MILLIS);
5893         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
5894         synchronized (mQuotaController.mLock) {
5895             mQuotaController.prepareForExecutionLocked(jobFg1);
5896         }
5897         advanceElapsedClock(5 * SECOND_IN_MILLIS);
5898         setProcessState(ActivityManager.PROCESS_STATE_TOP);
5899         advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
5900         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5901         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
5902         synchronized (mQuotaController.mLock) {
5903             mQuotaController.prepareForExecutionLocked(jobBg2);
5904         }
5905         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5906         synchronized (mQuotaController.mLock) {
5907             mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
5908         }
5909         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5910         synchronized (mQuotaController.mLock) {
5911             mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
5912             mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
5913         }
5914         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
5915         assertEquals(expected,
5916                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5917     }
5918 
5919     /**
5920      * Tests that Timers properly track sessions when an app is added and removed from the temp
5921      * allowlist.
5922      */
5923     @Test
testEJTimerTracking_TempAllowlisting()5924     public void testEJTimerTracking_TempAllowlisting() {
5925         setDischarging();
5926         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
5927         final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
5928         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
5929         Handler handler = mQuotaController.getHandler();
5930         spyOn(handler);
5931 
5932         JobStatus job1 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 1);
5933         JobStatus job2 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 2);
5934         JobStatus job3 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 3);
5935         JobStatus job4 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 4);
5936         JobStatus job5 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 5);
5937         synchronized (mQuotaController.mLock) {
5938             mQuotaController.maybeStartTrackingJobLocked(job1, null);
5939         }
5940         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5941         List<TimingSession> expected = new ArrayList<>();
5942 
5943         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
5944         synchronized (mQuotaController.mLock) {
5945             mQuotaController.prepareForExecutionLocked(job1);
5946         }
5947         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5948         synchronized (mQuotaController.mLock) {
5949             mQuotaController.maybeStopTrackingJobLocked(job1, job1, true);
5950         }
5951         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
5952         assertEquals(expected,
5953                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5954 
5955         advanceElapsedClock(SECOND_IN_MILLIS);
5956 
5957         // Job starts after app is added to temp allowlist and stops before removal.
5958         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5959         mTempAllowlistListener.onAppAdded(mSourceUid);
5960         synchronized (mQuotaController.mLock) {
5961             mQuotaController.maybeStartTrackingJobLocked(job2, null);
5962             mQuotaController.prepareForExecutionLocked(job2);
5963         }
5964         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5965         synchronized (mQuotaController.mLock) {
5966             mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
5967         }
5968         assertEquals(expected,
5969                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5970 
5971         // Job starts after app is added to temp allowlist and stops after removal,
5972         // before grace period ends.
5973         mTempAllowlistListener.onAppAdded(mSourceUid);
5974         synchronized (mQuotaController.mLock) {
5975             mQuotaController.maybeStartTrackingJobLocked(job3, null);
5976             mQuotaController.prepareForExecutionLocked(job3);
5977         }
5978         advanceElapsedClock(10 * SECOND_IN_MILLIS);
5979         mTempAllowlistListener.onAppRemoved(mSourceUid);
5980         start = JobSchedulerService.sElapsedRealtimeClock.millis();
5981         long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
5982         advanceElapsedClock(elapsedGracePeriodMs);
5983         synchronized (mQuotaController.mLock) {
5984             mQuotaController.maybeStopTrackingJobLocked(job3, null, false);
5985         }
5986         assertEquals(expected,
5987                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
5988 
5989         advanceElapsedClock(SECOND_IN_MILLIS);
5990         elapsedGracePeriodMs += SECOND_IN_MILLIS;
5991 
5992         // Job starts during grace period and ends after grace period ends
5993         synchronized (mQuotaController.mLock) {
5994             mQuotaController.maybeStartTrackingJobLocked(job4, null);
5995             mQuotaController.prepareForExecutionLocked(job4);
5996         }
5997         final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
5998         start = JobSchedulerService.sElapsedRealtimeClock.millis() + remainingGracePeriod;
5999         advanceElapsedClock(remainingGracePeriod);
6000         // Wait for handler to update Timer
6001         // Can't directly evaluate the message because for some reason, the captured message returns
6002         // the wrong 'what' even though the correct message goes to the handler and the correct
6003         // path executes.
6004         verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
6005         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6006         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
6007         synchronized (mQuotaController.mLock) {
6008             mQuotaController.maybeStopTrackingJobLocked(job4, job4, true);
6009         }
6010         assertEquals(expected,
6011                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6012 
6013         // Job starts and runs completely after temp allowlist grace period.
6014         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6015         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6016         synchronized (mQuotaController.mLock) {
6017             mQuotaController.maybeStartTrackingJobLocked(job5, null);
6018             mQuotaController.prepareForExecutionLocked(job5);
6019         }
6020         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6021         synchronized (mQuotaController.mLock) {
6022             mQuotaController.maybeStopTrackingJobLocked(job5, job5, true);
6023         }
6024         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
6025         assertEquals(expected,
6026                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6027     }
6028 
6029     @Test
testEJTimerTracking_TempAllowlisting_Restricted()6030     public void testEJTimerTracking_TempAllowlisting_Restricted() {
6031         setDischarging();
6032         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
6033         final long gracePeriodMs = 15 * SECOND_IN_MILLIS;
6034         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
6035         Handler handler = mQuotaController.getHandler();
6036         spyOn(handler);
6037 
6038         JobStatus job = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting_Restricted", 1);
6039         setStandbyBucket(RESTRICTED_INDEX, job);
6040         synchronized (mQuotaController.mLock) {
6041             mQuotaController.maybeStartTrackingJobLocked(job, null);
6042         }
6043         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6044         List<TimingSession> expected = new ArrayList<>();
6045 
6046         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
6047         synchronized (mQuotaController.mLock) {
6048             mQuotaController.prepareForExecutionLocked(job);
6049         }
6050         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6051         synchronized (mQuotaController.mLock) {
6052             mQuotaController.maybeStopTrackingJobLocked(job, job, true);
6053         }
6054         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
6055         assertEquals(expected,
6056                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6057 
6058         advanceElapsedClock(SECOND_IN_MILLIS);
6059 
6060         // Job starts after app is added to temp allowlist and stops before removal.
6061         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6062         mTempAllowlistListener.onAppAdded(mSourceUid);
6063         synchronized (mQuotaController.mLock) {
6064             mQuotaController.maybeStartTrackingJobLocked(job, null);
6065             mQuotaController.prepareForExecutionLocked(job);
6066         }
6067         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6068         synchronized (mQuotaController.mLock) {
6069             mQuotaController.maybeStopTrackingJobLocked(job, null, false);
6070         }
6071         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
6072         assertEquals(expected,
6073                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6074 
6075         // Job starts after app is added to temp allowlist and stops after removal,
6076         // before grace period ends.
6077         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6078         mTempAllowlistListener.onAppAdded(mSourceUid);
6079         synchronized (mQuotaController.mLock) {
6080             mQuotaController.maybeStartTrackingJobLocked(job, null);
6081             mQuotaController.prepareForExecutionLocked(job);
6082         }
6083         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6084         mTempAllowlistListener.onAppRemoved(mSourceUid);
6085         long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
6086         advanceElapsedClock(elapsedGracePeriodMs);
6087         synchronized (mQuotaController.mLock) {
6088             mQuotaController.maybeStopTrackingJobLocked(job, null, false);
6089         }
6090         expected.add(createTimingSession(start, 12 * SECOND_IN_MILLIS, 1));
6091         assertEquals(expected,
6092                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6093 
6094         advanceElapsedClock(SECOND_IN_MILLIS);
6095         elapsedGracePeriodMs += SECOND_IN_MILLIS;
6096 
6097         // Job starts during grace period and ends after grace period ends
6098         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6099         synchronized (mQuotaController.mLock) {
6100             mQuotaController.maybeStartTrackingJobLocked(job, null);
6101             mQuotaController.prepareForExecutionLocked(job);
6102         }
6103         final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs;
6104         advanceElapsedClock(remainingGracePeriod);
6105         // Wait for handler to update Timer
6106         // Can't directly evaluate the message because for some reason, the captured message returns
6107         // the wrong 'what' even though the correct message goes to the handler and the correct
6108         // path executes.
6109         verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any());
6110         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6111         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + remainingGracePeriod, 1));
6112         synchronized (mQuotaController.mLock) {
6113             mQuotaController.maybeStopTrackingJobLocked(job, job, true);
6114         }
6115         assertEquals(expected,
6116                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6117 
6118         // Job starts and runs completely after temp allowlist grace period.
6119         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6120         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6121         synchronized (mQuotaController.mLock) {
6122             mQuotaController.maybeStartTrackingJobLocked(job, null);
6123             mQuotaController.prepareForExecutionLocked(job);
6124         }
6125         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6126         synchronized (mQuotaController.mLock) {
6127             mQuotaController.maybeStopTrackingJobLocked(job, job, true);
6128         }
6129         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
6130         assertEquals(expected,
6131                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6132     }
6133 
6134     /**
6135      * Tests that Timers properly track sessions when TOP state and temp allowlisting overlaps.
6136      */
6137     @Test
6138     @LargeTest
testEJTimerTracking_TopAndTempAllowlisting()6139     public void testEJTimerTracking_TopAndTempAllowlisting() throws Exception {
6140         setDischarging();
6141         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
6142         final long gracePeriodMs = 5 * SECOND_IN_MILLIS;
6143         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs);
6144         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, gracePeriodMs);
6145         Handler handler = mQuotaController.getHandler();
6146         spyOn(handler);
6147 
6148         JobStatus job1 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 1);
6149         JobStatus job2 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 2);
6150         JobStatus job3 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 3);
6151         JobStatus job4 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 4);
6152         JobStatus job5 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 5);
6153         synchronized (mQuotaController.mLock) {
6154             mQuotaController.maybeStartTrackingJobLocked(job1, null);
6155         }
6156         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6157         List<TimingSession> expected = new ArrayList<>();
6158 
6159         // Case 1: job starts in TA grace period then app becomes TOP
6160         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
6161         mTempAllowlistListener.onAppAdded(mSourceUid);
6162         mTempAllowlistListener.onAppRemoved(mSourceUid);
6163         advanceElapsedClock(gracePeriodMs / 2);
6164         synchronized (mQuotaController.mLock) {
6165             mQuotaController.prepareForExecutionLocked(job1);
6166         }
6167         setProcessState(ActivityManager.PROCESS_STATE_TOP);
6168         advanceElapsedClock(gracePeriodMs);
6169         // Wait for the grace period to expire so the handler can process the message.
6170         Thread.sleep(gracePeriodMs);
6171         synchronized (mQuotaController.mLock) {
6172             mQuotaController.maybeStopTrackingJobLocked(job1, job1, true);
6173         }
6174         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6175 
6176         advanceElapsedClock(gracePeriodMs);
6177 
6178         // Case 2: job starts in TOP grace period then is TAed
6179         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6180         setProcessState(ActivityManager.PROCESS_STATE_TOP);
6181         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
6182         advanceElapsedClock(gracePeriodMs / 2);
6183         synchronized (mQuotaController.mLock) {
6184             mQuotaController.maybeStartTrackingJobLocked(job2, null);
6185             mQuotaController.prepareForExecutionLocked(job2);
6186         }
6187         mTempAllowlistListener.onAppAdded(mSourceUid);
6188         advanceElapsedClock(gracePeriodMs);
6189         // Wait for the grace period to expire so the handler can process the message.
6190         Thread.sleep(gracePeriodMs);
6191         synchronized (mQuotaController.mLock) {
6192             mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
6193         }
6194         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6195 
6196         advanceElapsedClock(gracePeriodMs);
6197 
6198         // Case 3: job starts in TA grace period then app becomes TOP; job ends after TOP grace
6199         mTempAllowlistListener.onAppAdded(mSourceUid);
6200         mTempAllowlistListener.onAppRemoved(mSourceUid);
6201         advanceElapsedClock(gracePeriodMs / 2);
6202         synchronized (mQuotaController.mLock) {
6203             mQuotaController.maybeStartTrackingJobLocked(job3, null);
6204             mQuotaController.prepareForExecutionLocked(job3);
6205         }
6206         advanceElapsedClock(SECOND_IN_MILLIS);
6207         setProcessState(ActivityManager.PROCESS_STATE_TOP);
6208         advanceElapsedClock(gracePeriodMs);
6209         // Wait for the grace period to expire so the handler can process the message.
6210         Thread.sleep(gracePeriodMs);
6211         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
6212         advanceElapsedClock(gracePeriodMs);
6213         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6214         // Wait for the grace period to expire so the handler can process the message.
6215         Thread.sleep(2 * gracePeriodMs);
6216         advanceElapsedClock(gracePeriodMs);
6217         synchronized (mQuotaController.mLock) {
6218             mQuotaController.maybeStopTrackingJobLocked(job3, job3, true);
6219         }
6220         expected.add(createTimingSession(start, gracePeriodMs, 1));
6221         assertEquals(expected,
6222                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6223 
6224         advanceElapsedClock(gracePeriodMs);
6225 
6226         // Case 4: job starts in TOP grace period then app becomes TAed; job ends after TA grace
6227         setProcessState(ActivityManager.PROCESS_STATE_TOP);
6228         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
6229         advanceElapsedClock(gracePeriodMs / 2);
6230         synchronized (mQuotaController.mLock) {
6231             mQuotaController.maybeStartTrackingJobLocked(job4, null);
6232             mQuotaController.prepareForExecutionLocked(job4);
6233         }
6234         advanceElapsedClock(SECOND_IN_MILLIS);
6235         mTempAllowlistListener.onAppAdded(mSourceUid);
6236         advanceElapsedClock(gracePeriodMs);
6237         // Wait for the grace period to expire so the handler can process the message.
6238         Thread.sleep(gracePeriodMs);
6239         mTempAllowlistListener.onAppRemoved(mSourceUid);
6240         advanceElapsedClock(gracePeriodMs);
6241         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6242         // Wait for the grace period to expire so the handler can process the message.
6243         Thread.sleep(2 * gracePeriodMs);
6244         advanceElapsedClock(gracePeriodMs);
6245         synchronized (mQuotaController.mLock) {
6246             mQuotaController.maybeStopTrackingJobLocked(job4, job4, true);
6247         }
6248         expected.add(createTimingSession(start, gracePeriodMs, 1));
6249         assertEquals(expected,
6250                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6251 
6252         advanceElapsedClock(gracePeriodMs);
6253 
6254         // Case 5: job starts during overlapping grace period
6255         setProcessState(ActivityManager.PROCESS_STATE_TOP);
6256         advanceElapsedClock(SECOND_IN_MILLIS);
6257         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
6258         advanceElapsedClock(SECOND_IN_MILLIS);
6259         mTempAllowlistListener.onAppAdded(mSourceUid);
6260         advanceElapsedClock(SECOND_IN_MILLIS);
6261         mTempAllowlistListener.onAppRemoved(mSourceUid);
6262         advanceElapsedClock(gracePeriodMs - SECOND_IN_MILLIS);
6263         synchronized (mQuotaController.mLock) {
6264             mQuotaController.maybeStartTrackingJobLocked(job5, null);
6265             mQuotaController.prepareForExecutionLocked(job5);
6266         }
6267         advanceElapsedClock(SECOND_IN_MILLIS);
6268         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6269         // Wait for the grace period to expire so the handler can process the message.
6270         Thread.sleep(2 * gracePeriodMs);
6271         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6272         synchronized (mQuotaController.mLock) {
6273             mQuotaController.maybeStopTrackingJobLocked(job5, job5, true);
6274         }
6275         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
6276         assertEquals(expected,
6277                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6278     }
6279 
6280     /**
6281      * Tests that expedited jobs aren't stopped when an app runs out of quota.
6282      */
6283     @Test
testEJTracking_OutOfQuota_ForegroundAndBackground()6284     public void testEJTracking_OutOfQuota_ForegroundAndBackground() {
6285         setDischarging();
6286         setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0);
6287 
6288         JobStatus jobBg =
6289                 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 1);
6290         JobStatus jobTop =
6291                 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 2);
6292         JobStatus jobUnstarted =
6293                 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 3);
6294         trackJobs(jobBg, jobTop, jobUnstarted);
6295         setStandbyBucket(WORKING_INDEX, jobTop, jobBg, jobUnstarted);
6296         // Now the package only has 20 seconds to run.
6297         final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
6298         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
6299                 createTimingSession(
6300                         JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
6301                         mQcConstants.EJ_LIMIT_WORKING_MS - remainingTimeMs, 1), true);
6302 
6303         InOrder inOrder = inOrder(mJobSchedulerService);
6304 
6305         // UID starts out inactive.
6306         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
6307         // Start the job.
6308         synchronized (mQuotaController.mLock) {
6309             mQuotaController.prepareForExecutionLocked(jobBg);
6310         }
6311         advanceElapsedClock(remainingTimeMs / 2);
6312         // New job starts after UID is in the foreground. Since the app is now in the foreground, it
6313         // should continue to have remainingTimeMs / 2 time remaining.
6314         setProcessState(ActivityManager.PROCESS_STATE_TOP);
6315         synchronized (mQuotaController.mLock) {
6316             mQuotaController.prepareForExecutionLocked(jobTop);
6317         }
6318         advanceElapsedClock(remainingTimeMs);
6319 
6320         // Wait for some extra time to allow for job processing.
6321         inOrder.verify(mJobSchedulerService,
6322                         timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
6323                 .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
6324         synchronized (mQuotaController.mLock) {
6325             assertEquals(remainingTimeMs / 2,
6326                     mQuotaController.getRemainingEJExecutionTimeLocked(
6327                             SOURCE_USER_ID, SOURCE_PACKAGE));
6328         }
6329         // Go to a background state.
6330         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
6331         advanceElapsedClock(remainingTimeMs / 2 + 1);
6332         inOrder.verify(mJobSchedulerService,
6333                         timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
6334                 .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
6335         // Top should still be "in quota" since it started before the app ran on top out of quota.
6336         assertFalse(jobBg.isExpeditedQuotaApproved());
6337         assertTrue(jobTop.isExpeditedQuotaApproved());
6338         assertFalse(jobUnstarted.isExpeditedQuotaApproved());
6339         synchronized (mQuotaController.mLock) {
6340             assertTrue(
6341                     0 >= mQuotaController
6342                             .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6343         }
6344 
6345         // New jobs to run.
6346         JobStatus jobBg2 =
6347                 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 4);
6348         JobStatus jobTop2 =
6349                 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 5);
6350         JobStatus jobFg =
6351                 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 6);
6352         setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
6353 
6354         advanceElapsedClock(20 * SECOND_IN_MILLIS);
6355         setProcessState(ActivityManager.PROCESS_STATE_TOP);
6356         // Confirm QC recognizes that jobUnstarted has changed from out-of-quota to in-quota.
6357         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
6358                 .onControllerStateChanged(argThat(jobs -> jobs.size() == 2));
6359         trackJobs(jobTop2, jobFg);
6360         synchronized (mQuotaController.mLock) {
6361             mQuotaController.prepareForExecutionLocked(jobTop2);
6362         }
6363         assertTrue(jobTop2.isExpeditedQuotaApproved());
6364         assertTrue(jobFg.isExpeditedQuotaApproved());
6365         assertTrue(jobBg.isExpeditedQuotaApproved());
6366         assertTrue(jobUnstarted.isExpeditedQuotaApproved());
6367 
6368         // App still in foreground so everything should be in quota.
6369         advanceElapsedClock(20 * SECOND_IN_MILLIS);
6370         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
6371         assertTrue(jobTop2.isExpeditedQuotaApproved());
6372         assertTrue(jobFg.isExpeditedQuotaApproved());
6373         assertTrue(jobBg.isExpeditedQuotaApproved());
6374         assertTrue(jobUnstarted.isExpeditedQuotaApproved());
6375 
6376         advanceElapsedClock(20 * SECOND_IN_MILLIS);
6377         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
6378         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
6379                 .onControllerStateChanged(argThat(jobs -> jobs.size() == 3));
6380         // App is now in background and out of quota. Fg should now change to out of quota since it
6381         // wasn't started. Top should remain in quota since it started when the app was in TOP.
6382         assertTrue(jobTop2.isExpeditedQuotaApproved());
6383         assertFalse(jobFg.isExpeditedQuotaApproved());
6384         assertFalse(jobBg.isExpeditedQuotaApproved());
6385         trackJobs(jobBg2);
6386         assertFalse(jobBg2.isExpeditedQuotaApproved());
6387         assertFalse(jobUnstarted.isExpeditedQuotaApproved());
6388         synchronized (mQuotaController.mLock) {
6389             assertTrue(
6390                     0 >= mQuotaController
6391                             .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6392         }
6393     }
6394 
6395     /**
6396      * Tests that Timers properly track overlapping top and background jobs.
6397      */
6398     @Test
testEJTimerTrackingSeparateFromRegularTracking()6399     public void testEJTimerTrackingSeparateFromRegularTracking() {
6400         setDischarging();
6401         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
6402 
6403         JobStatus jobReg1 = createJobStatus("testEJTimerTrackingSeparateFromRegularTracking", 1);
6404         JobStatus jobEJ1 =
6405                 createExpeditedJobStatus("testEJTimerTrackingSeparateFromRegularTracking", 2);
6406         JobStatus jobReg2 = createJobStatus("testEJTimerTrackingSeparateFromRegularTracking", 3);
6407         JobStatus jobEJ2 =
6408                 createExpeditedJobStatus("testEJTimerTrackingSeparateFromRegularTracking", 4);
6409         synchronized (mQuotaController.mLock) {
6410             mQuotaController.maybeStartTrackingJobLocked(jobReg1, null);
6411             mQuotaController.maybeStartTrackingJobLocked(jobEJ1, null);
6412             mQuotaController.maybeStartTrackingJobLocked(jobReg2, null);
6413             mQuotaController.maybeStartTrackingJobLocked(jobEJ2, null);
6414         }
6415         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6416         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6417         List<TimingSession> expectedRegular = new ArrayList<>();
6418         List<TimingSession> expectedEJ = new ArrayList<>();
6419 
6420         // First, regular job runs by itself.
6421         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
6422         synchronized (mQuotaController.mLock) {
6423             mQuotaController.prepareForExecutionLocked(jobReg1);
6424         }
6425         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6426         synchronized (mQuotaController.mLock) {
6427             mQuotaController.maybeStopTrackingJobLocked(jobReg1, jobReg1, true);
6428         }
6429         expectedRegular.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
6430         assertEquals(expectedRegular,
6431                 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6432         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6433 
6434         advanceElapsedClock(SECOND_IN_MILLIS);
6435 
6436         // Next, EJ runs by itself.
6437         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6438         synchronized (mQuotaController.mLock) {
6439             mQuotaController.prepareForExecutionLocked(jobEJ1);
6440         }
6441         advanceElapsedClock(10 * SECOND_IN_MILLIS);
6442         synchronized (mQuotaController.mLock) {
6443             mQuotaController.maybeStopTrackingJobLocked(jobEJ1, null, false);
6444         }
6445         expectedEJ.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
6446         assertEquals(expectedRegular,
6447                 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6448         assertEquals(expectedEJ,
6449                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6450 
6451         advanceElapsedClock(SECOND_IN_MILLIS);
6452 
6453         // Finally, a regular job and EJ happen to overlap runs.
6454         start = JobSchedulerService.sElapsedRealtimeClock.millis();
6455         synchronized (mQuotaController.mLock) {
6456             mQuotaController.prepareForExecutionLocked(jobEJ2);
6457         }
6458         advanceElapsedClock(5 * SECOND_IN_MILLIS);
6459         synchronized (mQuotaController.mLock) {
6460             mQuotaController.prepareForExecutionLocked(jobReg2);
6461         }
6462         advanceElapsedClock(5 * SECOND_IN_MILLIS);
6463         synchronized (mQuotaController.mLock) {
6464             mQuotaController.maybeStopTrackingJobLocked(jobEJ2, null, false);
6465         }
6466         expectedEJ.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
6467         advanceElapsedClock(5 * SECOND_IN_MILLIS);
6468         synchronized (mQuotaController.mLock) {
6469             mQuotaController.maybeStopTrackingJobLocked(jobReg2, null, false);
6470         }
6471         expectedRegular.add(
6472                 createTimingSession(start + 5 * SECOND_IN_MILLIS, 10 * SECOND_IN_MILLIS, 1));
6473         assertEquals(expectedRegular,
6474                 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6475         assertEquals(expectedEJ,
6476                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
6477     }
6478 
6479     /**
6480      * Tests that a job is properly handled when it's at the edge of its quota and the old quota is
6481      * being phased out.
6482      */
6483     @Test
testEJTracking_RollingQuota()6484     public void testEJTracking_RollingQuota() {
6485         JobStatus jobStatus = createExpeditedJobStatus("testEJTracking_RollingQuota", 1);
6486         synchronized (mQuotaController.mLock) {
6487             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
6488         }
6489         setStandbyBucket(WORKING_INDEX, jobStatus);
6490         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
6491         Handler handler = mQuotaController.getHandler();
6492         spyOn(handler);
6493 
6494         long now = JobSchedulerService.sElapsedRealtimeClock.millis();
6495         final long remainingTimeMs = SECOND_IN_MILLIS;
6496         // The package only has one second to run, but this session is at the edge of the rolling
6497         // window, so as the package "reaches its quota" it will have more to keep running.
6498         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
6499                 createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS,
6500                         10 * SECOND_IN_MILLIS - remainingTimeMs, 1), true);
6501         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
6502                 createTimingSession(now - HOUR_IN_MILLIS,
6503                         mQcConstants.EJ_LIMIT_WORKING_MS - 10 * SECOND_IN_MILLIS, 1), true);
6504 
6505         synchronized (mQuotaController.mLock) {
6506             assertEquals(remainingTimeMs,
6507                     mQuotaController.getRemainingEJExecutionTimeLocked(
6508                             SOURCE_USER_ID, SOURCE_PACKAGE));
6509 
6510             // Start the job.
6511             mQuotaController.prepareForExecutionLocked(jobStatus);
6512         }
6513         advanceElapsedClock(remainingTimeMs);
6514 
6515         // Wait for some extra time to allow for job processing.
6516         verify(mJobSchedulerService,
6517                 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
6518                 .onControllerStateChanged(argThat(jobs -> jobs.size() > 0));
6519         assertTrue(jobStatus.isExpeditedQuotaApproved());
6520         // The job used up the remaining quota, but in that time, the same amount of time in the
6521         // old TimingSession also fell out of the quota window, so it should still have the same
6522         // amount of remaining time left its quota.
6523         synchronized (mQuotaController.mLock) {
6524             assertEquals(remainingTimeMs,
6525                     mQuotaController.getRemainingEJExecutionTimeLocked(
6526                             SOURCE_USER_ID, SOURCE_PACKAGE));
6527         }
6528         // Handler is told to check when the quota will be consumed, not when the initial
6529         // remaining time is over.
6530         verify(handler, atLeast(1)).sendMessageDelayed(
6531                 argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA),
6532                 eq(10 * SECOND_IN_MILLIS));
6533         verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
6534     }
6535 
6536     @Test
testEJDebitTallying()6537     public void testEJDebitTallying() {
6538         setStandbyBucket(RARE_INDEX);
6539         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
6540         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
6541         // 15 seconds for each 30 second chunk.
6542         setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 30 * SECOND_IN_MILLIS);
6543         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 15 * SECOND_IN_MILLIS);
6544 
6545         // No history. Debits should be 0.
6546         ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
6547         assertEquals(0, debit.getTallyLocked());
6548         assertEquals(10 * MINUTE_IN_MILLIS,
6549                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6550 
6551         // Regular job shouldn't affect EJ tally.
6552         JobStatus regJob = createJobStatus("testEJDebitTallying", 1);
6553         synchronized (mQuotaController.mLock) {
6554             mQuotaController.maybeStartTrackingJobLocked(regJob, null);
6555             mQuotaController.prepareForExecutionLocked(regJob);
6556         }
6557         advanceElapsedClock(5000);
6558         synchronized (mQuotaController.mLock) {
6559             mQuotaController.maybeStopTrackingJobLocked(regJob, null, false);
6560         }
6561         assertEquals(0, debit.getTallyLocked());
6562         assertEquals(10 * MINUTE_IN_MILLIS,
6563                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6564 
6565         // EJ job should affect EJ tally.
6566         JobStatus eJob = createExpeditedJobStatus("testEJDebitTallying", 2);
6567         synchronized (mQuotaController.mLock) {
6568             mQuotaController.maybeStartTrackingJobLocked(eJob, null);
6569             mQuotaController.prepareForExecutionLocked(eJob);
6570         }
6571         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
6572         synchronized (mQuotaController.mLock) {
6573             mQuotaController.maybeStopTrackingJobLocked(eJob, null, false);
6574         }
6575         assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked());
6576         assertEquals(5 * MINUTE_IN_MILLIS,
6577                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6578 
6579         // Instantaneous event for a different user shouldn't affect tally.
6580         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
6581         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS);
6582 
6583         UsageEvents.Event event =
6584                 new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
6585         event.mPackage = SOURCE_PACKAGE;
6586         mUsageEventListener.onUsageEvent(SOURCE_USER_ID + 10, event);
6587         assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked());
6588 
6589         // Instantaneous event for correct user should reduce tally.
6590         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
6591 
6592         mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
6593         waitForNonDelayedMessagesProcessed();
6594         assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked());
6595         assertEquals(6 * MINUTE_IN_MILLIS,
6596                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6597 
6598         // Activity start shouldn't reduce tally, but duration with activity started should affect
6599         // remaining EJ time.
6600         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
6601         event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_RESUMED, sSystemClock.millis());
6602         event.mPackage = SOURCE_PACKAGE;
6603         mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
6604         waitForNonDelayedMessagesProcessed();
6605         advanceElapsedClock(30 * SECOND_IN_MILLIS);
6606         assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked());
6607         assertEquals(6 * MINUTE_IN_MILLIS + 15 * SECOND_IN_MILLIS,
6608                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6609         advanceElapsedClock(30 * SECOND_IN_MILLIS);
6610         assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked());
6611         assertEquals(6 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
6612                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6613 
6614         // With activity pausing/stopping/destroying, tally should be updated.
6615         advanceElapsedClock(MINUTE_IN_MILLIS);
6616         event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_DESTROYED, sSystemClock.millis());
6617         event.mPackage = SOURCE_PACKAGE;
6618         mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
6619         waitForNonDelayedMessagesProcessed();
6620         assertEquals(3 * MINUTE_IN_MILLIS, debit.getTallyLocked());
6621         assertEquals(7 * MINUTE_IN_MILLIS,
6622                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6623     }
6624 
6625     @Test
testEJDebitTallying_StaleSession()6626     public void testEJDebitTallying_StaleSession() {
6627         setStandbyBucket(RARE_INDEX);
6628         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS);
6629 
6630         final long nowElapsed = sElapsedRealtimeClock.millis();
6631         TimingSession ts = new TimingSession(nowElapsed, nowElapsed + 10 * MINUTE_IN_MILLIS, 5);
6632         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true);
6633 
6634         // Make the session stale.
6635         advanceElapsedClock(12 * MINUTE_IN_MILLIS + mQcConstants.EJ_WINDOW_SIZE_MS);
6636 
6637         // With lazy deletion, we don't update the tally until getRemainingEJExecutionTimeLocked()
6638         // is called, so call that first.
6639         assertEquals(10 * MINUTE_IN_MILLIS,
6640                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6641         ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
6642         assertEquals(0, debit.getTallyLocked());
6643     }
6644 
6645     /**
6646      * Tests that rewards are properly accounted when there's no EJ running and the rewards exceed
6647      * the accumulated debits.
6648      */
6649     @Test
testEJDebitTallying_RewardExceedDebits_NoActiveSession()6650     public void testEJDebitTallying_RewardExceedDebits_NoActiveSession() {
6651         setStandbyBucket(WORKING_INDEX);
6652         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
6653         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 30 * MINUTE_IN_MILLIS);
6654         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS);
6655 
6656         final long nowElapsed = sElapsedRealtimeClock.millis();
6657         TimingSession ts = new TimingSession(nowElapsed - 5 * MINUTE_IN_MILLIS,
6658                 nowElapsed - 4 * MINUTE_IN_MILLIS, 2);
6659         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true);
6660 
6661         ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
6662         assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked());
6663         assertEquals(29 * MINUTE_IN_MILLIS,
6664                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6665 
6666         advanceElapsedClock(30 * SECOND_IN_MILLIS);
6667         UsageEvents.Event event =
6668                 new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
6669         event.mPackage = SOURCE_PACKAGE;
6670         mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
6671         waitForNonDelayedMessagesProcessed();
6672         assertEquals(0, debit.getTallyLocked());
6673         assertEquals(30 * MINUTE_IN_MILLIS,
6674                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6675 
6676         advanceElapsedClock(MINUTE_IN_MILLIS);
6677         assertEquals(0, debit.getTallyLocked());
6678         assertEquals(30 * MINUTE_IN_MILLIS,
6679                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6680 
6681         // Excessive rewards don't increase maximum quota.
6682         event = new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
6683         event.mPackage = SOURCE_PACKAGE;
6684         mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
6685         waitForNonDelayedMessagesProcessed();
6686         assertEquals(0, debit.getTallyLocked());
6687         assertEquals(30 * MINUTE_IN_MILLIS,
6688                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6689     }
6690 
6691     /**
6692      * Tests that rewards are properly accounted when there's an active EJ running and the rewards
6693      * exceed the accumulated debits.
6694      */
6695     @Test
testEJDebitTallying_RewardExceedDebits_ActiveSession()6696     public void testEJDebitTallying_RewardExceedDebits_ActiveSession() {
6697         setStandbyBucket(WORKING_INDEX);
6698         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
6699         setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 30 * MINUTE_IN_MILLIS);
6700         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS);
6701         // 15 seconds for each 30 second chunk.
6702         setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 30 * SECOND_IN_MILLIS);
6703         setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 15 * SECOND_IN_MILLIS);
6704 
6705         final long nowElapsed = sElapsedRealtimeClock.millis();
6706         TimingSession ts = new TimingSession(nowElapsed - 5 * MINUTE_IN_MILLIS,
6707                 nowElapsed - 4 * MINUTE_IN_MILLIS, 2);
6708         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true);
6709 
6710         ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE);
6711         assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked());
6712         assertEquals(29 * MINUTE_IN_MILLIS,
6713                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6714 
6715         // With rewards coming in while an EJ is running, the remaining execution time should be
6716         // adjusted accordingly (decrease due to EJ running + increase from reward).
6717         JobStatus eJob =
6718                 createExpeditedJobStatus("testEJDebitTallying_RewardExceedDebits_ActiveSession", 1);
6719         synchronized (mQuotaController.mLock) {
6720             mQuotaController.maybeStartTrackingJobLocked(eJob, null);
6721             mQuotaController.prepareForExecutionLocked(eJob);
6722         }
6723         advanceElapsedClock(30 * SECOND_IN_MILLIS);
6724         assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked());
6725         assertEquals(28 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
6726                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6727 
6728         advanceElapsedClock(30 * SECOND_IN_MILLIS);
6729         UsageEvents.Event event =
6730                 new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
6731         event.mPackage = SOURCE_PACKAGE;
6732         mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
6733         waitForNonDelayedMessagesProcessed();
6734         assertEquals(0, debit.getTallyLocked());
6735         assertEquals(29 * MINUTE_IN_MILLIS,
6736                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6737 
6738         advanceElapsedClock(MINUTE_IN_MILLIS);
6739         assertEquals(0, debit.getTallyLocked());
6740         assertEquals(28 * MINUTE_IN_MILLIS,
6741                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6742 
6743         // Activity start shouldn't reduce tally, but duration with activity started should affect
6744         // remaining EJ time.
6745         event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_RESUMED, sSystemClock.millis());
6746         event.mPackage = SOURCE_PACKAGE;
6747         mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
6748         waitForNonDelayedMessagesProcessed();
6749         advanceElapsedClock(30 * SECOND_IN_MILLIS);
6750         assertEquals(0, debit.getTallyLocked());
6751         // Decrease by 30 seconds for running EJ, increase by 15 seconds due to ongoing activity.
6752         assertEquals(27 * MINUTE_IN_MILLIS + 45 * SECOND_IN_MILLIS,
6753                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6754         advanceElapsedClock(30 * SECOND_IN_MILLIS);
6755         assertEquals(0, debit.getTallyLocked());
6756         assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
6757                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6758 
6759         advanceElapsedClock(MINUTE_IN_MILLIS);
6760         assertEquals(0, debit.getTallyLocked());
6761         assertEquals(27 * MINUTE_IN_MILLIS,
6762                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6763 
6764         event = new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis());
6765         event.mPackage = SOURCE_PACKAGE;
6766         mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
6767         waitForNonDelayedMessagesProcessed();
6768         assertEquals(0, debit.getTallyLocked());
6769         assertEquals(28 * MINUTE_IN_MILLIS,
6770                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6771 
6772         advanceElapsedClock(MINUTE_IN_MILLIS);
6773         assertEquals(0, debit.getTallyLocked());
6774         assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
6775                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6776 
6777         // At this point, with activity pausing/stopping/destroying, since we're giving a reward,
6778         // tally should remain 0, and time remaining shouldn't change since it was accounted for
6779         // at every step.
6780         event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_DESTROYED, sSystemClock.millis());
6781         event.mPackage = SOURCE_PACKAGE;
6782         mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event);
6783         waitForNonDelayedMessagesProcessed();
6784         assertEquals(0, debit.getTallyLocked());
6785         assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS,
6786                 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
6787     }
6788 }
6789