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