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