blob: f492d13f371f4e09f902cdd2b1caaf671c48a1e1 [file] [log] [blame]
Kweku Adams4836f9d2018-11-12 17:04:17 -08001/*
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
17package com.android.server.job.controllers;
18
19import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
20import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
21import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
22import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
23import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
24import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
25import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
26import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
27import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
Kweku Adams288e73b2019-01-17 13:53:24 -080028import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
Kweku Adams4836f9d2018-11-12 17:04:17 -080029import static com.android.server.job.JobSchedulerService.RARE_INDEX;
30import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
31
32import static org.junit.Assert.assertEquals;
33import static org.junit.Assert.assertFalse;
Kweku Adams045fb5722018-12-11 14:29:10 -080034import static org.junit.Assert.assertNotEquals;
Kweku Adams4836f9d2018-11-12 17:04:17 -080035import static org.junit.Assert.assertNull;
36import static org.junit.Assert.assertTrue;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080037import static org.junit.Assert.fail;
Kweku Adams4836f9d2018-11-12 17:04:17 -080038import static org.mockito.ArgumentMatchers.any;
39import static org.mockito.ArgumentMatchers.anyInt;
40import static org.mockito.ArgumentMatchers.anyLong;
41import static org.mockito.Mockito.atLeast;
42import static org.mockito.Mockito.eq;
43import static org.mockito.Mockito.never;
44import static org.mockito.Mockito.timeout;
45import static org.mockito.Mockito.times;
46import static org.mockito.Mockito.verify;
47
Kweku Adamsd6625ff2019-01-10 12:06:21 -080048import android.app.ActivityManager;
49import android.app.ActivityManagerInternal;
Kweku Adams4836f9d2018-11-12 17:04:17 -080050import android.app.AlarmManager;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080051import android.app.AppGlobals;
52import android.app.IActivityManager;
53import android.app.IUidObserver;
Kweku Adams4836f9d2018-11-12 17:04:17 -080054import android.app.job.JobInfo;
55import android.app.usage.UsageStatsManager;
56import android.app.usage.UsageStatsManagerInternal;
57import android.content.BroadcastReceiver;
58import android.content.ComponentName;
59import android.content.Context;
60import android.content.Intent;
Kweku Adams7d6a31c2019-04-16 17:05:30 -070061import android.content.pm.IPackageManager;
Kweku Adams4836f9d2018-11-12 17:04:17 -080062import android.content.pm.PackageManagerInternal;
63import android.os.BatteryManager;
64import android.os.BatteryManagerInternal;
65import android.os.Handler;
66import android.os.Looper;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080067import android.os.RemoteException;
Kweku Adams4836f9d2018-11-12 17:04:17 -080068import android.os.SystemClock;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080069import android.util.SparseBooleanArray;
Kweku Adams4836f9d2018-11-12 17:04:17 -080070
71import androidx.test.runner.AndroidJUnit4;
72
73import com.android.server.LocalServices;
74import com.android.server.job.JobSchedulerService;
75import com.android.server.job.JobSchedulerService.Constants;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080076import com.android.server.job.JobStore;
Kweku Adams045fb5722018-12-11 14:29:10 -080077import com.android.server.job.controllers.QuotaController.ExecutionStats;
Kweku Adams4836f9d2018-11-12 17:04:17 -080078import com.android.server.job.controllers.QuotaController.TimingSession;
79
80import org.junit.After;
81import org.junit.Before;
82import org.junit.Test;
83import org.junit.runner.RunWith;
84import org.mockito.ArgumentCaptor;
85import org.mockito.InOrder;
86import org.mockito.Mock;
87import org.mockito.MockitoSession;
88import org.mockito.quality.Strictness;
89
90import java.time.Clock;
91import java.time.Duration;
92import java.time.ZoneOffset;
93import java.util.ArrayList;
94import java.util.List;
95
96@RunWith(AndroidJUnit4.class)
97public class QuotaControllerTest {
98 private static final long SECOND_IN_MILLIS = 1000L;
99 private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
100 private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
101 private static final String TAG_CLEANUP = "*job.cleanup*";
102 private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
Kweku Adams4836f9d2018-11-12 17:04:17 -0800103 private static final int CALLING_UID = 1000;
104 private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
105 private static final int SOURCE_USER_ID = 0;
106
107 private BroadcastReceiver mChargingReceiver;
108 private Constants mConstants;
109 private QuotaController mQuotaController;
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800110 private int mSourceUid;
111 private IUidObserver mUidObserver;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800112
113 private MockitoSession mMockingSession;
114 @Mock
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800115 private ActivityManagerInternal mActivityMangerInternal;
116 @Mock
Kweku Adams4836f9d2018-11-12 17:04:17 -0800117 private AlarmManager mAlarmManager;
118 @Mock
119 private Context mContext;
120 @Mock
121 private JobSchedulerService mJobSchedulerService;
122 @Mock
123 private UsageStatsManagerInternal mUsageStatsManager;
124
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800125 private JobStore mJobStore;
126
Kweku Adams4836f9d2018-11-12 17:04:17 -0800127 @Before
128 public void setUp() {
129 mMockingSession = mockitoSession()
130 .initMocks(this)
131 .strictness(Strictness.LENIENT)
132 .mockStatic(LocalServices.class)
133 .startMocking();
134 // Make sure constants turn on QuotaController.
135 mConstants = new Constants();
136 mConstants.USE_HEARTBEATS = false;
137
138 // Called in StateController constructor.
139 when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
140 when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
141 when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
142 // Called in QuotaController constructor.
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800143 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 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800150 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
151 when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800152 doReturn(mActivityMangerInternal)
153 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
Kweku Adams4836f9d2018-11-12 17:04:17 -0800154 doReturn(mock(BatteryManagerInternal.class))
155 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
156 doReturn(mUsageStatsManager)
157 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
158 // Used in JobStatus.
159 doReturn(mock(PackageManagerInternal.class))
160 .when(() -> LocalServices.getService(PackageManagerInternal.class));
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800161 // Used in QuotaController.Handler.
162 mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
163 when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800164
Kweku Adams045fb5722018-12-11 14:29:10 -0800165 // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
166 // in the past, and QuotaController sometimes floors values at 0, so if the test time
167 // causes sessions with negative timestamps, they will fail.
Kweku Adams4836f9d2018-11-12 17:04:17 -0800168 JobSchedulerService.sSystemClock =
Kweku Adams045fb5722018-12-11 14:29:10 -0800169 getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
170 24 * HOUR_IN_MILLIS);
171 JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
172 Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC),
173 24 * HOUR_IN_MILLIS);
174 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
175 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
176 24 * HOUR_IN_MILLIS);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800177
178 // Initialize real objects.
179 // Capture the listeners.
180 ArgumentCaptor<BroadcastReceiver> receiverCaptor =
181 ArgumentCaptor.forClass(BroadcastReceiver.class);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800182 ArgumentCaptor<IUidObserver> uidObserverCaptor =
183 ArgumentCaptor.forClass(IUidObserver.class);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800184 mQuotaController = new QuotaController(mJobSchedulerService);
185
186 verify(mContext).registerReceiver(receiverCaptor.capture(), any());
187 mChargingReceiver = receiverCaptor.getValue();
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800188 try {
189 verify(activityManager).registerUidObserver(
190 uidObserverCaptor.capture(),
191 eq(ActivityManager.UID_OBSERVER_PROCSTATE),
192 eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE),
193 any());
194 mUidObserver = uidObserverCaptor.getValue();
195 mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
196 } catch (RemoteException e) {
197 fail(e.getMessage());
198 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800199 }
200
201 @After
202 public void tearDown() {
203 if (mMockingSession != null) {
204 mMockingSession.finishMocking();
205 }
206 }
207
208 private Clock getAdvancedClock(Clock clock, long incrementMs) {
209 return Clock.offset(clock, Duration.ofMillis(incrementMs));
210 }
211
212 private void advanceElapsedClock(long incrementMs) {
213 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
214 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
215 }
216
217 private void setCharging() {
218 Intent intent = new Intent(BatteryManager.ACTION_CHARGING);
219 mChargingReceiver.onReceive(mContext, intent);
220 }
221
222 private void setDischarging() {
223 Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING);
224 mChargingReceiver.onReceive(mContext, intent);
225 }
226
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800227 private void setProcessState(int procState) {
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700228 setProcessState(procState, mSourceUid);
229 }
230
231 private void setProcessState(int procState, int uid) {
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800232 try {
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700233 doReturn(procState).when(mActivityMangerInternal).getUidProcessState(uid);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800234 SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
235 spyOn(foregroundUids);
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700236 mUidObserver.onUidStateChanged(uid, procState, 0);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800237 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700238 verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1))
239 .put(eq(uid), eq(true));
240 assertTrue(foregroundUids.get(uid));
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800241 } else {
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700242 verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)).delete(eq(uid));
243 assertFalse(foregroundUids.get(uid));
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800244 }
245 } catch (RemoteException e) {
246 fail("registerUidObserver threw exception: " + e.getMessage());
247 }
248 }
249
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700250 private int bucketIndexToUsageStatsBucket(int bucketIndex) {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800251 switch (bucketIndex) {
252 case ACTIVE_INDEX:
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700253 return UsageStatsManager.STANDBY_BUCKET_ACTIVE;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800254 case WORKING_INDEX:
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700255 return UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800256 case FREQUENT_INDEX:
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700257 return UsageStatsManager.STANDBY_BUCKET_FREQUENT;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800258 case RARE_INDEX:
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700259 return UsageStatsManager.STANDBY_BUCKET_RARE;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800260 default:
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700261 return UsageStatsManager.STANDBY_BUCKET_NEVER;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800262 }
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700263 }
264
265 private void setStandbyBucket(int bucketIndex) {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800266 when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700267 anyLong())).thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
Kweku Adams4836f9d2018-11-12 17:04:17 -0800268 }
269
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800270 private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800271 setStandbyBucket(bucketIndex);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800272 for (JobStatus job : jobs) {
273 job.setStandbyBucket(bucketIndex);
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700274 when(mUsageStatsManager.getAppStandbyBucket(
275 eq(job.getSourcePackageName()), eq(job.getSourceUserId()), anyLong()))
276 .thenReturn(bucketIndexToUsageStatsBucket(bucketIndex));
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800277 }
278 }
279
280 private void trackJobs(JobStatus... jobs) {
281 for (JobStatus job : jobs) {
282 mJobStore.add(job);
283 mQuotaController.maybeStartTrackingJobLocked(job, null);
284 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800285 }
286
287 private JobStatus createJobStatus(String testTag, int jobId) {
288 JobInfo jobInfo = new JobInfo.Builder(jobId,
289 new ComponentName(mContext, "TestQuotaJobService"))
290 .setMinimumLatency(Math.abs(jobId) + 1)
291 .build();
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700292 return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo);
293 }
294
295 private JobStatus createJobStatus(String testTag, String packageName, int callingUid,
296 JobInfo jobInfo) {
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800297 JobStatus js = JobStatus.createFromJobInfo(
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700298 jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800299 // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
300 js.setStandbyBucket(FREQUENT_INDEX);
301 return js;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800302 }
303
304 private TimingSession createTimingSession(long start, long duration, int count) {
305 return new TimingSession(start, start + duration, count);
306 }
307
308 @Test
309 public void testSaveTimingSession() {
310 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
311
312 List<TimingSession> expected = new ArrayList<>();
313 TimingSession one = new TimingSession(1, 10, 1);
314 TimingSession two = new TimingSession(11, 20, 2);
315 TimingSession thr = new TimingSession(21, 30, 3);
316
317 mQuotaController.saveTimingSession(0, "com.android.test", one);
318 expected.add(one);
319 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
320
321 mQuotaController.saveTimingSession(0, "com.android.test", two);
322 expected.add(two);
323 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
324
325 mQuotaController.saveTimingSession(0, "com.android.test", thr);
326 expected.add(thr);
327 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
328 }
329
330 @Test
331 public void testDeleteObsoleteSessionsLocked() {
332 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
333 TimingSession one = createTimingSession(
334 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
335 TimingSession two = createTimingSession(
336 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
337 TimingSession thr = createTimingSession(
338 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
339 // Overlaps 24 hour boundary.
340 TimingSession fou = createTimingSession(
341 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
342 // Way past the 24 hour boundary.
343 TimingSession fiv = createTimingSession(
344 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
345 List<TimingSession> expected = new ArrayList<>();
346 // Added in correct (chronological) order.
347 expected.add(fou);
348 expected.add(thr);
349 expected.add(two);
350 expected.add(one);
351 mQuotaController.saveTimingSession(0, "com.android.test", fiv);
352 mQuotaController.saveTimingSession(0, "com.android.test", fou);
353 mQuotaController.saveTimingSession(0, "com.android.test", thr);
354 mQuotaController.saveTimingSession(0, "com.android.test", two);
355 mQuotaController.saveTimingSession(0, "com.android.test", one);
356
357 mQuotaController.deleteObsoleteSessionsLocked();
358
359 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
360 }
361
362 @Test
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800363 public void testOnAppRemovedLocked() {
364 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
365 mQuotaController.saveTimingSession(0, "com.android.test.remove",
366 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
367 mQuotaController.saveTimingSession(0, "com.android.test.remove",
368 createTimingSession(
369 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
370 mQuotaController.saveTimingSession(0, "com.android.test.remove",
371 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
372 // Test that another app isn't affected.
373 TimingSession one = createTimingSession(
374 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
375 TimingSession two = createTimingSession(
376 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
377 List<TimingSession> expected = new ArrayList<>();
378 // Added in correct (chronological) order.
379 expected.add(two);
380 expected.add(one);
381 mQuotaController.saveTimingSession(0, "com.android.test.stay", two);
382 mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
383
Kweku Adams045fb5722018-12-11 14:29:10 -0800384 ExecutionStats expectedStats = new ExecutionStats();
Kweku Adams288e73b2019-01-17 13:53:24 -0800385 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800386 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
387
Kweku Adams288e73b2019-01-17 13:53:24 -0800388 final int uid = 10001;
389 mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800390 assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
391 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800392 assertEquals(expectedStats,
393 mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
394 assertNotEquals(expectedStats,
395 mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
Kweku Adams288e73b2019-01-17 13:53:24 -0800396
397 assertFalse(mQuotaController.getForegroundUids().get(uid));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800398 }
399
400 @Test
401 public void testOnUserRemovedLocked() {
402 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
403 mQuotaController.saveTimingSession(0, "com.android.test",
404 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
405 mQuotaController.saveTimingSession(0, "com.android.test",
406 createTimingSession(
407 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
408 mQuotaController.saveTimingSession(0, "com.android.test",
409 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
410 // Test that another user isn't affected.
411 TimingSession one = createTimingSession(
412 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
413 TimingSession two = createTimingSession(
414 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
415 List<TimingSession> expected = new ArrayList<>();
416 // Added in correct (chronological) order.
417 expected.add(two);
418 expected.add(one);
419 mQuotaController.saveTimingSession(10, "com.android.test", two);
420 mQuotaController.saveTimingSession(10, "com.android.test", one);
421
Kweku Adams045fb5722018-12-11 14:29:10 -0800422 ExecutionStats expectedStats = new ExecutionStats();
Kweku Adams288e73b2019-01-17 13:53:24 -0800423 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800424 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
425
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800426 mQuotaController.onUserRemovedLocked(0);
427 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
428 assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800429 assertEquals(expectedStats,
430 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
431 assertNotEquals(expectedStats,
432 mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800433 }
434
435 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -0800436 public void testUpdateExecutionStatsLocked_NoTimer() {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800437 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
438 // Added in chronological order.
439 mQuotaController.saveTimingSession(0, "com.android.test",
440 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
441 mQuotaController.saveTimingSession(0, "com.android.test",
442 createTimingSession(
443 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
444 mQuotaController.saveTimingSession(0, "com.android.test",
445 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
446 mQuotaController.saveTimingSession(0, "com.android.test",
447 createTimingSession(
448 now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1));
449 mQuotaController.saveTimingSession(0, "com.android.test",
450 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
451
Kweku Adams045fb5722018-12-11 14:29:10 -0800452 // Test an app that hasn't had any activity.
453 ExecutionStats expectedStats = new ExecutionStats();
454 ExecutionStats inputStats = new ExecutionStats();
455
456 inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
457 // Invalid time is now +24 hours since there are no sessions at all for the app.
Kweku Adams288e73b2019-01-17 13:53:24 -0800458 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800459 mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
460 assertEquals(expectedStats, inputStats);
461
462 inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
463 // Invalid time is now +18 hours since there are no sessions in the window but the earliest
464 // session is 6 hours ago.
Kweku Adams288e73b2019-01-17 13:53:24 -0800465 expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800466 expectedStats.executionTimeInWindowMs = 0;
467 expectedStats.bgJobCountInWindow = 0;
468 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
469 expectedStats.bgJobCountInMaxPeriod = 15;
470 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
471 assertEquals(expectedStats, inputStats);
472
473 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
474 // Invalid time is now since the session straddles the window cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800475 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800476 expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
477 expectedStats.bgJobCountInWindow = 3;
478 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
479 expectedStats.bgJobCountInMaxPeriod = 15;
480 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
481 assertEquals(expectedStats, inputStats);
482
483 inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
484 // Invalid time is now since the start of the session is at the very edge of the window
485 // cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800486 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800487 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
488 expectedStats.bgJobCountInWindow = 3;
489 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
490 expectedStats.bgJobCountInMaxPeriod = 15;
491 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
492 assertEquals(expectedStats, inputStats);
493
494 inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
495 // Invalid time is now +44 minutes since the earliest session in the window is now-5
496 // minutes.
Kweku Adams288e73b2019-01-17 13:53:24 -0800497 expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800498 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
499 expectedStats.bgJobCountInWindow = 3;
500 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
501 expectedStats.bgJobCountInMaxPeriod = 15;
502 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
503 assertEquals(expectedStats, inputStats);
504
505 inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
506 // Invalid time is now since the session is at the very edge of the window cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800507 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800508 expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
509 expectedStats.bgJobCountInWindow = 4;
510 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
511 expectedStats.bgJobCountInMaxPeriod = 15;
512 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
513 assertEquals(expectedStats, inputStats);
514
515 inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
516 // Invalid time is now since the start of the session is at the very edge of the window
517 // cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800518 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800519 expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
520 expectedStats.bgJobCountInWindow = 5;
521 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
522 expectedStats.bgJobCountInMaxPeriod = 15;
523 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
524 assertEquals(expectedStats, inputStats);
525
526 inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
527 // Invalid time is now since the session straddles the window cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800528 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800529 expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
530 expectedStats.bgJobCountInWindow = 10;
531 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
532 expectedStats.bgJobCountInMaxPeriod = 15;
533 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
534 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
535 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
536 assertEquals(expectedStats, inputStats);
537
538 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
539 // Invalid time is now +59 minutes since the earliest session in the window is now-121
540 // minutes.
Kweku Adams288e73b2019-01-17 13:53:24 -0800541 expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800542 expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
543 expectedStats.bgJobCountInWindow = 10;
544 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
545 expectedStats.bgJobCountInMaxPeriod = 15;
546 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
547 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
548 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
549 assertEquals(expectedStats, inputStats);
550
551 inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
552 // Invalid time is now since the start of the session is at the very edge of the window
553 // cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800554 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800555 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
556 expectedStats.bgJobCountInWindow = 15;
557 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
558 expectedStats.bgJobCountInMaxPeriod = 15;
559 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
560 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
561 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
562 assertEquals(expectedStats, inputStats);
563
Kweku Adams288e73b2019-01-17 13:53:24 -0800564 // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period.
Kweku Adams045fb5722018-12-11 14:29:10 -0800565 mQuotaController.getTimingSessions(0, "com.android.test")
566 .add(0,
567 createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
568 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
569 // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
570 // before the end of the max period cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800571 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800572 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
573 expectedStats.bgJobCountInWindow = 15;
574 expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
575 expectedStats.bgJobCountInMaxPeriod = 18;
576 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
577 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
578 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
579 assertEquals(expectedStats, inputStats);
580
581 mQuotaController.getTimingSessions(0, "com.android.test")
582 .add(0,
583 createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
584 2 * MINUTE_IN_MILLIS, 2));
585 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
586 // Invalid time is now since the earlist session straddles the max period cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800587 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800588 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
589 expectedStats.bgJobCountInWindow = 15;
590 expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
591 expectedStats.bgJobCountInMaxPeriod = 20;
592 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
593 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
594 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
595 assertEquals(expectedStats, inputStats);
596 }
597
598 /**
599 * Tests that getExecutionStatsLocked returns the correct stats.
600 */
601 @Test
602 public void testGetExecutionStatsLocked_Values() {
603 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
604 mQuotaController.saveTimingSession(0, "com.android.test",
605 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
606 mQuotaController.saveTimingSession(0, "com.android.test",
607 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
608 mQuotaController.saveTimingSession(0, "com.android.test",
609 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
610 mQuotaController.saveTimingSession(0, "com.android.test",
611 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
612
613 ExecutionStats expectedStats = new ExecutionStats();
614
615 // Active
616 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800617 expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800618 expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
619 expectedStats.bgJobCountInWindow = 5;
620 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
621 expectedStats.bgJobCountInMaxPeriod = 20;
622 assertEquals(expectedStats,
623 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
624
625 // Working
626 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800627 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800628 expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
629 expectedStats.bgJobCountInWindow = 10;
630 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
631 expectedStats.bgJobCountInMaxPeriod = 20;
632 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
633 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
634 assertEquals(expectedStats,
635 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
636
637 // Frequent
638 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800639 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800640 expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
641 expectedStats.bgJobCountInWindow = 15;
642 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
643 expectedStats.bgJobCountInMaxPeriod = 20;
644 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
645 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
646 assertEquals(expectedStats,
647 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
648
649 // Rare
650 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800651 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800652 expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
653 expectedStats.bgJobCountInWindow = 20;
654 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
655 expectedStats.bgJobCountInMaxPeriod = 20;
656 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
657 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
658 assertEquals(expectedStats,
659 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
660 }
661
662 /**
663 * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
664 */
665 @Test
666 public void testGetExecutionStatsLocked_Caching() {
667 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
668 mQuotaController.saveTimingSession(0, "com.android.test",
669 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
670 mQuotaController.saveTimingSession(0, "com.android.test",
671 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
672 mQuotaController.saveTimingSession(0, "com.android.test",
673 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
674 mQuotaController.saveTimingSession(0, "com.android.test",
675 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
676 final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0,
677 "com.android.test", ACTIVE_INDEX);
678 final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0,
679 "com.android.test", WORKING_INDEX);
680 final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
681 "com.android.test", FREQUENT_INDEX);
682 final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0,
683 "com.android.test", RARE_INDEX);
684
685 // Advance clock so that the working stats shouldn't be the same.
686 advanceElapsedClock(MINUTE_IN_MILLIS);
687 // Change frequent bucket size so that the stats need to be recalculated.
688 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS;
689 mQuotaController.onConstantsUpdatedLocked();
690
691 ExecutionStats expectedStats = new ExecutionStats();
692 expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800693 expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800694 expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
695 expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
696 expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
697 expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
698 expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
699 final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
700 "com.android.test", ACTIVE_INDEX);
701 // Stats for the same bucket should use the same object.
702 assertTrue(originalStatsActive == newStatsActive);
703 assertEquals(expectedStats, newStatsActive);
704
705 expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800706 expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800707 expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
708 expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
709 expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
710 final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
711 "com.android.test", WORKING_INDEX);
712 assertTrue(originalStatsWorking == newStatsWorking);
713 assertNotEquals(expectedStats, newStatsWorking);
714
715 expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800716 expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800717 expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
718 expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
719 expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
720 final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
721 "com.android.test", FREQUENT_INDEX);
722 assertTrue(originalStatsFrequent == newStatsFrequent);
723 assertNotEquals(expectedStats, newStatsFrequent);
724
725 expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800726 expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800727 expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
728 expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
729 expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
730 final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
731 "com.android.test", RARE_INDEX);
732 assertTrue(originalStatsRare == newStatsRare);
733 assertEquals(expectedStats, newStatsRare);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800734 }
735
Kweku Adamse3e16402019-03-26 15:48:51 -0700736 /**
737 * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
738 * window.
739 */
740 @Test
741 public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() {
742 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
743 // Close to RARE boundary.
744 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
745 createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
746 30 * SECOND_IN_MILLIS, 5));
747 // Far away from FREQUENT boundary.
748 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
749 createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
750 // Overlap WORKING_SET boundary.
751 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
752 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
753 3 * MINUTE_IN_MILLIS, 5));
754 // Close to ACTIVE boundary.
755 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
756 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
757
758 setStandbyBucket(RARE_INDEX);
759 assertEquals(30 * SECOND_IN_MILLIS,
760 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
761 assertEquals(MINUTE_IN_MILLIS,
762 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
763
764 setStandbyBucket(FREQUENT_INDEX);
765 assertEquals(MINUTE_IN_MILLIS,
766 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
767 assertEquals(MINUTE_IN_MILLIS,
768 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
769
770 setStandbyBucket(WORKING_INDEX);
771 assertEquals(5 * MINUTE_IN_MILLIS,
772 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
773 assertEquals(7 * MINUTE_IN_MILLIS,
774 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
775
776 // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
777 // max execution time.
778 setStandbyBucket(ACTIVE_INDEX);
779 assertEquals(7 * MINUTE_IN_MILLIS,
780 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
781 assertEquals(mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
782 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
783 }
784
785 /**
786 * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit.
787 */
788 @Test
789 public void testGetTimeUntilQuotaConsumedLocked_MaxExecution() {
790 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
791 // Overlap boundary.
792 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
793 createTimingSession(
794 now - (24 * HOUR_IN_MILLIS + 8 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5));
795
796 setStandbyBucket(WORKING_INDEX);
797 assertEquals(8 * MINUTE_IN_MILLIS,
798 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
799 // Max time will phase out, so should use bucket limit.
800 assertEquals(10 * MINUTE_IN_MILLIS,
801 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
802
803 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
804 // Close to boundary.
805 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
806 createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS),
807 4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5));
808
809 setStandbyBucket(WORKING_INDEX);
810 assertEquals(5 * MINUTE_IN_MILLIS,
811 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
812 assertEquals(10 * MINUTE_IN_MILLIS,
813 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
814
815 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
816 // Far from boundary.
817 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
818 createTimingSession(
819 now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5));
820
821 setStandbyBucket(WORKING_INDEX);
822 assertEquals(3 * MINUTE_IN_MILLIS,
823 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
824 assertEquals(3 * MINUTE_IN_MILLIS,
825 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
826 }
827
828 /**
829 * Test getTimeUntilQuotaConsumedLocked when the max execution time and bucket window time
830 * remaining are equal.
831 */
832 @Test
833 public void testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining() {
834 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
835 setStandbyBucket(FREQUENT_INDEX);
836
837 // Overlap boundary.
838 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
839 createTimingSession(
840 now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS),
841 4 * HOUR_IN_MILLIS,
842 5));
843 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
844 createTimingSession(
845 now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
846
847 // Both max and bucket time have 8 minutes left.
848 assertEquals(8 * MINUTE_IN_MILLIS,
849 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
850 // Max time essentially free. Bucket time has 2 min phase out plus original 8 minute
851 // window time.
852 assertEquals(10 * MINUTE_IN_MILLIS,
853 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
854
855 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
856 // Overlap boundary.
857 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
858 createTimingSession(
859 now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5));
860 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
861 createTimingSession(
862 now - (20 * HOUR_IN_MILLIS),
863 3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS,
864 5));
865 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
866 createTimingSession(
867 now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
868
869 // Both max and bucket time have 8 minutes left.
870 assertEquals(8 * MINUTE_IN_MILLIS,
871 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
872 // Max time only has one minute phase out. Bucket time has 2 minute phase out.
873 assertEquals(9 * MINUTE_IN_MILLIS,
874 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
875 }
876
Kweku Adams4836f9d2018-11-12 17:04:17 -0800877 @Test
Kweku Adams288e73b2019-01-17 13:53:24 -0800878 public void testIsWithinQuotaLocked_NeverApp() {
879 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
880 }
881
882 @Test
883 public void testIsWithinQuotaLocked_Charging() {
884 setCharging();
885 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
886 }
887
888 @Test
889 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() {
890 setDischarging();
891 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
892 mQuotaController.saveTimingSession(0, "com.android.test",
893 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
894 mQuotaController.saveTimingSession(0, "com.android.test",
895 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
896 mQuotaController.incrementJobCount(0, "com.android.test", 5);
897 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
898 }
899
900 @Test
901 public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
902 setDischarging();
903 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
904 final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
905 mQuotaController.saveTimingSession(0, "com.android.test.spam",
906 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
907 mQuotaController.saveTimingSession(0, "com.android.test.spam",
908 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
909 mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount);
910 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam",
911 WORKING_INDEX));
912
913 mQuotaController.saveTimingSession(0, "com.android.test.frequent",
914 createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000));
915 mQuotaController.saveTimingSession(0, "com.android.test.frequent",
916 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500));
917 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent",
918 FREQUENT_INDEX));
919 }
920
921 @Test
922 public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() {
923 setDischarging();
924 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
925 mQuotaController.saveTimingSession(0, "com.android.test",
926 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
927 mQuotaController.saveTimingSession(0, "com.android.test",
928 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
929 mQuotaController.saveTimingSession(0, "com.android.test",
930 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5));
931 mQuotaController.incrementJobCount(0, "com.android.test", 5);
932 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
933 }
934
935 @Test
936 public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
937 setDischarging();
938 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
939 final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
940 mQuotaController.saveTimingSession(0, "com.android.test",
941 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
942 mQuotaController.saveTimingSession(0, "com.android.test",
943 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
944 mQuotaController.incrementJobCount(0, "com.android.test", jobCount);
945 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
946 }
947
948 @Test
Kweku Adams7d6a31c2019-04-16 17:05:30 -0700949 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS() {
950 setDischarging();
951
952 JobStatus jobStatus = createJobStatus(
953 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS", 1);
954 setStandbyBucket(ACTIVE_INDEX, jobStatus);
955 setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
956
957 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
958 mQuotaController.prepareForExecutionLocked(jobStatus);
959 for (int i = 0; i < 20; ++i) {
960 advanceElapsedClock(SECOND_IN_MILLIS);
961 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
962 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
963 }
964 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
965
966 advanceElapsedClock(15 * SECOND_IN_MILLIS);
967
968 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
969 mQuotaController.prepareForExecutionLocked(jobStatus);
970 for (int i = 0; i < 20; ++i) {
971 advanceElapsedClock(SECOND_IN_MILLIS);
972 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
973 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
974 }
975 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
976
977 advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS);
978
979 assertEquals(2, mQuotaController.getExecutionStatsLocked(
980 SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInAllowedTime);
981 assertTrue(mQuotaController.isWithinQuotaLocked(jobStatus));
982 }
983
984 @Test
985 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps()
986 throws Exception {
987 setDischarging();
988
989 final String unaffectedPkgName = "com.android.unaffected";
990 final int unaffectedUid = 10987;
991 JobInfo unaffectedJobInfo = new JobInfo.Builder(1,
992 new ComponentName(unaffectedPkgName, "foo"))
993 .build();
994 JobStatus unaffected = createJobStatus(
995 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps",
996 unaffectedPkgName, unaffectedUid, unaffectedJobInfo);
997 setStandbyBucket(FREQUENT_INDEX, unaffected);
998 setProcessState(ActivityManager.PROCESS_STATE_SERVICE, unaffectedUid);
999
1000 final String fgChangerPkgName = "com.android.foreground.changer";
1001 final int fgChangerUid = 10234;
1002 JobInfo fgChangerJobInfo = new JobInfo.Builder(2,
1003 new ComponentName(fgChangerPkgName, "foo"))
1004 .build();
1005 JobStatus fgStateChanger = createJobStatus(
1006 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps",
1007 fgChangerPkgName, fgChangerUid, fgChangerJobInfo);
1008 setStandbyBucket(ACTIVE_INDEX, fgStateChanger);
1009 setProcessState(ActivityManager.PROCESS_STATE_BACKUP, fgChangerUid);
1010
1011 IPackageManager packageManager = AppGlobals.getPackageManager();
1012 spyOn(packageManager);
1013 doReturn(new String[]{unaffectedPkgName})
1014 .when(packageManager).getPackagesForUid(unaffectedUid);
1015 doReturn(new String[]{fgChangerPkgName})
1016 .when(packageManager).getPackagesForUid(fgChangerUid);
1017
1018 mQuotaController.maybeStartTrackingJobLocked(unaffected, null);
1019 mQuotaController.prepareForExecutionLocked(unaffected);
1020
1021 mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null);
1022 mQuotaController.prepareForExecutionLocked(fgStateChanger);
1023 for (int i = 0; i < 20; ++i) {
1024 advanceElapsedClock(SECOND_IN_MILLIS);
1025 setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid);
1026 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
1027 }
1028 mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
1029
1030 advanceElapsedClock(15 * SECOND_IN_MILLIS);
1031
1032 mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null);
1033 mQuotaController.prepareForExecutionLocked(fgStateChanger);
1034 for (int i = 0; i < 20; ++i) {
1035 advanceElapsedClock(SECOND_IN_MILLIS);
1036 setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid);
1037 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
1038 }
1039 mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
1040
1041 mQuotaController.maybeStopTrackingJobLocked(unaffected, null, false);
1042
1043 assertTrue(mQuotaController.isWithinQuotaLocked(unaffected));
1044 assertFalse(mQuotaController.isWithinQuotaLocked(fgStateChanger));
1045 assertEquals(1,
1046 mQuotaController.getTimingSessions(SOURCE_USER_ID, unaffectedPkgName).size());
1047 assertEquals(42,
1048 mQuotaController.getTimingSessions(SOURCE_USER_ID, fgChangerPkgName).size());
1049 for (int i = ACTIVE_INDEX; i < RARE_INDEX; ++i) {
1050 assertEquals(42, mQuotaController.getExecutionStatsLocked(
1051 SOURCE_USER_ID, fgChangerPkgName, i).jobCountInAllowedTime);
1052 assertEquals(1, mQuotaController.getExecutionStatsLocked(
1053 SOURCE_USER_ID, unaffectedPkgName, i).jobCountInAllowedTime);
1054 }
1055 }
1056
1057 @Test
Kweku Adams4836f9d2018-11-12 17:04:17 -08001058 public void testMaybeScheduleCleanupAlarmLocked() {
1059 // No sessions saved yet.
1060 mQuotaController.maybeScheduleCleanupAlarmLocked();
1061 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
1062
1063 // Test with only one timing session saved.
1064 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1065 final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
1066 mQuotaController.saveTimingSession(0, "com.android.test",
1067 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1));
1068 mQuotaController.maybeScheduleCleanupAlarmLocked();
1069 verify(mAlarmManager, times(1))
1070 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
1071
1072 // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
1073 mQuotaController.saveTimingSession(0, "com.android.test",
1074 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1075 mQuotaController.saveTimingSession(0, "com.android.test",
1076 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
1077 mQuotaController.maybeScheduleCleanupAlarmLocked();
1078 verify(mAlarmManager, times(1))
1079 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
1080 }
1081
1082 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -08001083 public void testMaybeScheduleStartAlarmLocked_Active() {
1084 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1085 // because it schedules an alarm too. Prevent it from doing so.
1086 spyOn(mQuotaController);
1087 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1088
1089 // Active window size is 10 minutes.
1090 final int standbyBucket = ACTIVE_INDEX;
Kweku Adams288e73b2019-01-17 13:53:24 -08001091 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
Kweku Adams045fb5722018-12-11 14:29:10 -08001092
1093 // No sessions saved yet.
1094 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1095 standbyBucket);
1096 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1097
1098 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1099 // Test with timing sessions out of window but still under max execution limit.
1100 final long expectedAlarmTime =
1101 (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS
1102 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
1103 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1104 createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
1105 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1106 createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
1107 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1108 createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
1109 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1110 standbyBucket);
1111 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1112
1113 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1114 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1));
1115 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1116 standbyBucket);
1117 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1118
1119 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001120 setStandbyBucket(standbyBucket, jobStatus);
Kweku Adams045fb5722018-12-11 14:29:10 -08001121 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1122 mQuotaController.prepareForExecutionLocked(jobStatus);
1123 advanceElapsedClock(5 * MINUTE_IN_MILLIS);
1124 // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window
1125 // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no
1126 // longer has quota.
1127 assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
1128 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1129 standbyBucket);
1130 verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK),
1131 any(), any());
1132 }
1133
1134 @Test
Kweku Adams4836f9d2018-11-12 17:04:17 -08001135 public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
1136 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1137 // because it schedules an alarm too. Prevent it from doing so.
1138 spyOn(mQuotaController);
1139 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1140
1141 // Working set window size is 2 hours.
1142 final int standbyBucket = WORKING_INDEX;
1143
1144 // No sessions saved yet.
1145 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1146 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1147
1148 // Test with timing sessions out of window.
1149 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1150 mQuotaController.saveTimingSession(0, "com.android.test",
1151 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1152 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1153 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1154
1155 // Test with timing sessions in window but still in quota.
1156 final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
1157 // Counting backwards, the quota will come back one minute before the end.
1158 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001159 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS
1160 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001161 mQuotaController.saveTimingSession(0, "com.android.test",
1162 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1));
1163 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1164 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1165
1166 // Add some more sessions, but still in quota.
1167 mQuotaController.saveTimingSession(0, "com.android.test",
1168 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1169 mQuotaController.saveTimingSession(0, "com.android.test",
1170 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1));
1171 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1172 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1173
1174 // Test when out of quota.
1175 mQuotaController.saveTimingSession(0, "com.android.test",
1176 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1177 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1178 verify(mAlarmManager, times(1))
1179 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1180
1181 // Alarm already scheduled, so make sure it's not scheduled again.
1182 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1183 verify(mAlarmManager, times(1))
1184 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1185 }
1186
1187 @Test
1188 public void testMaybeScheduleStartAlarmLocked_Frequent() {
1189 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1190 // because it schedules an alarm too. Prevent it from doing so.
1191 spyOn(mQuotaController);
1192 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1193
1194 // Frequent window size is 8 hours.
1195 final int standbyBucket = FREQUENT_INDEX;
1196
1197 // No sessions saved yet.
1198 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1199 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1200
1201 // Test with timing sessions out of window.
1202 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1203 mQuotaController.saveTimingSession(0, "com.android.test",
1204 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1205 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1206 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1207
1208 // Test with timing sessions in window but still in quota.
1209 final long start = now - (6 * HOUR_IN_MILLIS);
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001210 final long expectedAlarmTime =
1211 start + 8 * HOUR_IN_MILLIS + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001212 mQuotaController.saveTimingSession(0, "com.android.test",
1213 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
1214 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1215 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1216
1217 // Add some more sessions, but still in quota.
1218 mQuotaController.saveTimingSession(0, "com.android.test",
1219 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1220 mQuotaController.saveTimingSession(0, "com.android.test",
1221 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
1222 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1223 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1224
1225 // Test when out of quota.
1226 mQuotaController.saveTimingSession(0, "com.android.test",
1227 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1228 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1229 verify(mAlarmManager, times(1))
1230 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1231
1232 // Alarm already scheduled, so make sure it's not scheduled again.
1233 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1234 verify(mAlarmManager, times(1))
1235 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1236 }
1237
1238 @Test
1239 public void testMaybeScheduleStartAlarmLocked_Rare() {
1240 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1241 // because it schedules an alarm too. Prevent it from doing so.
1242 spyOn(mQuotaController);
1243 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1244
1245 // Rare window size is 24 hours.
1246 final int standbyBucket = RARE_INDEX;
1247
1248 // No sessions saved yet.
1249 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1250 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1251
1252 // Test with timing sessions out of window.
1253 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1254 mQuotaController.saveTimingSession(0, "com.android.test",
1255 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1256 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1257 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1258
1259 // Test with timing sessions in window but still in quota.
1260 final long start = now - (6 * HOUR_IN_MILLIS);
1261 // Counting backwards, the first minute in the session is over the allowed time, so it
1262 // needs to be excluded.
1263 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001264 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
1265 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001266 mQuotaController.saveTimingSession(0, "com.android.test",
1267 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
1268 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1269 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1270
1271 // Add some more sessions, but still in quota.
1272 mQuotaController.saveTimingSession(0, "com.android.test",
1273 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1274 mQuotaController.saveTimingSession(0, "com.android.test",
1275 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
1276 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1277 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1278
1279 // Test when out of quota.
1280 mQuotaController.saveTimingSession(0, "com.android.test",
1281 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1));
1282 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1283 verify(mAlarmManager, times(1))
1284 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1285
1286 // Alarm already scheduled, so make sure it's not scheduled again.
1287 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1288 verify(mAlarmManager, times(1))
1289 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1290 }
1291
1292 /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
1293 @Test
1294 public void testMaybeScheduleStartAlarmLocked_BucketChange() {
1295 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1296 // because it schedules an alarm too. Prevent it from doing so.
1297 spyOn(mQuotaController);
1298 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1299
1300 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1301
1302 // Affects rare bucket
1303 mQuotaController.saveTimingSession(0, "com.android.test",
1304 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3));
1305 // Affects frequent and rare buckets
1306 mQuotaController.saveTimingSession(0, "com.android.test",
1307 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
1308 // Affects working, frequent, and rare buckets
1309 final long outOfQuotaTime = now - HOUR_IN_MILLIS;
1310 mQuotaController.saveTimingSession(0, "com.android.test",
1311 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10));
1312 // Affects all buckets
1313 mQuotaController.saveTimingSession(0, "com.android.test",
1314 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3));
1315
1316 InOrder inOrder = inOrder(mAlarmManager);
1317
1318 // Start in ACTIVE bucket.
1319 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
Kweku Adamsbffea5a2018-12-13 22:13:28 -08001320 inOrder.verify(mAlarmManager, never())
1321 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001322 inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
1323
1324 // And down from there.
1325 final long expectedWorkingAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001326 outOfQuotaTime + (2 * HOUR_IN_MILLIS)
1327 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001328 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
1329 inOrder.verify(mAlarmManager, times(1))
1330 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1331
1332 final long expectedFrequentAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001333 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
1334 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001335 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
1336 inOrder.verify(mAlarmManager, times(1))
1337 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1338
1339 final long expectedRareAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001340 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
1341 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001342 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
1343 inOrder.verify(mAlarmManager, times(1))
1344 .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1345
1346 // And back up again.
1347 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
1348 inOrder.verify(mAlarmManager, times(1))
1349 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1350
1351 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
1352 inOrder.verify(mAlarmManager, times(1))
1353 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1354
1355 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
Kweku Adams288e73b2019-01-17 13:53:24 -08001356 inOrder.verify(mAlarmManager, never())
1357 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001358 inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
1359 }
1360
Kweku Adams288e73b2019-01-17 13:53:24 -08001361 @Test
1362 public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
1363 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1364 final int standbyBucket = WORKING_INDEX;
1365 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1366 SOURCE_PACKAGE, standbyBucket);
1367 stats.jobCountInAllowedTime =
1368 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
1369
1370 // Invalid time in the past, so the count shouldn't be used.
1371 stats.jobCountExpirationTimeElapsed =
1372 now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
1373 mQuotaController.maybeScheduleStartAlarmLocked(
1374 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
1375 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1376
1377 // Invalid time in the future, so the count should be used.
1378 stats.jobCountExpirationTimeElapsed =
1379 now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
1380 final long expectedWorkingAlarmTime =
1381 stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
1382 mQuotaController.maybeScheduleStartAlarmLocked(
1383 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
1384 verify(mAlarmManager, times(1))
1385 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1386 }
Kweku Adams045fb5722018-12-11 14:29:10 -08001387
1388 /**
1389 * Tests that the start alarm is properly rescheduled if the earliest session that contributes
1390 * to the app being out of quota contributes less than the quota buffer time.
1391 */
1392 @Test
1393 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() {
1394 // Use the default values
1395 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1396 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1397 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1398 }
1399
1400 @Test
1401 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
1402 // Make sure any new value is used correctly.
1403 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
1404 mQuotaController.onConstantsUpdatedLocked();
1405 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1406 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1407 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1408 }
1409
1410 @Test
1411 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
1412 // Make sure any new value is used correctly.
1413 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
1414 mQuotaController.onConstantsUpdatedLocked();
1415 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1416 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1417 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1418 }
1419
1420 @Test
1421 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
1422 // Make sure any new value is used correctly.
1423 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
1424 mQuotaController.onConstantsUpdatedLocked();
1425 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1426 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1427 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1428 }
1429
1430 @Test
1431 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
1432 // Make sure any new value is used correctly.
1433 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
1434 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
1435 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
1436 mQuotaController.onConstantsUpdatedLocked();
1437 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1438 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1439 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1440 }
1441
1442 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() {
1443 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1444 // because it schedules an alarm too. Prevent it from doing so.
1445 spyOn(mQuotaController);
1446 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1447
1448 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1449 // Working set window size is 2 hours.
1450 final int standbyBucket = WORKING_INDEX;
1451 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1452 final long remainingTimeMs =
1453 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
1454
1455 // Session straddles edge of bucket window. Only the contribution should be counted towards
1456 // the quota.
1457 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1458 createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1459 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1460 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1461 createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2));
1462 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1463 // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1464 final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
1465 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1466 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1467 standbyBucket);
1468 verify(mAlarmManager, times(1))
1469 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1470 }
1471
1472
1473 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
1474 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1475 // because it schedules an alarm too. Prevent it from doing so.
1476 spyOn(mQuotaController);
1477 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1478
1479 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1480 // Working set window size is 2 hours.
1481 final int standbyBucket = WORKING_INDEX;
1482 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1483 final long remainingTimeMs =
1484 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - contributionMs;
1485
1486 // Session straddles edge of 24 hour window. Only the contribution should be counted towards
1487 // the quota.
1488 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1489 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1490 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1491 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1492 createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300));
1493 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1494 // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1495 final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS
1496 //+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS
1497 + 24 * HOUR_IN_MILLIS
1498 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1499 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1500 standbyBucket);
1501 verify(mAlarmManager, times(1))
1502 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1503 }
1504
Kweku Adams4836f9d2018-11-12 17:04:17 -08001505 /** Tests that QuotaController doesn't throttle if throttling is turned off. */
1506 @Test
1507 public void testThrottleToggling() throws Exception {
1508 setDischarging();
1509 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1510 createTimingSession(
1511 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1512 10 * MINUTE_IN_MILLIS, 4));
1513 JobStatus jobStatus = createJobStatus("testThrottleToggling", 1);
1514 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
1515 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1516 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1517
1518 mConstants.USE_HEARTBEATS = true;
1519 mQuotaController.onConstantsUpdatedLocked();
1520 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1521 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1522
1523 mConstants.USE_HEARTBEATS = false;
1524 mQuotaController.onConstantsUpdatedLocked();
1525 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1526 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1527 }
1528
1529 @Test
1530 public void testConstantsUpdating_ValidValues() {
1531 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS;
1532 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 2 * MINUTE_IN_MILLIS;
1533 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 15 * MINUTE_IN_MILLIS;
1534 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
1535 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
1536 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001537 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -08001538 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000;
1539 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000;
1540 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000;
1541 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000;
1542 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001543
1544 mQuotaController.onConstantsUpdatedLocked();
1545
1546 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1547 assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1548 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1549 assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1550 assertEquals(45 * MINUTE_IN_MILLIS,
1551 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1552 assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001553 assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams288e73b2019-01-17 13:53:24 -08001554 assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
1555 assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
1556 assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
1557 assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
1558 assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001559 }
1560
1561 @Test
1562 public void testConstantsUpdating_InvalidValues() {
Kweku Adams288e73b2019-01-17 13:53:24 -08001563 // Test negatives/too low.
Kweku Adams4836f9d2018-11-12 17:04:17 -08001564 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
1565 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
1566 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
1567 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
1568 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
1569 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001570 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -08001571 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1;
1572 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1;
1573 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1;
1574 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1;
1575 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001576
1577 mQuotaController.onConstantsUpdatedLocked();
1578
1579 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1580 assertEquals(0, mQuotaController.getInQuotaBufferMs());
1581 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1582 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1583 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1584 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001585 assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams288e73b2019-01-17 13:53:24 -08001586 assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
1587 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
1588 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
1589 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
1590 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001591
1592 // Test larger than a day. Controller should cap at one day.
1593 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
1594 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 25 * HOUR_IN_MILLIS;
1595 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 25 * HOUR_IN_MILLIS;
1596 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
1597 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
1598 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001599 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001600
1601 mQuotaController.onConstantsUpdatedLocked();
1602
1603 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1604 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1605 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1606 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1607 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1608 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001609 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001610 }
1611
1612 /** Tests that TimingSessions aren't saved when the device is charging. */
1613 @Test
1614 public void testTimerTracking_Charging() {
1615 setCharging();
1616
1617 JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1);
1618 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1619
1620 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1621
1622 mQuotaController.prepareForExecutionLocked(jobStatus);
1623 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1624 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1625 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1626 }
1627
1628 /** Tests that TimingSessions are saved properly when the device is discharging. */
1629 @Test
1630 public void testTimerTracking_Discharging() {
1631 setDischarging();
Kweku Adams288e73b2019-01-17 13:53:24 -08001632 setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001633
1634 JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
1635 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1636
1637 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1638
1639 List<TimingSession> expected = new ArrayList<>();
1640
1641 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1642 mQuotaController.prepareForExecutionLocked(jobStatus);
1643 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1644 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1645 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1646 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1647
1648 // Test overlapping jobs.
1649 JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2);
1650 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1651
1652 JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3);
1653 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1654
1655 advanceElapsedClock(SECOND_IN_MILLIS);
1656
1657 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1658 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1659 mQuotaController.prepareForExecutionLocked(jobStatus);
1660 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1661 mQuotaController.prepareForExecutionLocked(jobStatus2);
1662 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1663 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1664 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1665 mQuotaController.prepareForExecutionLocked(jobStatus3);
1666 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1667 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1668 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1669 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1670 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1671 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1672 }
1673
1674 /**
1675 * Tests that TimingSessions are saved properly when the device alternates between
1676 * charging and discharging.
1677 */
1678 @Test
1679 public void testTimerTracking_ChargingAndDischarging() {
Kweku Adams288e73b2019-01-17 13:53:24 -08001680 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1681
Kweku Adams4836f9d2018-11-12 17:04:17 -08001682 JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
1683 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1684 JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
1685 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1686 JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3);
1687 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1688 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1689 List<TimingSession> expected = new ArrayList<>();
1690
1691 // A job starting while charging. Only the portion that runs during the discharging period
1692 // should be counted.
1693 setCharging();
1694
1695 mQuotaController.prepareForExecutionLocked(jobStatus);
1696 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1697 setDischarging();
1698 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1699 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1700 mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
1701 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1702 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1703
1704 advanceElapsedClock(SECOND_IN_MILLIS);
1705
1706 // One job starts while discharging, spans a charging session, and ends after the charging
1707 // session. Only the portions during the discharging periods should be counted. This should
1708 // result in two TimingSessions. A second job starts while discharging and ends within the
1709 // charging session. Only the portion during the first discharging portion should be
1710 // counted. A third job starts and ends within the charging session. The third job
1711 // shouldn't be included in either job count.
1712 setDischarging();
1713 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1714 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1715 mQuotaController.prepareForExecutionLocked(jobStatus);
1716 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1717 mQuotaController.prepareForExecutionLocked(jobStatus2);
1718 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1719 setCharging();
1720 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
1721 mQuotaController.prepareForExecutionLocked(jobStatus3);
1722 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1723 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1724 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1725 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1726 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1727 setDischarging();
1728 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1729 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1730 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1731 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
1732 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1733
1734 // A job starting while discharging and ending while charging. Only the portion that runs
1735 // during the discharging period should be counted.
1736 setDischarging();
1737 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1738 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1739 mQuotaController.prepareForExecutionLocked(jobStatus2);
1740 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1741 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1742 setCharging();
1743 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1744 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1745 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1746 }
1747
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001748 /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
1749 @Test
1750 public void testTimerTracking_AllBackground() {
1751 setDischarging();
Kweku Adams288e73b2019-01-17 13:53:24 -08001752 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001753
1754 JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
1755 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1756
1757 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1758
1759 List<TimingSession> expected = new ArrayList<>();
1760
1761 // Test single job.
1762 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1763 mQuotaController.prepareForExecutionLocked(jobStatus);
1764 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1765 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1766 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1767 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1768
1769 // Test overlapping jobs.
1770 JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
1771 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1772
1773 JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
1774 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1775
1776 advanceElapsedClock(SECOND_IN_MILLIS);
1777
1778 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1779 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1780 mQuotaController.prepareForExecutionLocked(jobStatus);
1781 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1782 mQuotaController.prepareForExecutionLocked(jobStatus2);
1783 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1784 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1785 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1786 mQuotaController.prepareForExecutionLocked(jobStatus3);
1787 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1788 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1789 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1790 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1791 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1792 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1793 }
1794
1795 /** Tests that Timers don't count foreground jobs. */
1796 @Test
1797 public void testTimerTracking_AllForeground() {
1798 setDischarging();
1799
1800 JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001801 setProcessState(ActivityManager.PROCESS_STATE_TOP);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001802 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1803
1804 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1805
1806 mQuotaController.prepareForExecutionLocked(jobStatus);
1807 advanceElapsedClock(5 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001808 // Change to a state that should still be considered foreground.
1809 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1810 advanceElapsedClock(5 * SECOND_IN_MILLIS);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001811 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1812 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1813 }
1814
1815 /**
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001816 * Tests that Timers properly track sessions when switching between foreground and background
1817 * states.
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001818 */
1819 @Test
1820 public void testTimerTracking_ForegroundAndBackground() {
1821 setDischarging();
1822
1823 JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
1824 JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
1825 JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001826 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1827 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1828 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
1829 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1830 List<TimingSession> expected = new ArrayList<>();
1831
1832 // UID starts out inactive.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001833 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001834 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1835 mQuotaController.prepareForExecutionLocked(jobBg1);
1836 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1837 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1838 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1839 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1840
1841 advanceElapsedClock(SECOND_IN_MILLIS);
1842
1843 // Bg job starts while inactive, spans an entire active session, and ends after the
1844 // active session.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001845 // App switching to foreground state then fg job starts.
1846 // App remains in foreground state after coming to foreground, so there should only be one
1847 // session.
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001848 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1849 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1850 mQuotaController.prepareForExecutionLocked(jobBg2);
1851 advanceElapsedClock(10 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001852 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1853 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001854 mQuotaController.prepareForExecutionLocked(jobFg3);
1855 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1856 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1857 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1858 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001859 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1860
1861 advanceElapsedClock(SECOND_IN_MILLIS);
1862
1863 // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
1864 // "inactive" and then bg job 2 starts. Then fg job ends.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001865 // This should result in two TimingSessions:
1866 // * The first should have a count of 1
1867 // * The second should have a count of 2 since it will include both jobs
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001868 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1869 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1870 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1871 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001872 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001873 mQuotaController.prepareForExecutionLocked(jobBg1);
1874 advanceElapsedClock(10 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001875 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1876 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001877 mQuotaController.prepareForExecutionLocked(jobFg3);
1878 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1879 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001880 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
1881 start = JobSchedulerService.sElapsedRealtimeClock.millis();
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001882 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001883 mQuotaController.prepareForExecutionLocked(jobBg2);
1884 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1885 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1886 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1887 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001888 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001889 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1890 }
1891
Kweku Adams4836f9d2018-11-12 17:04:17 -08001892 /**
Kweku Adams288e73b2019-01-17 13:53:24 -08001893 * Tests that Timers don't track job counts while in the foreground.
1894 */
1895 @Test
1896 public void testTimerTracking_JobCount_Foreground() {
1897 setDischarging();
1898
1899 final int standbyBucket = ACTIVE_INDEX;
1900 JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1);
1901 JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2);
1902
1903 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
1904 mQuotaController.maybeStartTrackingJobLocked(jobFg2, null);
1905 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1906 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1907 SOURCE_PACKAGE, standbyBucket);
1908 assertEquals(0, stats.jobCountInAllowedTime);
1909
1910 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1911 mQuotaController.prepareForExecutionLocked(jobFg1);
1912 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1913 mQuotaController.prepareForExecutionLocked(jobFg2);
1914 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1915 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
1916 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1917 mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
1918 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1919
1920 assertEquals(0, stats.jobCountInAllowedTime);
1921 }
1922
1923 /**
1924 * Tests that Timers properly track job counts while in the background.
1925 */
1926 @Test
1927 public void testTimerTracking_JobCount_Background() {
1928 final int standbyBucket = WORKING_INDEX;
1929 JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1);
1930 JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2);
1931 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1932 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1933
1934 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1935 SOURCE_PACKAGE, standbyBucket);
1936 assertEquals(0, stats.jobCountInAllowedTime);
1937
1938 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
1939 mQuotaController.prepareForExecutionLocked(jobBg1);
1940 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1941 mQuotaController.prepareForExecutionLocked(jobBg2);
1942 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1943 mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
1944 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1945 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1946
1947 assertEquals(2, stats.jobCountInAllowedTime);
1948 }
1949
1950 /**
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001951 * Tests that Timers properly track overlapping top and background jobs.
1952 */
1953 @Test
1954 public void testTimerTracking_TopAndNonTop() {
1955 setDischarging();
1956
1957 JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1);
1958 JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2);
1959 JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3);
1960 JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4);
1961 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1962 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1963 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
1964 mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
1965 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1966 List<TimingSession> expected = new ArrayList<>();
1967
1968 // UID starts out inactive.
1969 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1970 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1971 mQuotaController.prepareForExecutionLocked(jobBg1);
1972 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1973 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1974 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1975 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1976
1977 advanceElapsedClock(SECOND_IN_MILLIS);
1978
1979 // Bg job starts while inactive, spans an entire active session, and ends after the
1980 // active session.
1981 // App switching to top state then fg job starts.
1982 // App remains in top state after coming to top, so there should only be one
1983 // session.
1984 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1985 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1986 mQuotaController.prepareForExecutionLocked(jobBg2);
1987 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1988 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1989 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1990 mQuotaController.prepareForExecutionLocked(jobTop);
1991 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1992 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
1993 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1994 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1995 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1996
1997 advanceElapsedClock(SECOND_IN_MILLIS);
1998
1999 // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
2000 // foreground_service and a new job starts. Shortly after, uid goes
2001 // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
2002 // This should result in two TimingSessions:
2003 // * The first should have a count of 1
2004 // * The second should have a count of 2, which accounts for the bg2 and fg, but not top
2005 // jobs.
2006 start = JobSchedulerService.sElapsedRealtimeClock.millis();
2007 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
2008 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
2009 mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
2010 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
2011 mQuotaController.prepareForExecutionLocked(jobBg1);
2012 advanceElapsedClock(10 * SECOND_IN_MILLIS);
2013 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
2014 setProcessState(ActivityManager.PROCESS_STATE_TOP);
2015 mQuotaController.prepareForExecutionLocked(jobTop);
2016 advanceElapsedClock(10 * SECOND_IN_MILLIS);
2017 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
2018 advanceElapsedClock(5 * SECOND_IN_MILLIS);
2019 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
2020 mQuotaController.prepareForExecutionLocked(jobFg1);
2021 advanceElapsedClock(5 * SECOND_IN_MILLIS);
2022 setProcessState(ActivityManager.PROCESS_STATE_TOP);
2023 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
2024 start = JobSchedulerService.sElapsedRealtimeClock.millis();
2025 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
2026 mQuotaController.prepareForExecutionLocked(jobBg2);
2027 advanceElapsedClock(10 * SECOND_IN_MILLIS);
2028 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
2029 advanceElapsedClock(10 * SECOND_IN_MILLIS);
2030 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
2031 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
2032 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
2033 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
2034 }
2035
2036 /**
2037 * Tests that TOP jobs aren't stopped when an app runs out of quota.
2038 */
2039 @Test
2040 public void testTracking_OutOfQuota_ForegroundAndBackground() {
2041 setDischarging();
2042
2043 JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
2044 JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
2045 trackJobs(jobBg, jobTop);
2046 setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
2047 // Now the package only has 20 seconds to run.
2048 final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
2049 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2050 createTimingSession(
2051 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
2052 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
2053
2054 InOrder inOrder = inOrder(mJobSchedulerService);
2055
2056 // UID starts out inactive.
2057 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
2058 // Start the job.
2059 mQuotaController.prepareForExecutionLocked(jobBg);
2060 advanceElapsedClock(remainingTimeMs / 2);
2061 // New job starts after UID is in the foreground. Since the app is now in the foreground, it
2062 // should continue to have remainingTimeMs / 2 time remaining.
2063 setProcessState(ActivityManager.PROCESS_STATE_TOP);
2064 mQuotaController.prepareForExecutionLocked(jobTop);
2065 advanceElapsedClock(remainingTimeMs);
2066
2067 // Wait for some extra time to allow for job processing.
2068 inOrder.verify(mJobSchedulerService,
2069 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
2070 .onControllerStateChanged();
2071 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg));
2072 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop));
2073 // Go to a background state.
2074 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
2075 advanceElapsedClock(remainingTimeMs / 2 + 1);
2076 inOrder.verify(mJobSchedulerService,
2077 timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
2078 .onControllerStateChanged();
2079 // Top job should still be allowed to run.
2080 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2081 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2082
2083 // New jobs to run.
2084 JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
2085 JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
2086 JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
2087 setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
2088
2089 advanceElapsedClock(20 * SECOND_IN_MILLIS);
2090 setProcessState(ActivityManager.PROCESS_STATE_TOP);
2091 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
2092 .onControllerStateChanged();
2093 trackJobs(jobFg, jobTop);
2094 mQuotaController.prepareForExecutionLocked(jobTop);
2095 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2096 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2097 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2098
2099 // App still in foreground so everything should be in quota.
2100 advanceElapsedClock(20 * SECOND_IN_MILLIS);
2101 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
2102 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2103 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2104 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2105
2106 advanceElapsedClock(20 * SECOND_IN_MILLIS);
2107 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
2108 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
2109 .onControllerStateChanged();
2110 // App is now in background and out of quota. Fg should now change to out of quota since it
2111 // wasn't started. Top should remain in quota since it started when the app was in TOP.
2112 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2113 assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2114 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2115 trackJobs(jobBg2);
2116 assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2117 }
2118
2119 /**
Kweku Adams4836f9d2018-11-12 17:04:17 -08002120 * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
2121 * its quota.
2122 */
2123 @Test
2124 public void testTracking_OutOfQuota() {
2125 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
2126 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2127 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
Kweku Adams288e73b2019-01-17 13:53:24 -08002128 setProcessState(ActivityManager.PROCESS_STATE_HOME);
Kweku Adams4836f9d2018-11-12 17:04:17 -08002129 // Now the package only has two seconds to run.
2130 final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
2131 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2132 createTimingSession(
2133 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
2134 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
2135
2136 // Start the job.
2137 mQuotaController.prepareForExecutionLocked(jobStatus);
2138 advanceElapsedClock(remainingTimeMs);
2139
2140 // Wait for some extra time to allow for job processing.
2141 verify(mJobSchedulerService,
2142 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
2143 .onControllerStateChanged();
2144 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2145 }
2146
2147 /**
2148 * Tests that a job is properly handled when it's at the edge of its quota and the old quota is
2149 * being phased out.
2150 */
2151 @Test
2152 public void testTracking_RollingQuota() {
2153 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
2154 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2155 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
Kweku Adams288e73b2019-01-17 13:53:24 -08002156 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
Kweku Adams4836f9d2018-11-12 17:04:17 -08002157 Handler handler = mQuotaController.getHandler();
2158 spyOn(handler);
2159
2160 long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2161 final long remainingTimeMs = SECOND_IN_MILLIS;
2162 // The package only has one second to run, but this session is at the edge of the rolling
2163 // window, so as the package "reaches its quota" it will have more to keep running.
2164 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2165 createTimingSession(now - 2 * HOUR_IN_MILLIS,
Kweku Adamse3e16402019-03-26 15:48:51 -07002166 10 * SECOND_IN_MILLIS - remainingTimeMs, 1));
2167 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2168 createTimingSession(now - HOUR_IN_MILLIS,
2169 9 * MINUTE_IN_MILLIS + 50 * SECOND_IN_MILLIS, 1));
Kweku Adams4836f9d2018-11-12 17:04:17 -08002170
2171 assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
2172 // Start the job.
2173 mQuotaController.prepareForExecutionLocked(jobStatus);
2174 advanceElapsedClock(remainingTimeMs);
2175
2176 // Wait for some extra time to allow for job processing.
2177 verify(mJobSchedulerService,
2178 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
2179 .onControllerStateChanged();
2180 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2181 // The job used up the remaining quota, but in that time, the same amount of time in the
2182 // old TimingSession also fell out of the quota window, so it should still have the same
2183 // amount of remaining time left its quota.
2184 assertEquals(remainingTimeMs,
2185 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
Kweku Adamse3e16402019-03-26 15:48:51 -07002186 // Handler is told to check when the quota will be consumed, not when the initial
2187 // remaining time is over.
2188 verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(10 * SECOND_IN_MILLIS));
2189 verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
2190
2191 // After 10 seconds, the job should finally be out of quota.
2192 advanceElapsedClock(10 * SECOND_IN_MILLIS - remainingTimeMs);
2193 // Wait for some extra time to allow for job processing.
2194 verify(mJobSchedulerService,
2195 timeout(12 * SECOND_IN_MILLIS).times(1))
2196 .onControllerStateChanged();
2197 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2198 verify(handler, never()).sendMessageDelayed(any(), anyInt());
Kweku Adams4836f9d2018-11-12 17:04:17 -08002199 }
2200}