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.doNothing;
20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
26 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
27 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
28 import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
29 import static com.android.server.job.JobSchedulerService.RARE_INDEX;
30 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
31 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertFalse;
35 import static org.junit.Assert.assertNotEquals;
36 import static org.junit.Assert.assertNull;
37 import static org.junit.Assert.assertTrue;
38 import static org.junit.Assert.fail;
39 import static org.mockito.ArgumentMatchers.any;
40 import static org.mockito.ArgumentMatchers.anyInt;
41 import static org.mockito.ArgumentMatchers.anyLong;
42 import static org.mockito.Mockito.atLeast;
43 import static org.mockito.Mockito.eq;
44 import static org.mockito.Mockito.never;
45 import static org.mockito.Mockito.timeout;
46 import static org.mockito.Mockito.times;
47 import static org.mockito.Mockito.verify;
48 
49 import android.app.ActivityManager;
50 import android.app.ActivityManagerInternal;
51 import android.app.AlarmManager;
52 import android.app.AppGlobals;
53 import android.app.IActivityManager;
54 import android.app.IUidObserver;
55 import android.app.job.JobInfo;
56 import android.app.usage.UsageStatsManager;
57 import android.app.usage.UsageStatsManagerInternal;
58 import android.content.BroadcastReceiver;
59 import android.content.ComponentName;
60 import android.content.Context;
61 import android.content.Intent;
62 import android.content.pm.IPackageManager;
63 import android.content.pm.PackageManagerInternal;
64 import android.os.BatteryManager;
65 import android.os.BatteryManagerInternal;
66 import android.os.Handler;
67 import android.os.Looper;
68 import android.os.RemoteException;
69 import android.os.SystemClock;
70 import android.util.SparseBooleanArray;
71 
72 import androidx.test.runner.AndroidJUnit4;
73 
74 import com.android.server.LocalServices;
75 import com.android.server.job.JobSchedulerService;
76 import com.android.server.job.JobSchedulerService.Constants;
77 import com.android.server.job.JobServiceContext;
78 import com.android.server.job.JobStore;
79 import com.android.server.job.controllers.QuotaController.ExecutionStats;
80 import com.android.server.job.controllers.QuotaController.TimingSession;
81 import com.android.server.usage.AppStandbyInternal;
82 
83 import org.junit.After;
84 import org.junit.Before;
85 import org.junit.Test;
86 import org.junit.runner.RunWith;
87 import org.mockito.ArgumentCaptor;
88 import org.mockito.InOrder;
89 import org.mockito.Mock;
90 import org.mockito.MockitoSession;
91 import org.mockito.quality.Strictness;
92 
93 import java.time.Clock;
94 import java.time.Duration;
95 import java.time.ZoneOffset;
96 import java.util.ArrayList;
97 import java.util.List;
98 
99 @RunWith(AndroidJUnit4.class)
100 public class QuotaControllerTest {
101     private static final long SECOND_IN_MILLIS = 1000L;
102     private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
103     private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
104     private static final String TAG_CLEANUP = "*job.cleanup*";
105     private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
106     private static final int CALLING_UID = 1000;
107     private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
108     private static final int SOURCE_USER_ID = 0;
109 
110     private BroadcastReceiver mChargingReceiver;
111     private QuotaController mQuotaController;
112     private QuotaController.QcConstants mQcConstants;
113     private int mSourceUid;
114     private IUidObserver mUidObserver;
115 
116     private MockitoSession mMockingSession;
117     @Mock
118     private ActivityManagerInternal mActivityMangerInternal;
119     @Mock
120     private AlarmManager mAlarmManager;
121     @Mock
122     private Context mContext;
123     @Mock
124     private JobSchedulerService mJobSchedulerService;
125     @Mock
126     private UsageStatsManagerInternal mUsageStatsManager;
127 
128     private JobStore mJobStore;
129 
130     @Before
setUp()131     public void setUp() {
132         mMockingSession = mockitoSession()
133                 .initMocks(this)
134                 .strictness(Strictness.LENIENT)
135                 .mockStatic(LocalServices.class)
136                 .startMocking();
137 
138         // Called in StateController constructor.
139         when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
140         when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
141         when(mJobSchedulerService.getConstants()).thenReturn(mock(Constants.class));
142         // Called in QuotaController constructor.
143         IActivityManager activityManager = ActivityManager.getService();
144         spyOn(activityManager);
145         try {
146             doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
147         } catch (RemoteException e) {
148             fail("registerUidObserver threw exception: " + e.getMessage());
149         }
150         when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
151         when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
152         doReturn(mActivityMangerInternal)
153                 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
154         doReturn(mock(AppStandbyInternal.class))
155                 .when(() -> LocalServices.getService(AppStandbyInternal.class));
156         doReturn(mock(BatteryManagerInternal.class))
157                 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
158         doReturn(mUsageStatsManager)
159                 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
160         // Used in JobStatus.
161         doReturn(mock(PackageManagerInternal.class))
162                 .when(() -> LocalServices.getService(PackageManagerInternal.class));
163         // Used in QuotaController.Handler.
164         mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
165         when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
166 
167         // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
168         // in the past, and QuotaController sometimes floors values at 0, so if the test time
169         // causes sessions with negative timestamps, they will fail.
170         JobSchedulerService.sSystemClock =
171                 getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
172                         24 * HOUR_IN_MILLIS);
173         JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
174                 Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC),
175                 24 * HOUR_IN_MILLIS);
176         JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
177                 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
178                 24 * HOUR_IN_MILLIS);
179 
180         // Initialize real objects.
181         // Capture the listeners.
182         ArgumentCaptor<BroadcastReceiver> receiverCaptor =
183                 ArgumentCaptor.forClass(BroadcastReceiver.class);
184         ArgumentCaptor<IUidObserver> uidObserverCaptor =
185                 ArgumentCaptor.forClass(IUidObserver.class);
186         mQuotaController = new QuotaController(mJobSchedulerService);
187 
188         verify(mContext).registerReceiver(receiverCaptor.capture(), any());
189         mChargingReceiver = receiverCaptor.getValue();
190         try {
191             verify(activityManager).registerUidObserver(
192                     uidObserverCaptor.capture(),
193                     eq(ActivityManager.UID_OBSERVER_PROCSTATE),
194                     eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE),
195                     any());
196             mUidObserver = uidObserverCaptor.getValue();
197             mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
198         } catch (RemoteException e) {
199             fail(e.getMessage());
200         }
201         mQcConstants = mQuotaController.getQcConstants();
202     }
203 
204     @After
tearDown()205     public void tearDown() {
206         if (mMockingSession != null) {
207             mMockingSession.finishMocking();
208         }
209     }
210 
getAdvancedClock(Clock clock, long incrementMs)211     private Clock getAdvancedClock(Clock clock, long incrementMs) {
212         return Clock.offset(clock, Duration.ofMillis(incrementMs));
213     }
214 
advanceElapsedClock(long incrementMs)215     private void advanceElapsedClock(long incrementMs) {
216         JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
217                 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
218     }
219 
setCharging()220     private void setCharging() {
221         Intent intent = new Intent(BatteryManager.ACTION_CHARGING);
222         mChargingReceiver.onReceive(mContext, intent);
223     }
224 
setDischarging()225     private void setDischarging() {
226         Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING);
227         mChargingReceiver.onReceive(mContext, intent);
228     }
229 
setProcessState(int procState)230     private void setProcessState(int procState) {
231         setProcessState(procState, mSourceUid);
232     }
233 
setProcessState(int procState, int uid)234     private void setProcessState(int procState, int uid) {
235         try {
236             doReturn(procState).when(mActivityMangerInternal).getUidProcessState(uid);
237             SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
238             spyOn(foregroundUids);
239             mUidObserver.onUidStateChanged(uid, procState, 0,
240                     ActivityManager.PROCESS_CAPABILITY_NONE);
241             if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
242                 verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
243                         .put(eq(uid), eq(true));
244                 assertTrue(foregroundUids.get(uid));
245             } else {
246                 verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)).delete(eq(uid));
247                 assertFalse(foregroundUids.get(uid));
248             }
249         } catch (RemoteException e) {
250             fail("registerUidObserver threw exception: " + e.getMessage());
251         }
252     }
253 
bucketIndexToUsageStatsBucket(int bucketIndex)254     private int bucketIndexToUsageStatsBucket(int bucketIndex) {
255         switch (bucketIndex) {
256             case ACTIVE_INDEX:
257                 return UsageStatsManager.STANDBY_BUCKET_ACTIVE;
258             case WORKING_INDEX:
259                 return UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
260             case FREQUENT_INDEX:
261                 return UsageStatsManager.STANDBY_BUCKET_FREQUENT;
262             case RARE_INDEX:
263                 return UsageStatsManager.STANDBY_BUCKET_RARE;
264             default:
265                 return UsageStatsManager.STANDBY_BUCKET_NEVER;
266         }
267     }
268 
setStandbyBucket(int bucketIndex)269     private void setStandbyBucket(int bucketIndex) {
270         when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
271                 anyLong())).thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
272     }
273 
setStandbyBucket(int bucketIndex, JobStatus... jobs)274     private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
275         setStandbyBucket(bucketIndex);
276         for (JobStatus job : jobs) {
277             job.setStandbyBucket(bucketIndex);
278             when(mUsageStatsManager.getAppStandbyBucket(
279                     eq(job.getSourcePackageName()), eq(job.getSourceUserId()), anyLong()))
280                     .thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
281         }
282     }
283 
trackJobs(JobStatus... jobs)284     private void trackJobs(JobStatus... jobs) {
285         for (JobStatus job : jobs) {
286             mJobStore.add(job);
287             mQuotaController.maybeStartTrackingJobLocked(job, null);
288         }
289     }
290 
createJobStatus(String testTag, int jobId)291     private JobStatus createJobStatus(String testTag, int jobId) {
292         JobInfo jobInfo = new JobInfo.Builder(jobId,
293                 new ComponentName(mContext, "TestQuotaJobService"))
294                 .build();
295         return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
296     }
297 
createJobStatus(String testTag, String packageName, int callingUid, JobInfo jobInfo)298     private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
299             JobInfo jobInfo) {
300         JobStatus js = JobStatus.createFromJobInfo(
301                 jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
302         // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
303         js.setStandbyBucket(FREQUENT_INDEX);
304         // Make sure Doze and background-not-restricted don't affect tests.
305         js.setDeviceNotDozingConstraintSatisfied(/* state */ true, /* whitelisted */false);
306         js.setBackgroundNotRestrictedConstraintSatisfied(true);
307         return js;
308     }
309 
createTimingSession(long start, long duration, int count)310     private TimingSession createTimingSession(long start, long duration, int count) {
311         return new TimingSession(start, start + duration, count);
312     }
313 
314     @Test
testSaveTimingSession()315     public void testSaveTimingSession() {
316         assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
317 
318         List<TimingSession> expected = new ArrayList<>();
319         TimingSession one = new TimingSession(1, 10, 1);
320         TimingSession two = new TimingSession(11, 20, 2);
321         TimingSession thr = new TimingSession(21, 30, 3);
322 
323         mQuotaController.saveTimingSession(0, "com.android.test", one);
324         expected.add(one);
325         assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
326 
327         mQuotaController.saveTimingSession(0, "com.android.test", two);
328         expected.add(two);
329         assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
330 
331         mQuotaController.saveTimingSession(0, "com.android.test", thr);
332         expected.add(thr);
333         assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
334     }
335 
336     @Test
testDeleteObsoleteSessionsLocked()337     public void testDeleteObsoleteSessionsLocked() {
338         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
339         TimingSession one = createTimingSession(
340                 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
341         TimingSession two = createTimingSession(
342                 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
343         TimingSession thr = createTimingSession(
344                 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
345         // Overlaps 24 hour boundary.
346         TimingSession fou = createTimingSession(
347                 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
348         // Way past the 24 hour boundary.
349         TimingSession fiv = createTimingSession(
350                 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
351         List<TimingSession> expected = new ArrayList<>();
352         // Added in correct (chronological) order.
353         expected.add(fou);
354         expected.add(thr);
355         expected.add(two);
356         expected.add(one);
357         mQuotaController.saveTimingSession(0, "com.android.test", fiv);
358         mQuotaController.saveTimingSession(0, "com.android.test", fou);
359         mQuotaController.saveTimingSession(0, "com.android.test", thr);
360         mQuotaController.saveTimingSession(0, "com.android.test", two);
361         mQuotaController.saveTimingSession(0, "com.android.test", one);
362 
363         mQuotaController.deleteObsoleteSessionsLocked();
364 
365         assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
366     }
367 
368     @Test
testOnAppRemovedLocked()369     public void testOnAppRemovedLocked() {
370         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
371         mQuotaController.saveTimingSession(0, "com.android.test.remove",
372                 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
373         mQuotaController.saveTimingSession(0, "com.android.test.remove",
374                 createTimingSession(
375                         now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
376         mQuotaController.saveTimingSession(0, "com.android.test.remove",
377                 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
378         // Test that another app isn't affected.
379         TimingSession one = createTimingSession(
380                 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
381         TimingSession two = createTimingSession(
382                 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
383         List<TimingSession> expected = new ArrayList<>();
384         // Added in correct (chronological) order.
385         expected.add(two);
386         expected.add(one);
387         mQuotaController.saveTimingSession(0, "com.android.test.stay", two);
388         mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
389 
390         ExecutionStats expectedStats = new ExecutionStats();
391         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
392         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
393         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
394         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
395 
396         final int uid = 10001;
397         mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
398         assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
399         assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
400         assertEquals(expectedStats,
401                 mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
402         assertNotEquals(expectedStats,
403                 mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
404 
405         assertFalse(mQuotaController.getForegroundUids().get(uid));
406     }
407 
408     @Test
testOnUserRemovedLocked()409     public void testOnUserRemovedLocked() {
410         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
411         mQuotaController.saveTimingSession(0, "com.android.test",
412                 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
413         mQuotaController.saveTimingSession(0, "com.android.test",
414                 createTimingSession(
415                         now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
416         mQuotaController.saveTimingSession(0, "com.android.test",
417                 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
418         // Test that another user isn't affected.
419         TimingSession one = createTimingSession(
420                 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
421         TimingSession two = createTimingSession(
422                 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
423         List<TimingSession> expected = new ArrayList<>();
424         // Added in correct (chronological) order.
425         expected.add(two);
426         expected.add(one);
427         mQuotaController.saveTimingSession(10, "com.android.test", two);
428         mQuotaController.saveTimingSession(10, "com.android.test", one);
429 
430         ExecutionStats expectedStats = new ExecutionStats();
431         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
432         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
433         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
434         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
435 
436         mQuotaController.onUserRemovedLocked(0);
437         assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
438         assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test"));
439         assertEquals(expectedStats,
440                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
441         assertNotEquals(expectedStats,
442                 mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX));
443     }
444 
445     @Test
testUpdateExecutionStatsLocked_NoTimer()446     public void testUpdateExecutionStatsLocked_NoTimer() {
447         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
448         // Added in chronological order.
449         mQuotaController.saveTimingSession(0, "com.android.test",
450                 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
451         mQuotaController.saveTimingSession(0, "com.android.test",
452                 createTimingSession(
453                         now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
454         mQuotaController.saveTimingSession(0, "com.android.test",
455                 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
456         mQuotaController.saveTimingSession(0, "com.android.test",
457                 createTimingSession(
458                         now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1));
459         mQuotaController.saveTimingSession(0, "com.android.test",
460                 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
461 
462         // Test an app that hasn't had any activity.
463         ExecutionStats expectedStats = new ExecutionStats();
464         ExecutionStats inputStats = new ExecutionStats();
465 
466         inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
467         inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
468         inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
469         // Invalid time is now +24 hours since there are no sessions at all for the app.
470         expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
471         mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
472         assertEquals(expectedStats, inputStats);
473 
474         inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
475         // Invalid time is now +18 hours since there are no sessions in the window but the earliest
476         // session is 6 hours ago.
477         expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS;
478         expectedStats.executionTimeInWindowMs = 0;
479         expectedStats.bgJobCountInWindow = 0;
480         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
481         expectedStats.bgJobCountInMaxPeriod = 15;
482         expectedStats.sessionCountInWindow = 0;
483         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
484         assertEquals(expectedStats, inputStats);
485 
486         inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
487         // Invalid time is now since the session straddles the window cutoff time.
488         expectedStats.expirationTimeElapsed = now;
489         expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
490         expectedStats.bgJobCountInWindow = 3;
491         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
492         expectedStats.bgJobCountInMaxPeriod = 15;
493         expectedStats.sessionCountInWindow = 1;
494         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
495         assertEquals(expectedStats, inputStats);
496 
497         inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
498         // Invalid time is now since the start of the session is at the very edge of the window
499         // cutoff time.
500         expectedStats.expirationTimeElapsed = now;
501         expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
502         expectedStats.bgJobCountInWindow = 3;
503         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
504         expectedStats.bgJobCountInMaxPeriod = 15;
505         expectedStats.sessionCountInWindow = 1;
506         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
507         assertEquals(expectedStats, inputStats);
508 
509         inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
510         // Invalid time is now +44 minutes since the earliest session in the window is now-5
511         // minutes.
512         expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
513         expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
514         expectedStats.bgJobCountInWindow = 3;
515         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
516         expectedStats.bgJobCountInMaxPeriod = 15;
517         expectedStats.sessionCountInWindow = 1;
518         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
519         assertEquals(expectedStats, inputStats);
520 
521         inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
522         // Invalid time is now since the session is at the very edge of the window cutoff time.
523         expectedStats.expirationTimeElapsed = now;
524         expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
525         expectedStats.bgJobCountInWindow = 4;
526         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
527         expectedStats.bgJobCountInMaxPeriod = 15;
528         expectedStats.sessionCountInWindow = 2;
529         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
530         assertEquals(expectedStats, inputStats);
531 
532         inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
533         inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 2;
534         // Invalid time is now since the start of the session is at the very edge of the window
535         // cutoff time.
536         expectedStats.expirationTimeElapsed = now;
537         expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
538         expectedStats.bgJobCountInWindow = 5;
539         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
540         expectedStats.bgJobCountInMaxPeriod = 15;
541         expectedStats.sessionCountInWindow = 3;
542         expectedStats.inQuotaTimeElapsed = now + 11 * MINUTE_IN_MILLIS;
543         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
544         assertEquals(expectedStats, inputStats);
545 
546         inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
547         inputStats.jobCountLimit = expectedStats.jobCountLimit = 6;
548         inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100;
549         // Invalid time is now since the session straddles the window cutoff time.
550         expectedStats.expirationTimeElapsed = now;
551         expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
552         expectedStats.bgJobCountInWindow = 10;
553         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
554         expectedStats.bgJobCountInMaxPeriod = 15;
555         expectedStats.sessionCountInWindow = 4;
556         expectedStats.inQuotaTimeElapsed = now + 5 * MINUTE_IN_MILLIS;
557         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
558         assertEquals(expectedStats, inputStats);
559 
560         inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
561         // Invalid time is now +59 minutes since the earliest session in the window is now-121
562         // minutes.
563         expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
564         expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
565         expectedStats.bgJobCountInWindow = 10;
566         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
567         expectedStats.bgJobCountInMaxPeriod = 15;
568         expectedStats.sessionCountInWindow = 4;
569         // App goes under job execution time limit in ~61 minutes, but will be under job count limit
570         // in 65 minutes.
571         expectedStats.inQuotaTimeElapsed = now + 65 * MINUTE_IN_MILLIS;
572         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
573         assertEquals(expectedStats, inputStats);
574 
575         inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
576         // Invalid time is now since the start of the session is at the very edge of the window
577         // cutoff time.
578         expectedStats.expirationTimeElapsed = now;
579         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
580         expectedStats.bgJobCountInWindow = 15;
581         expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
582         expectedStats.bgJobCountInMaxPeriod = 15;
583         expectedStats.sessionCountInWindow = 5;
584         expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS + 5 * MINUTE_IN_MILLIS;
585         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
586         assertEquals(expectedStats, inputStats);
587 
588         // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period.
589         mQuotaController.getTimingSessions(0, "com.android.test")
590                 .add(0,
591                         createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
592         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
593         inputStats.jobCountLimit = expectedStats.jobCountLimit = 100;
594         // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
595         // before the end of the max period cutoff time.
596         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
597         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
598         expectedStats.bgJobCountInWindow = 15;
599         expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
600         expectedStats.bgJobCountInMaxPeriod = 18;
601         expectedStats.sessionCountInWindow = 5;
602         expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
603                 + mQcConstants.IN_QUOTA_BUFFER_MS;
604         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
605         assertEquals(expectedStats, inputStats);
606 
607         mQuotaController.getTimingSessions(0, "com.android.test")
608                 .add(0,
609                         createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
610                                 2 * MINUTE_IN_MILLIS, 2));
611         inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
612         // Invalid time is now since the earliest session straddles the max period cutoff time.
613         expectedStats.expirationTimeElapsed = now;
614         expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
615         expectedStats.bgJobCountInWindow = 15;
616         expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
617         expectedStats.bgJobCountInMaxPeriod = 20;
618         expectedStats.sessionCountInWindow = 5;
619         expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS
620                 + mQcConstants.IN_QUOTA_BUFFER_MS;
621         mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
622         assertEquals(expectedStats, inputStats);
623     }
624 
625     @Test
testUpdateExecutionStatsLocked_WithTimer()626     public void testUpdateExecutionStatsLocked_WithTimer() {
627         final long now = sElapsedRealtimeClock.millis();
628         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
629 
630         ExecutionStats expectedStats = new ExecutionStats();
631         ExecutionStats inputStats = new ExecutionStats();
632         inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
633         inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
634         inputStats.sessionCountLimit = expectedStats.sessionCountLimit =
635                 mQcConstants.MAX_SESSION_COUNT_RARE;
636         // Active timer isn't counted as session yet.
637         expectedStats.sessionCountInWindow = 0;
638         // Timer only, under quota.
639         for (int i = 1; i < mQcConstants.MAX_JOB_COUNT_RARE; ++i) {
640             JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", i);
641             setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window
642             mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
643             mQuotaController.prepareForExecutionLocked(jobStatus);
644             advanceElapsedClock(7000);
645 
646             expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis();
647             expectedStats.executionTimeInWindowMs = expectedStats.executionTimeInMaxPeriodMs =
648                     7000 * i;
649             expectedStats.bgJobCountInWindow = expectedStats.bgJobCountInMaxPeriod = i;
650             mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats);
651             assertEquals(expectedStats, inputStats);
652             assertTrue(mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
653                     RARE_INDEX));
654             assertTrue("Job not ready: " + jobStatus, jobStatus.isReady());
655         }
656 
657         // Add old session. Make sure values are combined correctly.
658         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
659                 createTimingSession(sElapsedRealtimeClock.millis() - (6 * HOUR_IN_MILLIS),
660                         10 * MINUTE_IN_MILLIS, 5));
661         expectedStats.sessionCountInWindow = 1;
662 
663         expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS;
664         expectedStats.executionTimeInWindowMs += 10 * MINUTE_IN_MILLIS;
665         expectedStats.executionTimeInMaxPeriodMs += 10 * MINUTE_IN_MILLIS;
666         expectedStats.bgJobCountInWindow += 5;
667         expectedStats.bgJobCountInMaxPeriod += 5;
668         // Active timer is under quota, so out of quota due to old session.
669         expectedStats.inQuotaTimeElapsed =
670                 sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS;
671         mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats);
672         assertEquals(expectedStats, inputStats);
673         assertFalse(
674                 mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
675 
676         // Quota should be exceeded due to activity in active timer.
677         JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", 0);
678         setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window
679         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
680         mQuotaController.prepareForExecutionLocked(jobStatus);
681         advanceElapsedClock(10000);
682 
683         expectedStats.executionTimeInWindowMs += 10000;
684         expectedStats.executionTimeInMaxPeriodMs += 10000;
685         expectedStats.bgJobCountInWindow++;
686         expectedStats.bgJobCountInMaxPeriod++;
687         // Out of quota due to activity in active timer, so in quota time should be when enough
688         // time has passed since active timer.
689         expectedStats.inQuotaTimeElapsed =
690                 sElapsedRealtimeClock.millis() + expectedStats.windowSizeMs;
691         mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats);
692         assertEquals(expectedStats, inputStats);
693         assertFalse(
694                 mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX));
695         assertFalse("Job unexpectedly ready: " + jobStatus, jobStatus.isReady());
696     }
697 
698     /**
699      * Tests that getExecutionStatsLocked returns the correct stats.
700      */
701     @Test
testGetExecutionStatsLocked_Values()702     public void testGetExecutionStatsLocked_Values() {
703         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
704         mQuotaController.saveTimingSession(0, "com.android.test",
705                 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
706         mQuotaController.saveTimingSession(0, "com.android.test",
707                 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
708         mQuotaController.saveTimingSession(0, "com.android.test",
709                 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
710         mQuotaController.saveTimingSession(0, "com.android.test",
711                 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
712 
713         ExecutionStats expectedStats = new ExecutionStats();
714 
715         // Active
716         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
717         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
718         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
719         expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
720         expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
721         expectedStats.bgJobCountInWindow = 5;
722         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
723         expectedStats.bgJobCountInMaxPeriod = 20;
724         expectedStats.sessionCountInWindow = 1;
725         assertEquals(expectedStats,
726                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
727 
728         // Working
729         expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
730         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
731         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
732         expectedStats.expirationTimeElapsed = now;
733         expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
734         expectedStats.bgJobCountInWindow = 10;
735         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
736         expectedStats.bgJobCountInMaxPeriod = 20;
737         expectedStats.sessionCountInWindow = 2;
738         expectedStats.inQuotaTimeElapsed = now + 3 * MINUTE_IN_MILLIS
739                 + mQcConstants.IN_QUOTA_BUFFER_MS;
740         assertEquals(expectedStats,
741                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
742 
743         // Frequent
744         expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
745         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
746         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
747         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
748         expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
749         expectedStats.bgJobCountInWindow = 15;
750         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
751         expectedStats.bgJobCountInMaxPeriod = 20;
752         expectedStats.sessionCountInWindow = 3;
753         expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
754                 + mQcConstants.IN_QUOTA_BUFFER_MS;
755         assertEquals(expectedStats,
756                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
757 
758         // Rare
759         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
760         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
761         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
762         expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
763         expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
764         expectedStats.bgJobCountInWindow = 20;
765         expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
766         expectedStats.bgJobCountInMaxPeriod = 20;
767         expectedStats.sessionCountInWindow = 4;
768         expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS
769                 + mQcConstants.IN_QUOTA_BUFFER_MS;
770         assertEquals(expectedStats,
771                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
772     }
773 
774     /**
775      * Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
776      */
777     @Test
testGetExecutionStatsLocked_Values_BeginningOfTime()778     public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
779         // Set time to 3 minutes after boot.
780         advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis());
781         advanceElapsedClock(3 * MINUTE_IN_MILLIS);
782 
783         mQuotaController.saveTimingSession(0, "com.android.test",
784                 createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2));
785 
786         ExecutionStats expectedStats = new ExecutionStats();
787 
788         // Active
789         expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
790         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE;
791         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE;
792         expectedStats.expirationTimeElapsed = 11 * MINUTE_IN_MILLIS;
793         expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS;
794         expectedStats.bgJobCountInWindow = 2;
795         expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS;
796         expectedStats.bgJobCountInMaxPeriod = 2;
797         expectedStats.sessionCountInWindow = 1;
798         assertEquals(expectedStats,
799                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
800 
801         // Working
802         expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
803         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING;
804         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING;
805         expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
806         assertEquals(expectedStats,
807                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
808 
809         // Frequent
810         expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
811         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT;
812         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT;
813         expectedStats.expirationTimeElapsed = 8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
814         assertEquals(expectedStats,
815                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
816 
817         // Rare
818         expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
819         expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE;
820         expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE;
821         expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS;
822         assertEquals(expectedStats,
823                 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
824     }
825 
826     /**
827      * Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing.
828      */
829     @Test
testGetExecutionStatsLocked_CoalescingSessions()830     public void testGetExecutionStatsLocked_CoalescingSessions() {
831         for (int i = 0; i < 10; ++i) {
832             mQuotaController.saveTimingSession(0, "com.android.test",
833                     createTimingSession(
834                             JobSchedulerService.sElapsedRealtimeClock.millis(),
835                             5 * MINUTE_IN_MILLIS, 5));
836             advanceElapsedClock(5 * MINUTE_IN_MILLIS);
837             advanceElapsedClock(5 * MINUTE_IN_MILLIS);
838             for (int j = 0; j < 5; ++j) {
839                 mQuotaController.saveTimingSession(0, "com.android.test",
840                         createTimingSession(
841                                 JobSchedulerService.sElapsedRealtimeClock.millis(),
842                                 MINUTE_IN_MILLIS, 2));
843                 advanceElapsedClock(MINUTE_IN_MILLIS);
844                 advanceElapsedClock(54 * SECOND_IN_MILLIS);
845                 mQuotaController.saveTimingSession(0, "com.android.test",
846                         createTimingSession(
847                                 JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1));
848                 advanceElapsedClock(500);
849                 advanceElapsedClock(400);
850                 mQuotaController.saveTimingSession(0, "com.android.test",
851                         createTimingSession(
852                                 JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1));
853                 advanceElapsedClock(100);
854                 advanceElapsedClock(5 * SECOND_IN_MILLIS);
855             }
856             advanceElapsedClock(40 * MINUTE_IN_MILLIS);
857         }
858 
859         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 0;
860         mQcConstants.updateConstants();
861 
862         mQuotaController.invalidateAllExecutionStatsLocked();
863         assertEquals(0, mQuotaController.getExecutionStatsLocked(
864                 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
865         assertEquals(32, mQuotaController.getExecutionStatsLocked(
866                 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
867         assertEquals(128, mQuotaController.getExecutionStatsLocked(
868                 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
869         assertEquals(160, mQuotaController.getExecutionStatsLocked(
870                 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
871 
872         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 500;
873         mQcConstants.updateConstants();
874 
875         mQuotaController.invalidateAllExecutionStatsLocked();
876         assertEquals(0, mQuotaController.getExecutionStatsLocked(
877                 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
878         assertEquals(22, mQuotaController.getExecutionStatsLocked(
879                 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
880         assertEquals(88, mQuotaController.getExecutionStatsLocked(
881                 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
882         assertEquals(110, mQuotaController.getExecutionStatsLocked(
883                 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
884 
885         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 1000;
886         mQcConstants.updateConstants();
887 
888         mQuotaController.invalidateAllExecutionStatsLocked();
889         assertEquals(0, mQuotaController.getExecutionStatsLocked(
890                 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
891         assertEquals(22, mQuotaController.getExecutionStatsLocked(
892                 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
893         assertEquals(88, mQuotaController.getExecutionStatsLocked(
894                 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
895         assertEquals(110, mQuotaController.getExecutionStatsLocked(
896                 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
897 
898         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 5 * SECOND_IN_MILLIS;
899         mQcConstants.updateConstants();
900 
901         mQuotaController.invalidateAllExecutionStatsLocked();
902         assertEquals(0, mQuotaController.getExecutionStatsLocked(
903                 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
904         assertEquals(14, mQuotaController.getExecutionStatsLocked(
905                 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
906         assertEquals(56, mQuotaController.getExecutionStatsLocked(
907                 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
908         assertEquals(70, mQuotaController.getExecutionStatsLocked(
909                 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
910 
911         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = MINUTE_IN_MILLIS;
912         mQcConstants.updateConstants();
913 
914         mQuotaController.invalidateAllExecutionStatsLocked();
915         assertEquals(0, mQuotaController.getExecutionStatsLocked(
916                 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
917         assertEquals(4, mQuotaController.getExecutionStatsLocked(
918                 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
919         assertEquals(16, mQuotaController.getExecutionStatsLocked(
920                 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
921         assertEquals(20, mQuotaController.getExecutionStatsLocked(
922                 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
923 
924         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 5 * MINUTE_IN_MILLIS;
925         mQcConstants.updateConstants();
926 
927         mQuotaController.invalidateAllExecutionStatsLocked();
928         assertEquals(0, mQuotaController.getExecutionStatsLocked(
929                 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
930         assertEquals(2, mQuotaController.getExecutionStatsLocked(
931                 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
932         assertEquals(8, mQuotaController.getExecutionStatsLocked(
933                 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
934         assertEquals(10, mQuotaController.getExecutionStatsLocked(
935                 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
936 
937         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 15 * MINUTE_IN_MILLIS;
938         mQcConstants.updateConstants();
939 
940         mQuotaController.invalidateAllExecutionStatsLocked();
941         assertEquals(0, mQuotaController.getExecutionStatsLocked(
942                 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
943         assertEquals(2, mQuotaController.getExecutionStatsLocked(
944                 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
945         assertEquals(8, mQuotaController.getExecutionStatsLocked(
946                 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
947         assertEquals(10, mQuotaController.getExecutionStatsLocked(
948                 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
949 
950         // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference
951         // between an hour and 15 minutes.
952         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = HOUR_IN_MILLIS;
953         mQcConstants.updateConstants();
954 
955         mQuotaController.invalidateAllExecutionStatsLocked();
956         assertEquals(0, mQuotaController.getExecutionStatsLocked(
957                 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow);
958         assertEquals(2, mQuotaController.getExecutionStatsLocked(
959                 0, "com.android.test", WORKING_INDEX).sessionCountInWindow);
960         assertEquals(8, mQuotaController.getExecutionStatsLocked(
961                 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow);
962         assertEquals(10, mQuotaController.getExecutionStatsLocked(
963                 0, "com.android.test", RARE_INDEX).sessionCountInWindow);
964     }
965 
966     /**
967      * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
968      */
969     @Test
testGetExecutionStatsLocked_Caching()970     public void testGetExecutionStatsLocked_Caching() {
971         spyOn(mQuotaController);
972         doNothing().when(mQuotaController).invalidateAllExecutionStatsLocked();
973 
974         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
975         mQuotaController.saveTimingSession(0, "com.android.test",
976                 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
977         mQuotaController.saveTimingSession(0, "com.android.test",
978                 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
979         mQuotaController.saveTimingSession(0, "com.android.test",
980                 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
981         mQuotaController.saveTimingSession(0, "com.android.test",
982                 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
983         final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0,
984                 "com.android.test", ACTIVE_INDEX);
985         final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0,
986                 "com.android.test", WORKING_INDEX);
987         final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
988                 "com.android.test", FREQUENT_INDEX);
989         final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0,
990                 "com.android.test", RARE_INDEX);
991 
992         // Advance clock so that the working stats shouldn't be the same.
993         advanceElapsedClock(MINUTE_IN_MILLIS);
994         // Change frequent bucket size so that the stats need to be recalculated.
995         mQcConstants.WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS;
996         mQcConstants.updateConstants();
997 
998         ExecutionStats expectedStats = new ExecutionStats();
999         expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
1000         expectedStats.jobCountLimit = originalStatsActive.jobCountLimit;
1001         expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit;
1002         expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
1003         expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
1004         expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
1005         expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
1006         expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
1007         expectedStats.sessionCountInWindow = originalStatsActive.sessionCountInWindow;
1008         expectedStats.inQuotaTimeElapsed = originalStatsActive.inQuotaTimeElapsed;
1009         final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
1010                 "com.android.test", ACTIVE_INDEX);
1011         // Stats for the same bucket should use the same object.
1012         assertTrue(originalStatsActive == newStatsActive);
1013         assertEquals(expectedStats, newStatsActive);
1014 
1015         expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
1016         expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit;
1017         expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit;
1018         expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
1019         expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
1020         expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
1021         expectedStats.sessionCountInWindow = originalStatsWorking.sessionCountInWindow;
1022         expectedStats.inQuotaTimeElapsed = originalStatsWorking.inQuotaTimeElapsed;
1023         final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
1024                 "com.android.test", WORKING_INDEX);
1025         assertTrue(originalStatsWorking == newStatsWorking);
1026         assertNotEquals(expectedStats, newStatsWorking);
1027 
1028         expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
1029         expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit;
1030         expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit;
1031         expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
1032         expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
1033         expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
1034         expectedStats.sessionCountInWindow = originalStatsFrequent.sessionCountInWindow;
1035         expectedStats.inQuotaTimeElapsed = originalStatsFrequent.inQuotaTimeElapsed;
1036         final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
1037                 "com.android.test", FREQUENT_INDEX);
1038         assertTrue(originalStatsFrequent == newStatsFrequent);
1039         assertNotEquals(expectedStats, newStatsFrequent);
1040 
1041         expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
1042         expectedStats.jobCountLimit = originalStatsRare.jobCountLimit;
1043         expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit;
1044         expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
1045         expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
1046         expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
1047         expectedStats.sessionCountInWindow = originalStatsRare.sessionCountInWindow;
1048         expectedStats.inQuotaTimeElapsed = originalStatsRare.inQuotaTimeElapsed;
1049         final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
1050                 "com.android.test", RARE_INDEX);
1051         assertTrue(originalStatsRare == newStatsRare);
1052         assertEquals(expectedStats, newStatsRare);
1053     }
1054 
1055     @Test
testGetMaxJobExecutionTimeLocked()1056     public void testGetMaxJobExecutionTimeLocked() {
1057         mQuotaController.saveTimingSession(0, SOURCE_PACKAGE,
1058                 createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS),
1059                         3 * MINUTE_IN_MILLIS, 5));
1060         JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0);
1061         job.setStandbyBucket(RARE_INDEX);
1062 
1063         setCharging();
1064         assertEquals(JobServiceContext.EXECUTING_TIMESLICE_MILLIS,
1065                 mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
1066 
1067         setDischarging();
1068         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1069         assertEquals(JobServiceContext.EXECUTING_TIMESLICE_MILLIS,
1070                 mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
1071 
1072         // Top-started job
1073         setProcessState(ActivityManager.PROCESS_STATE_TOP);
1074         mQuotaController.maybeStartTrackingJobLocked(job, null);
1075         mQuotaController.prepareForExecutionLocked(job);
1076         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1077         assertEquals(JobServiceContext.EXECUTING_TIMESLICE_MILLIS,
1078                 mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
1079         mQuotaController.maybeStopTrackingJobLocked(job, null, false);
1080 
1081         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
1082         assertEquals(7 * MINUTE_IN_MILLIS,
1083                 mQuotaController.getMaxJobExecutionTimeMsLocked(job));
1084     }
1085 
1086     /**
1087      * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
1088      * window.
1089      */
1090     @Test
testGetTimeUntilQuotaConsumedLocked_BucketWindow()1091     public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() {
1092         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1093         // Close to RARE boundary.
1094         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1095                 createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
1096                         30 * SECOND_IN_MILLIS, 5));
1097         // Far away from FREQUENT boundary.
1098         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1099                 createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
1100         // Overlap WORKING_SET boundary.
1101         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1102                 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
1103                         3 * MINUTE_IN_MILLIS, 5));
1104         // Close to ACTIVE boundary.
1105         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1106                 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
1107 
1108         setStandbyBucket(RARE_INDEX);
1109         assertEquals(30 * SECOND_IN_MILLIS,
1110                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1111         assertEquals(MINUTE_IN_MILLIS,
1112                 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1113 
1114         setStandbyBucket(FREQUENT_INDEX);
1115         assertEquals(MINUTE_IN_MILLIS,
1116                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1117         assertEquals(MINUTE_IN_MILLIS,
1118                 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1119 
1120         setStandbyBucket(WORKING_INDEX);
1121         assertEquals(5 * MINUTE_IN_MILLIS,
1122                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1123         assertEquals(7 * MINUTE_IN_MILLIS,
1124                 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1125 
1126         // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
1127         // max execution time.
1128         setStandbyBucket(ACTIVE_INDEX);
1129         assertEquals(7 * MINUTE_IN_MILLIS,
1130                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1131         assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
1132                 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1133     }
1134 
1135     /**
1136      * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit.
1137      */
1138     @Test
testGetTimeUntilQuotaConsumedLocked_MaxExecution()1139     public void testGetTimeUntilQuotaConsumedLocked_MaxExecution() {
1140         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1141         // Overlap boundary.
1142         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1143                 createTimingSession(
1144                         now - (24 * HOUR_IN_MILLIS + 8 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5));
1145 
1146         setStandbyBucket(WORKING_INDEX);
1147         assertEquals(8 * MINUTE_IN_MILLIS,
1148                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1149         // Max time will phase out, so should use bucket limit.
1150         assertEquals(10 * MINUTE_IN_MILLIS,
1151                 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1152 
1153         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1154         // Close to boundary.
1155         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1156                 createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS),
1157                         4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5));
1158 
1159         setStandbyBucket(WORKING_INDEX);
1160         assertEquals(5 * MINUTE_IN_MILLIS,
1161                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1162         assertEquals(10 * MINUTE_IN_MILLIS,
1163                 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1164 
1165         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1166         // Far from boundary.
1167         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1168                 createTimingSession(
1169                         now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5));
1170 
1171         setStandbyBucket(WORKING_INDEX);
1172         assertEquals(3 * MINUTE_IN_MILLIS,
1173                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1174         assertEquals(3 * MINUTE_IN_MILLIS,
1175                 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1176     }
1177 
1178     /**
1179      * Test getTimeUntilQuotaConsumedLocked when the max execution time and bucket window time
1180      * remaining are equal.
1181      */
1182     @Test
testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining()1183     public void testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining() {
1184         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1185         setStandbyBucket(FREQUENT_INDEX);
1186 
1187         // Overlap boundary.
1188         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1189                 createTimingSession(
1190                         now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS),
1191                         4 * HOUR_IN_MILLIS,
1192                         5));
1193         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1194                 createTimingSession(
1195                         now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
1196 
1197         // Both max and bucket time have 8 minutes left.
1198         assertEquals(8 * MINUTE_IN_MILLIS,
1199                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1200         // Max time essentially free. Bucket time has 2 min phase out plus original 8 minute
1201         // window time.
1202         assertEquals(10 * MINUTE_IN_MILLIS,
1203                 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1204 
1205         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1206         // Overlap boundary.
1207         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1208                 createTimingSession(
1209                         now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5));
1210         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1211                 createTimingSession(
1212                         now - (20 * HOUR_IN_MILLIS),
1213                         3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS,
1214                         5));
1215         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1216                 createTimingSession(
1217                         now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
1218 
1219         // Both max and bucket time have 8 minutes left.
1220         assertEquals(8 * MINUTE_IN_MILLIS,
1221                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1222         // Max time only has one minute phase out. Bucket time has 2 minute phase out.
1223         assertEquals(9 * MINUTE_IN_MILLIS,
1224                 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1225     }
1226 
1227     @Test
testIsWithinQuotaLocked_NeverApp()1228     public void testIsWithinQuotaLocked_NeverApp() {
1229         assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
1230     }
1231 
1232     @Test
testIsWithinQuotaLocked_Charging()1233     public void testIsWithinQuotaLocked_Charging() {
1234         setCharging();
1235         assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
1236     }
1237 
1238     @Test
testIsWithinQuotaLocked_UnderDuration_UnderJobCount()1239     public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() {
1240         setDischarging();
1241         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1242         mQuotaController.saveTimingSession(0, "com.android.test",
1243                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
1244         mQuotaController.saveTimingSession(0, "com.android.test",
1245                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
1246         mQuotaController.incrementJobCount(0, "com.android.test", 5);
1247         assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
1248     }
1249 
1250     @Test
testIsWithinQuotaLocked_UnderDuration_OverJobCount()1251     public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
1252         setDischarging();
1253         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1254         final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
1255         mQuotaController.saveTimingSession(0, "com.android.test.spam",
1256                 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
1257         mQuotaController.saveTimingSession(0, "com.android.test.spam",
1258                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
1259         mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount);
1260         assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam",
1261                 WORKING_INDEX));
1262 
1263         mQuotaController.saveTimingSession(0, "com.android.test.frequent",
1264                 createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000));
1265         mQuotaController.saveTimingSession(0, "com.android.test.frequent",
1266                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500));
1267         assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent",
1268                 FREQUENT_INDEX));
1269     }
1270 
1271     @Test
testIsWithinQuotaLocked_OverDuration_UnderJobCount()1272     public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() {
1273         setDischarging();
1274         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1275         mQuotaController.saveTimingSession(0, "com.android.test",
1276                 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
1277         mQuotaController.saveTimingSession(0, "com.android.test",
1278                 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
1279         mQuotaController.saveTimingSession(0, "com.android.test",
1280                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5));
1281         mQuotaController.incrementJobCount(0, "com.android.test", 5);
1282         assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
1283     }
1284 
1285     @Test
testIsWithinQuotaLocked_OverDuration_OverJobCount()1286     public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
1287         setDischarging();
1288         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1289         final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW;
1290         mQuotaController.saveTimingSession(0, "com.android.test",
1291                 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
1292         mQuotaController.saveTimingSession(0, "com.android.test",
1293                 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
1294         mQuotaController.incrementJobCount(0, "com.android.test", jobCount);
1295         assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
1296     }
1297 
1298     @Test
testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS()1299     public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS() {
1300         setDischarging();
1301 
1302         JobStatus jobStatus = createJobStatus(
1303                 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS", 1);
1304         setStandbyBucket(ACTIVE_INDEX, jobStatus);
1305         setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
1306 
1307         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1308         mQuotaController.prepareForExecutionLocked(jobStatus);
1309         for (int i = 0; i < 20; ++i) {
1310             advanceElapsedClock(SECOND_IN_MILLIS);
1311             setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
1312             setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1313         }
1314         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1315 
1316         advanceElapsedClock(15 * SECOND_IN_MILLIS);
1317 
1318         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1319         mQuotaController.prepareForExecutionLocked(jobStatus);
1320         for (int i = 0; i < 20; ++i) {
1321             advanceElapsedClock(SECOND_IN_MILLIS);
1322             setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
1323             setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
1324         }
1325         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1326 
1327         advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS);
1328 
1329         assertEquals(2, mQuotaController.getExecutionStatsLocked(
1330                 SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInRateLimitingWindow);
1331         assertTrue(mQuotaController.isWithinQuotaLocked(jobStatus));
1332         assertTrue(jobStatus.isReady());
1333     }
1334 
1335     @Test
testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps()1336     public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps()
1337             throws Exception {
1338         setDischarging();
1339 
1340         final String unaffectedPkgName = "com.android.unaffected";
1341         final int unaffectedUid = 10987;
1342         JobInfo unaffectedJobInfo = new JobInfo.Builder(1,
1343                 new ComponentName(unaffectedPkgName, "foo"))
1344                 .build();
1345         JobStatus unaffected = createJobStatus(
1346                 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps",
1347                 unaffectedPkgName, unaffectedUid, unaffectedJobInfo);
1348         setStandbyBucket(FREQUENT_INDEX, unaffected);
1349         setProcessState(ActivityManager.PROCESS_STATE_SERVICE, unaffectedUid);
1350 
1351         final String fgChangerPkgName = "com.android.foreground.changer";
1352         final int fgChangerUid = 10234;
1353         JobInfo fgChangerJobInfo = new JobInfo.Builder(2,
1354                 new ComponentName(fgChangerPkgName, "foo"))
1355                 .build();
1356         JobStatus fgStateChanger = createJobStatus(
1357                 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps",
1358                 fgChangerPkgName, fgChangerUid, fgChangerJobInfo);
1359         setStandbyBucket(ACTIVE_INDEX, fgStateChanger);
1360         setProcessState(ActivityManager.PROCESS_STATE_BACKUP, fgChangerUid);
1361 
1362         IPackageManager packageManager = AppGlobals.getPackageManager();
1363         spyOn(packageManager);
1364         doReturn(new String[]{unaffectedPkgName})
1365                 .when(packageManager).getPackagesForUid(unaffectedUid);
1366         doReturn(new String[]{fgChangerPkgName})
1367                 .when(packageManager).getPackagesForUid(fgChangerUid);
1368 
1369         mQuotaController.maybeStartTrackingJobLocked(unaffected, null);
1370         mQuotaController.prepareForExecutionLocked(unaffected);
1371 
1372         mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null);
1373         mQuotaController.prepareForExecutionLocked(fgStateChanger);
1374         for (int i = 0; i < 20; ++i) {
1375             advanceElapsedClock(SECOND_IN_MILLIS);
1376             setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid);
1377             setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
1378         }
1379         mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
1380 
1381         advanceElapsedClock(15 * SECOND_IN_MILLIS);
1382 
1383         mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null);
1384         mQuotaController.prepareForExecutionLocked(fgStateChanger);
1385         for (int i = 0; i < 20; ++i) {
1386             advanceElapsedClock(SECOND_IN_MILLIS);
1387             setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid);
1388             setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
1389         }
1390         mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
1391 
1392         mQuotaController.maybeStopTrackingJobLocked(unaffected, null, false);
1393 
1394         assertTrue(mQuotaController.isWithinQuotaLocked(unaffected));
1395         assertTrue(unaffected.isReady());
1396         assertFalse(mQuotaController.isWithinQuotaLocked(fgStateChanger));
1397         assertFalse(fgStateChanger.isReady());
1398         assertEquals(1,
1399                 mQuotaController.getTimingSessions(SOURCE_USER_ID, unaffectedPkgName).size());
1400         assertEquals(42,
1401                 mQuotaController.getTimingSessions(SOURCE_USER_ID, fgChangerPkgName).size());
1402         for (int i = ACTIVE_INDEX; i < RARE_INDEX; ++i) {
1403             assertEquals(42, mQuotaController.getExecutionStatsLocked(
1404                     SOURCE_USER_ID, fgChangerPkgName, i).jobCountInRateLimitingWindow);
1405             assertEquals(1, mQuotaController.getExecutionStatsLocked(
1406                     SOURCE_USER_ID, unaffectedPkgName, i).jobCountInRateLimitingWindow);
1407         }
1408     }
1409 
1410     @Test
testIsWithinQuotaLocked_TimingSession()1411     public void testIsWithinQuotaLocked_TimingSession() {
1412         setDischarging();
1413         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1414         mQcConstants.MAX_SESSION_COUNT_RARE = 3;
1415         mQcConstants.MAX_SESSION_COUNT_FREQUENT = 4;
1416         mQcConstants.MAX_SESSION_COUNT_WORKING = 5;
1417         mQcConstants.MAX_SESSION_COUNT_ACTIVE = 6;
1418         mQcConstants.updateConstants();
1419 
1420         for (int i = 0; i < 7; ++i) {
1421             mQuotaController.saveTimingSession(0, "com.android.test",
1422                     createTimingSession(now - ((10 - i) * MINUTE_IN_MILLIS), 30 * SECOND_IN_MILLIS,
1423                             2));
1424             mQuotaController.incrementJobCount(0, "com.android.test", 2);
1425 
1426             assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions",
1427                     i < 2,
1428                     mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
1429             assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions",
1430                     i < 3,
1431                     mQuotaController.isWithinQuotaLocked(0, "com.android.test", FREQUENT_INDEX));
1432             assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions",
1433                     i < 4,
1434                     mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
1435             assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions",
1436                     i < 5,
1437                     mQuotaController.isWithinQuotaLocked(0, "com.android.test", ACTIVE_INDEX));
1438         }
1439     }
1440 
1441     @Test
testMaybeScheduleCleanupAlarmLocked()1442     public void testMaybeScheduleCleanupAlarmLocked() {
1443         // No sessions saved yet.
1444         mQuotaController.maybeScheduleCleanupAlarmLocked();
1445         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
1446 
1447         // Test with only one timing session saved.
1448         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1449         final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
1450         mQuotaController.saveTimingSession(0, "com.android.test",
1451                 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1));
1452         mQuotaController.maybeScheduleCleanupAlarmLocked();
1453         verify(mAlarmManager, times(1))
1454                 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
1455 
1456         // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
1457         mQuotaController.saveTimingSession(0, "com.android.test",
1458                 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1459         mQuotaController.saveTimingSession(0, "com.android.test",
1460                 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
1461         mQuotaController.maybeScheduleCleanupAlarmLocked();
1462         verify(mAlarmManager, times(1))
1463                 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
1464     }
1465 
1466     @Test
testMaybeScheduleStartAlarmLocked_Active()1467     public void testMaybeScheduleStartAlarmLocked_Active() {
1468         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1469         // because it schedules an alarm too. Prevent it from doing so.
1470         spyOn(mQuotaController);
1471         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1472 
1473         // Active window size is 10 minutes.
1474         final int standbyBucket = ACTIVE_INDEX;
1475         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
1476 
1477         // No sessions saved yet.
1478         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1479                 standbyBucket);
1480         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1481 
1482         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1483         // Test with timing sessions out of window but still under max execution limit.
1484         final long expectedAlarmTime =
1485                 (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
1486         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1487                 createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
1488         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1489                 createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
1490         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1491                 createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
1492         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1493                 standbyBucket);
1494         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1495 
1496         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1497                 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1));
1498         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1499                 standbyBucket);
1500         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1501 
1502         JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
1503         setStandbyBucket(standbyBucket, jobStatus);
1504         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1505         mQuotaController.prepareForExecutionLocked(jobStatus);
1506         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
1507         // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window
1508         // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no
1509         // longer has quota.
1510         assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
1511         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1512                 standbyBucket);
1513         verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK),
1514                 any(), any());
1515     }
1516 
1517     @Test
testMaybeScheduleStartAlarmLocked_WorkingSet()1518     public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
1519         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1520         // because it schedules an alarm too. Prevent it from doing so.
1521         spyOn(mQuotaController);
1522         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1523 
1524         // Working set window size is 2 hours.
1525         final int standbyBucket = WORKING_INDEX;
1526 
1527         // No sessions saved yet.
1528         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1529         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1530 
1531         // Test with timing sessions out of window.
1532         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1533         mQuotaController.saveTimingSession(0, "com.android.test",
1534                 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1535         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1536         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1537 
1538         // Test with timing sessions in window but still in quota.
1539         final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
1540         // Counting backwards, the quota will come back one minute before the end.
1541         final long expectedAlarmTime =
1542                 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
1543         mQuotaController.saveTimingSession(0, "com.android.test",
1544                 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1));
1545         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1546         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1547 
1548         // Add some more sessions, but still in quota.
1549         mQuotaController.saveTimingSession(0, "com.android.test",
1550                 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1551         mQuotaController.saveTimingSession(0, "com.android.test",
1552                 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1));
1553         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1554         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1555 
1556         // Test when out of quota.
1557         mQuotaController.saveTimingSession(0, "com.android.test",
1558                 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1559         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1560         verify(mAlarmManager, times(1))
1561                 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1562 
1563         // Alarm already scheduled, so make sure it's not scheduled again.
1564         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1565         verify(mAlarmManager, times(1))
1566                 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1567     }
1568 
1569     @Test
testMaybeScheduleStartAlarmLocked_Frequent()1570     public void testMaybeScheduleStartAlarmLocked_Frequent() {
1571         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1572         // because it schedules an alarm too. Prevent it from doing so.
1573         spyOn(mQuotaController);
1574         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1575 
1576         // Frequent window size is 8 hours.
1577         final int standbyBucket = FREQUENT_INDEX;
1578 
1579         // No sessions saved yet.
1580         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1581         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1582 
1583         // Test with timing sessions out of window.
1584         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1585         mQuotaController.saveTimingSession(0, "com.android.test",
1586                 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1587         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1588         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1589 
1590         // Test with timing sessions in window but still in quota.
1591         final long start = now - (6 * HOUR_IN_MILLIS);
1592         final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS;
1593         mQuotaController.saveTimingSession(0, "com.android.test",
1594                 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
1595         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1596         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1597 
1598         // Add some more sessions, but still in quota.
1599         mQuotaController.saveTimingSession(0, "com.android.test",
1600                 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1601         mQuotaController.saveTimingSession(0, "com.android.test",
1602                 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
1603         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1604         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1605 
1606         // Test when out of quota.
1607         mQuotaController.saveTimingSession(0, "com.android.test",
1608                 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1609         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1610         verify(mAlarmManager, times(1))
1611                 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1612 
1613         // Alarm already scheduled, so make sure it's not scheduled again.
1614         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1615         verify(mAlarmManager, times(1))
1616                 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1617     }
1618 
1619     @Test
testMaybeScheduleStartAlarmLocked_Rare()1620     public void testMaybeScheduleStartAlarmLocked_Rare() {
1621         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1622         // because it schedules an alarm too. Prevent it from doing so.
1623         spyOn(mQuotaController);
1624         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1625 
1626         // Rare window size is 24 hours.
1627         final int standbyBucket = RARE_INDEX;
1628 
1629         // Prevent timing session throttling from affecting the test.
1630         mQcConstants.MAX_SESSION_COUNT_RARE = 50;
1631         mQcConstants.updateConstants();
1632 
1633         // No sessions saved yet.
1634         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1635         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1636 
1637         // Test with timing sessions out of window.
1638         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1639         mQuotaController.saveTimingSession(0, "com.android.test",
1640                 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1641         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1642         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1643 
1644         // Test with timing sessions in window but still in quota.
1645         final long start = now - (6 * HOUR_IN_MILLIS);
1646         // Counting backwards, the first minute in the session is over the allowed time, so it
1647         // needs to be excluded.
1648         final long expectedAlarmTime =
1649                 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
1650                         + mQcConstants.IN_QUOTA_BUFFER_MS;
1651         mQuotaController.saveTimingSession(0, "com.android.test",
1652                 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
1653         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1654         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1655 
1656         // Add some more sessions, but still in quota.
1657         mQuotaController.saveTimingSession(0, "com.android.test",
1658                 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1659         mQuotaController.saveTimingSession(0, "com.android.test",
1660                 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
1661         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1662         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1663 
1664         // Test when out of quota.
1665         mQuotaController.saveTimingSession(0, "com.android.test",
1666                 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1));
1667         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1668         verify(mAlarmManager, times(1))
1669                 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1670 
1671         // Alarm already scheduled, so make sure it's not scheduled again.
1672         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1673         verify(mAlarmManager, times(1))
1674                 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1675     }
1676 
1677     /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
1678     @Test
testMaybeScheduleStartAlarmLocked_BucketChange()1679     public void testMaybeScheduleStartAlarmLocked_BucketChange() {
1680         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1681         // because it schedules an alarm too. Prevent it from doing so.
1682         spyOn(mQuotaController);
1683         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1684 
1685         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1686 
1687         // Affects rare bucket
1688         mQuotaController.saveTimingSession(0, "com.android.test",
1689                 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3));
1690         // Affects frequent and rare buckets
1691         mQuotaController.saveTimingSession(0, "com.android.test",
1692                 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
1693         // Affects working, frequent, and rare buckets
1694         final long outOfQuotaTime = now - HOUR_IN_MILLIS;
1695         mQuotaController.saveTimingSession(0, "com.android.test",
1696                 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10));
1697         // Affects all buckets
1698         mQuotaController.saveTimingSession(0, "com.android.test",
1699                 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3));
1700 
1701         InOrder inOrder = inOrder(mAlarmManager);
1702 
1703         // Start in ACTIVE bucket.
1704         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
1705         inOrder.verify(mAlarmManager, never())
1706                 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1707         inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
1708 
1709         // And down from there.
1710         final long expectedWorkingAlarmTime =
1711                 outOfQuotaTime + (2 * HOUR_IN_MILLIS)
1712                         + mQcConstants.IN_QUOTA_BUFFER_MS;
1713         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
1714         inOrder.verify(mAlarmManager, times(1))
1715                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1716 
1717         final long expectedFrequentAlarmTime =
1718                 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
1719                         + mQcConstants.IN_QUOTA_BUFFER_MS;
1720         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
1721         inOrder.verify(mAlarmManager, times(1))
1722                 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1723 
1724         final long expectedRareAlarmTime =
1725                 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
1726                         + mQcConstants.IN_QUOTA_BUFFER_MS;
1727         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
1728         inOrder.verify(mAlarmManager, times(1))
1729                 .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1730 
1731         // And back up again.
1732         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
1733         inOrder.verify(mAlarmManager, times(1))
1734                 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1735 
1736         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
1737         inOrder.verify(mAlarmManager, times(1))
1738                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1739 
1740         mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
1741         inOrder.verify(mAlarmManager, never())
1742                 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1743         inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
1744     }
1745 
1746     @Test
testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow()1747     public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() {
1748         // Set rate limiting period different from allowed time to confirm code sets based on
1749         // the former.
1750         mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 10 * MINUTE_IN_MILLIS;
1751         mQcConstants.RATE_LIMITING_WINDOW_MS = 5 * MINUTE_IN_MILLIS;
1752         mQcConstants.updateConstants();
1753 
1754         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1755         final int standbyBucket = WORKING_INDEX;
1756         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1757                 SOURCE_PACKAGE, standbyBucket);
1758         stats.jobCountInRateLimitingWindow =
1759                 mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW + 2;
1760 
1761         // Invalid time in the past, so the count shouldn't be used.
1762         stats.jobRateLimitExpirationTimeElapsed = now - 5 * MINUTE_IN_MILLIS / 2;
1763         mQuotaController.maybeScheduleStartAlarmLocked(
1764                 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
1765         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1766 
1767         // Valid time in the future, so the count should be used.
1768         stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2;
1769         final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
1770         mQuotaController.maybeScheduleStartAlarmLocked(
1771                 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
1772         verify(mAlarmManager, times(1))
1773                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1774     }
1775 
1776     /**
1777      * Tests that the start alarm is properly rescheduled if the earliest session that contributes
1778      * to the app being out of quota contributes less than the quota buffer time.
1779      */
1780     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues()1781     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() {
1782         // Use the default values
1783         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1784         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1785         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1786     }
1787 
1788     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize()1789     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
1790         // Make sure any new value is used correctly.
1791         mQcConstants.IN_QUOTA_BUFFER_MS *= 2;
1792         mQcConstants.updateConstants();
1793         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1794         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1795         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1796     }
1797 
1798     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime()1799     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
1800         // Make sure any new value is used correctly.
1801         mQcConstants.ALLOWED_TIME_PER_PERIOD_MS /= 2;
1802         mQcConstants.updateConstants();
1803         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1804         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1805         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1806     }
1807 
1808     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime()1809     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
1810         // Make sure any new value is used correctly.
1811         mQcConstants.MAX_EXECUTION_TIME_MS /= 2;
1812         mQcConstants.updateConstants();
1813         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1814         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1815         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1816     }
1817 
1818     @Test
testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything()1819     public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
1820         // Make sure any new value is used correctly.
1821         mQcConstants.IN_QUOTA_BUFFER_MS *= 2;
1822         mQcConstants.ALLOWED_TIME_PER_PERIOD_MS /= 2;
1823         mQcConstants.MAX_EXECUTION_TIME_MS /= 2;
1824         mQcConstants.updateConstants();
1825         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1826         mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1827         runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1828     }
1829 
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck()1830     private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() {
1831         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1832         // because it schedules an alarm too. Prevent it from doing so.
1833         spyOn(mQuotaController);
1834         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1835 
1836         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1837         // Working set window size is 2 hours.
1838         final int standbyBucket = WORKING_INDEX;
1839         final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
1840         final long remainingTimeMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
1841 
1842         // Session straddles edge of bucket window. Only the contribution should be counted towards
1843         // the quota.
1844         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1845                 createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1846                         3 * MINUTE_IN_MILLIS + contributionMs, 3));
1847         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1848                 createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2));
1849         // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1850         // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1851         final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
1852                 + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs);
1853         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1854                 standbyBucket);
1855         verify(mAlarmManager, times(1))
1856                 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1857     }
1858 
1859 
runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck()1860     private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
1861         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1862         // because it schedules an alarm too. Prevent it from doing so.
1863         spyOn(mQuotaController);
1864         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1865 
1866         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1867         // Working set window size is 2 hours.
1868         final int standbyBucket = WORKING_INDEX;
1869         final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2;
1870         final long remainingTimeMs = mQcConstants.MAX_EXECUTION_TIME_MS - contributionMs;
1871 
1872         // Session straddles edge of 24 hour window. Only the contribution should be counted towards
1873         // the quota.
1874         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1875                 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1876                         3 * MINUTE_IN_MILLIS + contributionMs, 3));
1877         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1878                 createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300));
1879         // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1880         // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1881         final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS
1882                 + 24 * HOUR_IN_MILLIS
1883                 + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs);
1884         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1885                 standbyBucket);
1886         verify(mAlarmManager, times(1))
1887                 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1888     }
1889 
1890     @Test
testConstantsUpdating_ValidValues()1891     public void testConstantsUpdating_ValidValues() {
1892         mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS;
1893         mQcConstants.IN_QUOTA_BUFFER_MS = 2 * MINUTE_IN_MILLIS;
1894         mQcConstants.WINDOW_SIZE_ACTIVE_MS = 15 * MINUTE_IN_MILLIS;
1895         mQcConstants.WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
1896         mQcConstants.WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
1897         mQcConstants.WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
1898         mQcConstants.MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
1899         mQcConstants.MAX_JOB_COUNT_ACTIVE = 5000;
1900         mQcConstants.MAX_JOB_COUNT_WORKING = 4000;
1901         mQcConstants.MAX_JOB_COUNT_FREQUENT = 3000;
1902         mQcConstants.MAX_JOB_COUNT_RARE = 2000;
1903         mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * MINUTE_IN_MILLIS;
1904         mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 500;
1905         mQcConstants.MAX_SESSION_COUNT_ACTIVE = 500;
1906         mQcConstants.MAX_SESSION_COUNT_WORKING = 400;
1907         mQcConstants.MAX_SESSION_COUNT_FREQUENT = 300;
1908         mQcConstants.MAX_SESSION_COUNT_RARE = 200;
1909         mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 50;
1910         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 10 * SECOND_IN_MILLIS;
1911 
1912         mQcConstants.updateConstants();
1913 
1914         assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1915         assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1916         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1917         assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1918         assertEquals(45 * MINUTE_IN_MILLIS,
1919                 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1920         assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
1921         assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
1922         assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
1923         assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow());
1924         assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
1925         assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
1926         assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
1927         assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
1928         assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
1929         assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
1930         assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
1931         assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
1932         assertEquals(200, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
1933         assertEquals(10 * SECOND_IN_MILLIS,
1934                 mQuotaController.getTimingSessionCoalescingDurationMs());
1935     }
1936 
1937     @Test
testConstantsUpdating_InvalidValues()1938     public void testConstantsUpdating_InvalidValues() {
1939         // Test negatives/too low.
1940         mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
1941         mQcConstants.IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
1942         mQcConstants.WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
1943         mQcConstants.WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
1944         mQcConstants.WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
1945         mQcConstants.WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
1946         mQcConstants.MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
1947         mQcConstants.MAX_JOB_COUNT_ACTIVE = -1;
1948         mQcConstants.MAX_JOB_COUNT_WORKING = 1;
1949         mQcConstants.MAX_JOB_COUNT_FREQUENT = 1;
1950         mQcConstants.MAX_JOB_COUNT_RARE = 1;
1951         mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * SECOND_IN_MILLIS;
1952         mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 0;
1953         mQcConstants.MAX_SESSION_COUNT_ACTIVE = -1;
1954         mQcConstants.MAX_SESSION_COUNT_WORKING = 0;
1955         mQcConstants.MAX_SESSION_COUNT_FREQUENT = -3;
1956         mQcConstants.MAX_SESSION_COUNT_RARE = 0;
1957         mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 0;
1958         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = -1;
1959 
1960         mQcConstants.updateConstants();
1961 
1962         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1963         assertEquals(0, mQuotaController.getInQuotaBufferMs());
1964         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1965         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1966         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1967         assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
1968         assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
1969         assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
1970         assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow());
1971         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
1972         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
1973         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
1974         assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
1975         assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow());
1976         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]);
1977         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]);
1978         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]);
1979         assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]);
1980         assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs());
1981 
1982         // Invalid configurations.
1983         // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD
1984         mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 2 * MINUTE_IN_MILLIS;
1985         mQcConstants.IN_QUOTA_BUFFER_MS = 5 * MINUTE_IN_MILLIS;
1986 
1987         mQcConstants.updateConstants();
1988 
1989         assertTrue(mQuotaController.getInQuotaBufferMs()
1990                 <= mQuotaController.getAllowedTimePerPeriodMs());
1991 
1992         // Test larger than a day. Controller should cap at one day.
1993         mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
1994         mQcConstants.IN_QUOTA_BUFFER_MS = 25 * HOUR_IN_MILLIS;
1995         mQcConstants.WINDOW_SIZE_ACTIVE_MS = 25 * HOUR_IN_MILLIS;
1996         mQcConstants.WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
1997         mQcConstants.WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
1998         mQcConstants.WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
1999         mQcConstants.MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
2000         mQcConstants.RATE_LIMITING_WINDOW_MS = 25 * HOUR_IN_MILLIS;
2001         mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 25 * HOUR_IN_MILLIS;
2002 
2003         mQcConstants.updateConstants();
2004 
2005         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
2006         assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
2007         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
2008         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
2009         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
2010         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
2011         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
2012         assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getRateLimitingWindowMs());
2013         assertEquals(15 * MINUTE_IN_MILLIS,
2014                 mQuotaController.getTimingSessionCoalescingDurationMs());
2015     }
2016 
2017     /** Tests that TimingSessions aren't saved when the device is charging. */
2018     @Test
testTimerTracking_Charging()2019     public void testTimerTracking_Charging() {
2020         setCharging();
2021 
2022         JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1);
2023         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2024 
2025         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2026 
2027         mQuotaController.prepareForExecutionLocked(jobStatus);
2028         advanceElapsedClock(5 * SECOND_IN_MILLIS);
2029         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
2030         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2031     }
2032 
2033     /** Tests that TimingSessions are saved properly when the device is discharging. */
2034     @Test
testTimerTracking_Discharging()2035     public void testTimerTracking_Discharging() {
2036         setDischarging();
2037         setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
2038 
2039         JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
2040         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2041 
2042         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2043 
2044         List<TimingSession> expected = new ArrayList<>();
2045 
2046         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
2047         mQuotaController.prepareForExecutionLocked(jobStatus);
2048         advanceElapsedClock(5 * SECOND_IN_MILLIS);
2049         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
2050         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
2051         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2052 
2053         // Test overlapping jobs.
2054         JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2);
2055         mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
2056 
2057         JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3);
2058         mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
2059 
2060         advanceElapsedClock(SECOND_IN_MILLIS);
2061 
2062         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2063         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2064         mQuotaController.prepareForExecutionLocked(jobStatus);
2065         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2066         mQuotaController.prepareForExecutionLocked(jobStatus2);
2067         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2068         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
2069         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2070         mQuotaController.prepareForExecutionLocked(jobStatus3);
2071         advanceElapsedClock(20 * SECOND_IN_MILLIS);
2072         mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
2073         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2074         mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
2075         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
2076         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2077     }
2078 
2079     /**
2080      * Tests that TimingSessions are saved properly when the device alternates between
2081      * charging and discharging.
2082      */
2083     @Test
testTimerTracking_ChargingAndDischarging()2084     public void testTimerTracking_ChargingAndDischarging() {
2085         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
2086 
2087         JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
2088         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2089         JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
2090         mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
2091         JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3);
2092         mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
2093         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2094         List<TimingSession> expected = new ArrayList<>();
2095 
2096         // A job starting while charging. Only the portion that runs during the discharging period
2097         // should be counted.
2098         setCharging();
2099 
2100         mQuotaController.prepareForExecutionLocked(jobStatus);
2101         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2102         setDischarging();
2103         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
2104         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2105         mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
2106         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
2107         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2108 
2109         advanceElapsedClock(SECOND_IN_MILLIS);
2110 
2111         // One job starts while discharging, spans a charging session, and ends after the charging
2112         // session. Only the portions during the discharging periods should be counted. This should
2113         // result in two TimingSessions. A second job starts while discharging and ends within the
2114         // charging session. Only the portion during the first discharging portion should be
2115         // counted. A third job starts and ends within the charging session. The third job
2116         // shouldn't be included in either job count.
2117         setDischarging();
2118         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2119         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2120         mQuotaController.prepareForExecutionLocked(jobStatus);
2121         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2122         mQuotaController.prepareForExecutionLocked(jobStatus2);
2123         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2124         setCharging();
2125         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
2126         mQuotaController.prepareForExecutionLocked(jobStatus3);
2127         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2128         mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
2129         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2130         mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
2131         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2132         setDischarging();
2133         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2134         advanceElapsedClock(20 * SECOND_IN_MILLIS);
2135         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
2136         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
2137         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2138 
2139         // A job starting while discharging and ending while charging. Only the portion that runs
2140         // during the discharging period should be counted.
2141         setDischarging();
2142         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2143         mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
2144         mQuotaController.prepareForExecutionLocked(jobStatus2);
2145         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2146         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
2147         setCharging();
2148         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2149         mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
2150         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2151     }
2152 
2153     /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
2154     @Test
testTimerTracking_AllBackground()2155     public void testTimerTracking_AllBackground() {
2156         setDischarging();
2157         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
2158 
2159         JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
2160         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2161 
2162         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2163 
2164         List<TimingSession> expected = new ArrayList<>();
2165 
2166         // Test single job.
2167         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
2168         mQuotaController.prepareForExecutionLocked(jobStatus);
2169         advanceElapsedClock(5 * SECOND_IN_MILLIS);
2170         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
2171         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
2172         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2173 
2174         // Test overlapping jobs.
2175         JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
2176         mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
2177 
2178         JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
2179         mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
2180 
2181         advanceElapsedClock(SECOND_IN_MILLIS);
2182 
2183         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2184         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2185         mQuotaController.prepareForExecutionLocked(jobStatus);
2186         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2187         mQuotaController.prepareForExecutionLocked(jobStatus2);
2188         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2189         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
2190         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2191         mQuotaController.prepareForExecutionLocked(jobStatus3);
2192         advanceElapsedClock(20 * SECOND_IN_MILLIS);
2193         mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
2194         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2195         mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
2196         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
2197         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2198     }
2199 
2200     /** Tests that Timers don't count foreground jobs. */
2201     @Test
testTimerTracking_AllForeground()2202     public void testTimerTracking_AllForeground() {
2203         setDischarging();
2204 
2205         JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
2206         setProcessState(ActivityManager.PROCESS_STATE_TOP);
2207         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2208 
2209         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2210 
2211         mQuotaController.prepareForExecutionLocked(jobStatus);
2212         advanceElapsedClock(5 * SECOND_IN_MILLIS);
2213         // Change to a state that should still be considered foreground.
2214         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
2215         advanceElapsedClock(5 * SECOND_IN_MILLIS);
2216         mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
2217         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2218     }
2219 
2220     /**
2221      * Tests that Timers properly track sessions when switching between foreground and background
2222      * states.
2223      */
2224     @Test
testTimerTracking_ForegroundAndBackground()2225     public void testTimerTracking_ForegroundAndBackground() {
2226         setDischarging();
2227 
2228         JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
2229         JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
2230         JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
2231         mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
2232         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
2233         mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
2234         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2235         List<TimingSession> expected = new ArrayList<>();
2236 
2237         // UID starts out inactive.
2238         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
2239         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
2240         mQuotaController.prepareForExecutionLocked(jobBg1);
2241         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2242         mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
2243         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
2244         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2245 
2246         advanceElapsedClock(SECOND_IN_MILLIS);
2247 
2248         // Bg job starts while inactive, spans an entire active session, and ends after the
2249         // active session.
2250         // App switching to foreground state then fg job starts.
2251         // App remains in foreground state after coming to foreground, so there should only be one
2252         // session.
2253         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2254         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
2255         mQuotaController.prepareForExecutionLocked(jobBg2);
2256         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2257         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
2258         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
2259         mQuotaController.prepareForExecutionLocked(jobFg3);
2260         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2261         mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
2262         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2263         mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
2264         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2265 
2266         advanceElapsedClock(SECOND_IN_MILLIS);
2267 
2268         // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
2269         // "inactive" and then bg job 2 starts. Then fg job ends.
2270         // This should result in two TimingSessions:
2271         //  * The first should have a count of 1
2272         //  * The second should have a count of 2 since it will include both jobs
2273         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2274         mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
2275         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
2276         mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
2277         setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
2278         mQuotaController.prepareForExecutionLocked(jobBg1);
2279         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2280         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
2281         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
2282         mQuotaController.prepareForExecutionLocked(jobFg3);
2283         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2284         mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
2285         advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
2286         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2287         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
2288         mQuotaController.prepareForExecutionLocked(jobBg2);
2289         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2290         mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
2291         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2292         mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
2293         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
2294         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2295     }
2296 
2297     /**
2298      * Tests that Timers don't track job counts while in the foreground.
2299      */
2300     @Test
testTimerTracking_JobCount_Foreground()2301     public void testTimerTracking_JobCount_Foreground() {
2302         setDischarging();
2303 
2304         final int standbyBucket = ACTIVE_INDEX;
2305         JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1);
2306         JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2);
2307 
2308         mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
2309         mQuotaController.maybeStartTrackingJobLocked(jobFg2, null);
2310         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2311         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
2312                 SOURCE_PACKAGE, standbyBucket);
2313         assertEquals(0, stats.jobCountInRateLimitingWindow);
2314 
2315         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
2316         mQuotaController.prepareForExecutionLocked(jobFg1);
2317         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2318         mQuotaController.prepareForExecutionLocked(jobFg2);
2319         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2320         mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
2321         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2322         mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
2323         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2324 
2325         assertEquals(0, stats.jobCountInRateLimitingWindow);
2326     }
2327 
2328     /**
2329      * Tests that Timers properly track job counts while in the background.
2330      */
2331     @Test
testTimerTracking_JobCount_Background()2332     public void testTimerTracking_JobCount_Background() {
2333         final int standbyBucket = WORKING_INDEX;
2334         JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1);
2335         JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2);
2336         mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
2337         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
2338 
2339         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
2340                 SOURCE_PACKAGE, standbyBucket);
2341         assertEquals(0, stats.jobCountInRateLimitingWindow);
2342 
2343         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
2344         mQuotaController.prepareForExecutionLocked(jobBg1);
2345         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2346         mQuotaController.prepareForExecutionLocked(jobBg2);
2347         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2348         mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
2349         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2350         mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
2351 
2352         assertEquals(2, stats.jobCountInRateLimitingWindow);
2353     }
2354 
2355     /**
2356      * Tests that Timers properly track overlapping top and background jobs.
2357      */
2358     @Test
testTimerTracking_TopAndNonTop()2359     public void testTimerTracking_TopAndNonTop() {
2360         setDischarging();
2361 
2362         JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1);
2363         JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2);
2364         JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3);
2365         JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4);
2366         mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
2367         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
2368         mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
2369         mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
2370         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2371         List<TimingSession> expected = new ArrayList<>();
2372 
2373         // UID starts out inactive.
2374         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
2375         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
2376         mQuotaController.prepareForExecutionLocked(jobBg1);
2377         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2378         mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
2379         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
2380         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2381 
2382         advanceElapsedClock(SECOND_IN_MILLIS);
2383 
2384         // Bg job starts while inactive, spans an entire active session, and ends after the
2385         // active session.
2386         // App switching to top state then fg job starts.
2387         // App remains in top state after coming to top, so there should only be one
2388         // session.
2389         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2390         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
2391         mQuotaController.prepareForExecutionLocked(jobBg2);
2392         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2393         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
2394         setProcessState(ActivityManager.PROCESS_STATE_TOP);
2395         mQuotaController.prepareForExecutionLocked(jobTop);
2396         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2397         mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
2398         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2399         mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
2400         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2401 
2402         advanceElapsedClock(SECOND_IN_MILLIS);
2403 
2404         // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
2405         // foreground_service and a new job starts. Shortly after, uid goes
2406         // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
2407         // This should result in two TimingSessions:
2408         //  * The first should have a count of 1
2409         //  * The second should have a count of 2, which accounts for the bg2 and fg, but not top
2410         //    jobs.
2411         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2412         mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
2413         mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
2414         mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
2415         setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
2416         mQuotaController.prepareForExecutionLocked(jobBg1);
2417         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2418         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
2419         setProcessState(ActivityManager.PROCESS_STATE_TOP);
2420         mQuotaController.prepareForExecutionLocked(jobTop);
2421         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2422         mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
2423         advanceElapsedClock(5 * SECOND_IN_MILLIS);
2424         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
2425         mQuotaController.prepareForExecutionLocked(jobFg1);
2426         advanceElapsedClock(5 * SECOND_IN_MILLIS);
2427         setProcessState(ActivityManager.PROCESS_STATE_TOP);
2428         advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
2429         start = JobSchedulerService.sElapsedRealtimeClock.millis();
2430         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
2431         mQuotaController.prepareForExecutionLocked(jobBg2);
2432         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2433         mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
2434         advanceElapsedClock(10 * SECOND_IN_MILLIS);
2435         mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
2436         mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
2437         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
2438         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2439     }
2440 
2441     /**
2442      * Tests that TOP jobs aren't stopped when an app runs out of quota.
2443      */
2444     @Test
testTracking_OutOfQuota_ForegroundAndBackground()2445     public void testTracking_OutOfQuota_ForegroundAndBackground() {
2446         setDischarging();
2447 
2448         JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
2449         JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
2450         trackJobs(jobBg, jobTop);
2451         setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
2452         // Now the package only has 20 seconds to run.
2453         final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
2454         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2455                 createTimingSession(
2456                         JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
2457                         10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
2458 
2459         InOrder inOrder = inOrder(mJobSchedulerService);
2460 
2461         // UID starts out inactive.
2462         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
2463         // Start the job.
2464         mQuotaController.prepareForExecutionLocked(jobBg);
2465         advanceElapsedClock(remainingTimeMs / 2);
2466         // New job starts after UID is in the foreground. Since the app is now in the foreground, it
2467         // should continue to have remainingTimeMs / 2 time remaining.
2468         setProcessState(ActivityManager.PROCESS_STATE_TOP);
2469         mQuotaController.prepareForExecutionLocked(jobTop);
2470         advanceElapsedClock(remainingTimeMs);
2471 
2472         // Wait for some extra time to allow for job processing.
2473         inOrder.verify(mJobSchedulerService,
2474                 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
2475                 .onControllerStateChanged();
2476         assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg));
2477         assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop));
2478         // Go to a background state.
2479         setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
2480         advanceElapsedClock(remainingTimeMs / 2 + 1);
2481         inOrder.verify(mJobSchedulerService,
2482                 timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
2483                 .onControllerStateChanged();
2484         // Top job should still be allowed to run.
2485         assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2486         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2487 
2488         // New jobs to run.
2489         JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
2490         JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
2491         JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
2492         setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
2493 
2494         advanceElapsedClock(20 * SECOND_IN_MILLIS);
2495         setProcessState(ActivityManager.PROCESS_STATE_TOP);
2496         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
2497                 .onControllerStateChanged();
2498         trackJobs(jobFg, jobTop);
2499         mQuotaController.prepareForExecutionLocked(jobTop);
2500         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2501         assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2502         assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2503 
2504         // App still in foreground so everything should be in quota.
2505         advanceElapsedClock(20 * SECOND_IN_MILLIS);
2506         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
2507         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2508         assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2509         assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2510 
2511         advanceElapsedClock(20 * SECOND_IN_MILLIS);
2512         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
2513         inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
2514                 .onControllerStateChanged();
2515         // App is now in background and out of quota. Fg should now change to out of quota since it
2516         // wasn't started. Top should remain in quota since it started when the app was in TOP.
2517         assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2518         assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2519         assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2520         trackJobs(jobBg2);
2521         assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2522     }
2523 
2524     /**
2525      * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
2526      * its quota.
2527      */
2528     @Test
testTracking_OutOfQuota()2529     public void testTracking_OutOfQuota() {
2530         JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
2531         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2532         setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
2533         setProcessState(ActivityManager.PROCESS_STATE_HOME);
2534         // Now the package only has two seconds to run.
2535         final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
2536         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2537                 createTimingSession(
2538                         JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
2539                         10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
2540 
2541         // Start the job.
2542         mQuotaController.prepareForExecutionLocked(jobStatus);
2543         advanceElapsedClock(remainingTimeMs);
2544 
2545         // Wait for some extra time to allow for job processing.
2546         verify(mJobSchedulerService,
2547                 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
2548                 .onControllerStateChanged();
2549         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2550         assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(),
2551                 jobStatus.getWhenStandbyDeferred());
2552     }
2553 
2554     /**
2555      * Tests that a job is properly handled when it's at the edge of its quota and the old quota is
2556      * being phased out.
2557      */
2558     @Test
testTracking_RollingQuota()2559     public void testTracking_RollingQuota() {
2560         JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
2561         mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2562         setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
2563         setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
2564         Handler handler = mQuotaController.getHandler();
2565         spyOn(handler);
2566 
2567         long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2568         final long remainingTimeMs = SECOND_IN_MILLIS;
2569         // The package only has one second to run, but this session is at the edge of the rolling
2570         // window, so as the package "reaches its quota" it will have more to keep running.
2571         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2572                 createTimingSession(now - 2 * HOUR_IN_MILLIS,
2573                         10 * SECOND_IN_MILLIS - remainingTimeMs, 1));
2574         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2575                 createTimingSession(now - HOUR_IN_MILLIS,
2576                         9 * MINUTE_IN_MILLIS + 50 * SECOND_IN_MILLIS, 1));
2577 
2578         assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
2579         // Start the job.
2580         mQuotaController.prepareForExecutionLocked(jobStatus);
2581         advanceElapsedClock(remainingTimeMs);
2582 
2583         // Wait for some extra time to allow for job processing.
2584         verify(mJobSchedulerService,
2585                 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
2586                 .onControllerStateChanged();
2587         assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2588         // The job used up the remaining quota, but in that time, the same amount of time in the
2589         // old TimingSession also fell out of the quota window, so it should still have the same
2590         // amount of remaining time left its quota.
2591         assertEquals(remainingTimeMs,
2592                 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
2593         // Handler is told to check when the quota will be consumed, not when the initial
2594         // remaining time is over.
2595         verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(10 * SECOND_IN_MILLIS));
2596         verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
2597 
2598         // After 10 seconds, the job should finally be out of quota.
2599         advanceElapsedClock(10 * SECOND_IN_MILLIS - remainingTimeMs);
2600         // Wait for some extra time to allow for job processing.
2601         verify(mJobSchedulerService,
2602                 timeout(12 * SECOND_IN_MILLIS).times(1))
2603                 .onControllerStateChanged();
2604         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2605         verify(handler, never()).sendMessageDelayed(any(), anyInt());
2606     }
2607 
2608     /**
2609      * Tests that the start alarm is properly scheduled when a job has been throttled due to the job
2610      * count rate limiting.
2611      */
2612     @Test
testStartAlarmScheduled_JobCount_RateLimitingWindow()2613     public void testStartAlarmScheduled_JobCount_RateLimitingWindow() {
2614         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
2615         // because it schedules an alarm too. Prevent it from doing so.
2616         spyOn(mQuotaController);
2617         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
2618 
2619         // Essentially disable session throttling.
2620         mQcConstants.MAX_SESSION_COUNT_WORKING =
2621                 mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
2622         mQcConstants.updateConstants();
2623 
2624         final int standbyBucket = WORKING_INDEX;
2625         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
2626 
2627         // No sessions saved yet.
2628         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
2629                 standbyBucket);
2630         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2631 
2632         // Ran jobs up to the job limit. All of them should be allowed to run.
2633         for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
2634             JobStatus job = createJobStatus("testStartAlarmScheduled_JobCount_AllowedTime", i);
2635             setStandbyBucket(WORKING_INDEX, job);
2636             mQuotaController.maybeStartTrackingJobLocked(job, null);
2637             assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2638             mQuotaController.prepareForExecutionLocked(job);
2639             advanceElapsedClock(SECOND_IN_MILLIS);
2640             mQuotaController.maybeStopTrackingJobLocked(job, null, false);
2641             advanceElapsedClock(SECOND_IN_MILLIS);
2642         }
2643         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
2644         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2645 
2646         // The app is now out of job count quota
2647         JobStatus throttledJob = createJobStatus(
2648                 "testStartAlarmScheduled_JobCount_AllowedTime", 42);
2649         setStandbyBucket(WORKING_INDEX, throttledJob);
2650         mQuotaController.maybeStartTrackingJobLocked(throttledJob, null);
2651         assertFalse(throttledJob.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2652 
2653         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
2654                 SOURCE_PACKAGE, standbyBucket);
2655         final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
2656         verify(mAlarmManager, times(1))
2657                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
2658     }
2659 
2660     /**
2661      * Tests that the start alarm is properly scheduled when a job has been throttled due to the
2662      * session count rate limiting.
2663      */
2664     @Test
testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow()2665     public void testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow() {
2666         // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
2667         // because it schedules an alarm too. Prevent it from doing so.
2668         spyOn(mQuotaController);
2669         doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
2670 
2671         // Essentially disable job count throttling.
2672         mQcConstants.MAX_JOB_COUNT_FREQUENT =
2673                 mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE;
2674         // Make sure throttling is because of COUNT_PER_RATE_LIMITING_WINDOW.
2675         mQcConstants.MAX_SESSION_COUNT_FREQUENT =
2676                 mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW + 1;
2677         mQcConstants.updateConstants();
2678 
2679         final int standbyBucket = FREQUENT_INDEX;
2680         setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
2681 
2682         // No sessions saved yet.
2683         mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
2684                 standbyBucket);
2685         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2686 
2687         // Ran jobs up to the job limit. All of them should be allowed to run.
2688         for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
2689             JobStatus job = createJobStatus(
2690                     "testStartAlarmScheduled_TimingSessionCount_AllowedTime", i);
2691             setStandbyBucket(FREQUENT_INDEX, job);
2692             mQuotaController.maybeStartTrackingJobLocked(job, null);
2693             assertTrue("Constraint not satisfied for job #" + (i + 1),
2694                     job.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2695             mQuotaController.prepareForExecutionLocked(job);
2696             advanceElapsedClock(SECOND_IN_MILLIS);
2697             mQuotaController.maybeStopTrackingJobLocked(job, null, false);
2698             advanceElapsedClock(SECOND_IN_MILLIS);
2699         }
2700         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
2701         verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
2702 
2703         // The app is now out of session count quota
2704         JobStatus throttledJob = createJobStatus(
2705                 "testStartAlarmScheduled_TimingSessionCount_AllowedTime", 42);
2706         mQuotaController.maybeStartTrackingJobLocked(throttledJob, null);
2707         assertFalse(throttledJob.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2708         assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(),
2709                 throttledJob.getWhenStandbyDeferred());
2710 
2711         ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
2712                 SOURCE_PACKAGE, standbyBucket);
2713         final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed;
2714         verify(mAlarmManager, times(1))
2715                 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
2716     }
2717 }
2718