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