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