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