blob: 08f6a372de861cf1575f8f8fd6683e7e207d9ebd [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;
61import android.content.pm.PackageManagerInternal;
62import android.os.BatteryManager;
63import android.os.BatteryManagerInternal;
64import android.os.Handler;
65import android.os.Looper;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080066import android.os.RemoteException;
Kweku Adams4836f9d2018-11-12 17:04:17 -080067import android.os.SystemClock;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080068import android.util.SparseBooleanArray;
Kweku Adams4836f9d2018-11-12 17:04:17 -080069
70import androidx.test.runner.AndroidJUnit4;
71
72import com.android.server.LocalServices;
73import com.android.server.job.JobSchedulerService;
74import com.android.server.job.JobSchedulerService.Constants;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080075import com.android.server.job.JobStore;
Kweku Adams045fb5722018-12-11 14:29:10 -080076import com.android.server.job.controllers.QuotaController.ExecutionStats;
Kweku Adams4836f9d2018-11-12 17:04:17 -080077import com.android.server.job.controllers.QuotaController.TimingSession;
78
79import org.junit.After;
80import org.junit.Before;
81import org.junit.Test;
82import org.junit.runner.RunWith;
83import org.mockito.ArgumentCaptor;
84import org.mockito.InOrder;
85import org.mockito.Mock;
86import org.mockito.MockitoSession;
87import org.mockito.quality.Strictness;
88
89import java.time.Clock;
90import java.time.Duration;
91import java.time.ZoneOffset;
92import java.util.ArrayList;
93import java.util.List;
94
95@RunWith(AndroidJUnit4.class)
96public class QuotaControllerTest {
97 private static final long SECOND_IN_MILLIS = 1000L;
98 private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
99 private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
100 private static final String TAG_CLEANUP = "*job.cleanup*";
101 private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
Kweku Adams4836f9d2018-11-12 17:04:17 -0800102 private static final int CALLING_UID = 1000;
103 private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
104 private static final int SOURCE_USER_ID = 0;
105
106 private BroadcastReceiver mChargingReceiver;
107 private Constants mConstants;
108 private QuotaController mQuotaController;
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800109 private int mSourceUid;
110 private IUidObserver mUidObserver;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800111
112 private MockitoSession mMockingSession;
113 @Mock
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800114 private ActivityManagerInternal mActivityMangerInternal;
115 @Mock
Kweku Adams4836f9d2018-11-12 17:04:17 -0800116 private AlarmManager mAlarmManager;
117 @Mock
118 private Context mContext;
119 @Mock
120 private JobSchedulerService mJobSchedulerService;
121 @Mock
122 private UsageStatsManagerInternal mUsageStatsManager;
123
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800124 private JobStore mJobStore;
125
Kweku Adams4836f9d2018-11-12 17:04:17 -0800126 @Before
127 public void setUp() {
128 mMockingSession = mockitoSession()
129 .initMocks(this)
130 .strictness(Strictness.LENIENT)
131 .mockStatic(LocalServices.class)
132 .startMocking();
133 // Make sure constants turn on QuotaController.
134 mConstants = new Constants();
135 mConstants.USE_HEARTBEATS = false;
136
137 // Called in StateController constructor.
138 when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
139 when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
140 when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
141 // Called in QuotaController constructor.
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800142 IActivityManager activityManager = ActivityManager.getService();
143 spyOn(activityManager);
144 try {
145 doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
146 } catch (RemoteException e) {
147 fail("registerUidObserver threw exception: " + e.getMessage());
148 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800149 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
150 when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800151 doReturn(mActivityMangerInternal)
152 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
Kweku Adams4836f9d2018-11-12 17:04:17 -0800153 doReturn(mock(BatteryManagerInternal.class))
154 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
155 doReturn(mUsageStatsManager)
156 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
157 // Used in JobStatus.
158 doReturn(mock(PackageManagerInternal.class))
159 .when(() -> LocalServices.getService(PackageManagerInternal.class));
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800160 // Used in QuotaController.Handler.
161 mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
162 when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800163
Kweku Adams045fb5722018-12-11 14:29:10 -0800164 // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
165 // in the past, and QuotaController sometimes floors values at 0, so if the test time
166 // causes sessions with negative timestamps, they will fail.
Kweku Adams4836f9d2018-11-12 17:04:17 -0800167 JobSchedulerService.sSystemClock =
Kweku Adams045fb5722018-12-11 14:29:10 -0800168 getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
169 24 * HOUR_IN_MILLIS);
170 JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
171 Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC),
172 24 * HOUR_IN_MILLIS);
173 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
174 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
175 24 * HOUR_IN_MILLIS);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800176
177 // Initialize real objects.
178 // Capture the listeners.
179 ArgumentCaptor<BroadcastReceiver> receiverCaptor =
180 ArgumentCaptor.forClass(BroadcastReceiver.class);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800181 ArgumentCaptor<IUidObserver> uidObserverCaptor =
182 ArgumentCaptor.forClass(IUidObserver.class);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800183 mQuotaController = new QuotaController(mJobSchedulerService);
184
185 verify(mContext).registerReceiver(receiverCaptor.capture(), any());
186 mChargingReceiver = receiverCaptor.getValue();
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800187 try {
188 verify(activityManager).registerUidObserver(
189 uidObserverCaptor.capture(),
190 eq(ActivityManager.UID_OBSERVER_PROCSTATE),
191 eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE),
192 any());
193 mUidObserver = uidObserverCaptor.getValue();
194 mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
195 } catch (RemoteException e) {
196 fail(e.getMessage());
197 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800198 }
199
200 @After
201 public void tearDown() {
202 if (mMockingSession != null) {
203 mMockingSession.finishMocking();
204 }
205 }
206
207 private Clock getAdvancedClock(Clock clock, long incrementMs) {
208 return Clock.offset(clock, Duration.ofMillis(incrementMs));
209 }
210
211 private void advanceElapsedClock(long incrementMs) {
212 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
213 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
214 }
215
216 private void setCharging() {
217 Intent intent = new Intent(BatteryManager.ACTION_CHARGING);
218 mChargingReceiver.onReceive(mContext, intent);
219 }
220
221 private void setDischarging() {
222 Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING);
223 mChargingReceiver.onReceive(mContext, intent);
224 }
225
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800226 private void setProcessState(int procState) {
227 try {
228 doReturn(procState).when(mActivityMangerInternal).getUidProcessState(mSourceUid);
229 SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
230 spyOn(foregroundUids);
231 mUidObserver.onUidStateChanged(mSourceUid, procState, 0);
232 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
233 verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1))
234 .put(eq(mSourceUid), eq(true));
235 assertTrue(foregroundUids.get(mSourceUid));
236 } else {
237 verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1)).delete(eq(mSourceUid));
238 assertFalse(foregroundUids.get(mSourceUid));
239 }
240 } catch (RemoteException e) {
241 fail("registerUidObserver threw exception: " + e.getMessage());
242 }
243 }
244
Kweku Adams4836f9d2018-11-12 17:04:17 -0800245 private void setStandbyBucket(int bucketIndex) {
246 int bucket;
247 switch (bucketIndex) {
248 case ACTIVE_INDEX:
249 bucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
250 break;
251 case WORKING_INDEX:
252 bucket = UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
253 break;
254 case FREQUENT_INDEX:
255 bucket = UsageStatsManager.STANDBY_BUCKET_FREQUENT;
256 break;
257 case RARE_INDEX:
258 bucket = UsageStatsManager.STANDBY_BUCKET_RARE;
259 break;
260 default:
261 bucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
262 }
263 when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
264 anyLong())).thenReturn(bucket);
265 }
266
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800267 private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800268 setStandbyBucket(bucketIndex);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800269 for (JobStatus job : jobs) {
270 job.setStandbyBucket(bucketIndex);
271 }
272 }
273
274 private void trackJobs(JobStatus... jobs) {
275 for (JobStatus job : jobs) {
276 mJobStore.add(job);
277 mQuotaController.maybeStartTrackingJobLocked(job, null);
278 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800279 }
280
281 private JobStatus createJobStatus(String testTag, int jobId) {
282 JobInfo jobInfo = new JobInfo.Builder(jobId,
283 new ComponentName(mContext, "TestQuotaJobService"))
284 .setMinimumLatency(Math.abs(jobId) + 1)
285 .build();
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800286 JobStatus js = JobStatus.createFromJobInfo(
Kweku Adams4836f9d2018-11-12 17:04:17 -0800287 jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800288 // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
289 js.setStandbyBucket(FREQUENT_INDEX);
290 return js;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800291 }
292
293 private TimingSession createTimingSession(long start, long duration, int count) {
294 return new TimingSession(start, start + duration, count);
295 }
296
297 @Test
298 public void testSaveTimingSession() {
299 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
300
301 List<TimingSession> expected = new ArrayList<>();
302 TimingSession one = new TimingSession(1, 10, 1);
303 TimingSession two = new TimingSession(11, 20, 2);
304 TimingSession thr = new TimingSession(21, 30, 3);
305
306 mQuotaController.saveTimingSession(0, "com.android.test", one);
307 expected.add(one);
308 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
309
310 mQuotaController.saveTimingSession(0, "com.android.test", two);
311 expected.add(two);
312 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
313
314 mQuotaController.saveTimingSession(0, "com.android.test", thr);
315 expected.add(thr);
316 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
317 }
318
319 @Test
320 public void testDeleteObsoleteSessionsLocked() {
321 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
322 TimingSession one = createTimingSession(
323 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
324 TimingSession two = createTimingSession(
325 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
326 TimingSession thr = createTimingSession(
327 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
328 // Overlaps 24 hour boundary.
329 TimingSession fou = createTimingSession(
330 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
331 // Way past the 24 hour boundary.
332 TimingSession fiv = createTimingSession(
333 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
334 List<TimingSession> expected = new ArrayList<>();
335 // Added in correct (chronological) order.
336 expected.add(fou);
337 expected.add(thr);
338 expected.add(two);
339 expected.add(one);
340 mQuotaController.saveTimingSession(0, "com.android.test", fiv);
341 mQuotaController.saveTimingSession(0, "com.android.test", fou);
342 mQuotaController.saveTimingSession(0, "com.android.test", thr);
343 mQuotaController.saveTimingSession(0, "com.android.test", two);
344 mQuotaController.saveTimingSession(0, "com.android.test", one);
345
346 mQuotaController.deleteObsoleteSessionsLocked();
347
348 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
349 }
350
351 @Test
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800352 public void testOnAppRemovedLocked() {
353 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
354 mQuotaController.saveTimingSession(0, "com.android.test.remove",
355 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
356 mQuotaController.saveTimingSession(0, "com.android.test.remove",
357 createTimingSession(
358 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
359 mQuotaController.saveTimingSession(0, "com.android.test.remove",
360 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
361 // Test that another app isn't affected.
362 TimingSession one = createTimingSession(
363 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
364 TimingSession two = createTimingSession(
365 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
366 List<TimingSession> expected = new ArrayList<>();
367 // Added in correct (chronological) order.
368 expected.add(two);
369 expected.add(one);
370 mQuotaController.saveTimingSession(0, "com.android.test.stay", two);
371 mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
372
Kweku Adams045fb5722018-12-11 14:29:10 -0800373 ExecutionStats expectedStats = new ExecutionStats();
Kweku Adams288e73b2019-01-17 13:53:24 -0800374 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800375 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
376
Kweku Adams288e73b2019-01-17 13:53:24 -0800377 final int uid = 10001;
378 mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800379 assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
380 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800381 assertEquals(expectedStats,
382 mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
383 assertNotEquals(expectedStats,
384 mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
Kweku Adams288e73b2019-01-17 13:53:24 -0800385
386 assertFalse(mQuotaController.getForegroundUids().get(uid));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800387 }
388
389 @Test
390 public void testOnUserRemovedLocked() {
391 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
392 mQuotaController.saveTimingSession(0, "com.android.test",
393 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
394 mQuotaController.saveTimingSession(0, "com.android.test",
395 createTimingSession(
396 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
397 mQuotaController.saveTimingSession(0, "com.android.test",
398 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
399 // Test that another user isn't affected.
400 TimingSession one = createTimingSession(
401 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
402 TimingSession two = createTimingSession(
403 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
404 List<TimingSession> expected = new ArrayList<>();
405 // Added in correct (chronological) order.
406 expected.add(two);
407 expected.add(one);
408 mQuotaController.saveTimingSession(10, "com.android.test", two);
409 mQuotaController.saveTimingSession(10, "com.android.test", one);
410
Kweku Adams045fb5722018-12-11 14:29:10 -0800411 ExecutionStats expectedStats = new ExecutionStats();
Kweku Adams288e73b2019-01-17 13:53:24 -0800412 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800413 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
414
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800415 mQuotaController.onUserRemovedLocked(0);
416 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
417 assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800418 assertEquals(expectedStats,
419 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
420 assertNotEquals(expectedStats,
421 mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800422 }
423
424 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -0800425 public void testUpdateExecutionStatsLocked_NoTimer() {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800426 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
427 // Added in chronological order.
428 mQuotaController.saveTimingSession(0, "com.android.test",
429 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
430 mQuotaController.saveTimingSession(0, "com.android.test",
431 createTimingSession(
432 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
433 mQuotaController.saveTimingSession(0, "com.android.test",
434 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
435 mQuotaController.saveTimingSession(0, "com.android.test",
436 createTimingSession(
437 now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1));
438 mQuotaController.saveTimingSession(0, "com.android.test",
439 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
440
Kweku Adams045fb5722018-12-11 14:29:10 -0800441 // Test an app that hasn't had any activity.
442 ExecutionStats expectedStats = new ExecutionStats();
443 ExecutionStats inputStats = new ExecutionStats();
444
445 inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
446 // Invalid time is now +24 hours since there are no sessions at all for the app.
Kweku Adams288e73b2019-01-17 13:53:24 -0800447 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800448 mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
449 assertEquals(expectedStats, inputStats);
450
451 inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
452 // Invalid time is now +18 hours since there are no sessions in the window but the earliest
453 // session is 6 hours ago.
Kweku Adams288e73b2019-01-17 13:53:24 -0800454 expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800455 expectedStats.executionTimeInWindowMs = 0;
456 expectedStats.bgJobCountInWindow = 0;
457 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
458 expectedStats.bgJobCountInMaxPeriod = 15;
459 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
460 assertEquals(expectedStats, inputStats);
461
462 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
463 // Invalid time is now since the session straddles the window cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800464 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800465 expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
466 expectedStats.bgJobCountInWindow = 3;
467 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
468 expectedStats.bgJobCountInMaxPeriod = 15;
469 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
470 assertEquals(expectedStats, inputStats);
471
472 inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
473 // Invalid time is now since the start of the session is at the very edge of the window
474 // cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800475 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800476 expectedStats.executionTimeInWindowMs = 4 * 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 = 49 * MINUTE_IN_MILLIS;
484 // Invalid time is now +44 minutes since the earliest session in the window is now-5
485 // minutes.
Kweku Adams288e73b2019-01-17 13:53:24 -0800486 expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
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 = 50 * MINUTE_IN_MILLIS;
495 // Invalid time is now since the session is at the very edge of the window cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800496 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800497 expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
498 expectedStats.bgJobCountInWindow = 4;
499 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
500 expectedStats.bgJobCountInMaxPeriod = 15;
501 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
502 assertEquals(expectedStats, inputStats);
503
504 inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
505 // Invalid time is now since the start of the session is at the very edge of the window
506 // cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800507 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800508 expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
509 expectedStats.bgJobCountInWindow = 5;
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 = 2 * HOUR_IN_MILLIS;
516 // Invalid time is now since the session straddles the window cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800517 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800518 expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
519 expectedStats.bgJobCountInWindow = 10;
520 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
521 expectedStats.bgJobCountInMaxPeriod = 15;
522 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
523 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
524 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
525 assertEquals(expectedStats, inputStats);
526
527 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
528 // Invalid time is now +59 minutes since the earliest session in the window is now-121
529 // minutes.
Kweku Adams288e73b2019-01-17 13:53:24 -0800530 expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800531 expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
532 expectedStats.bgJobCountInWindow = 10;
533 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
534 expectedStats.bgJobCountInMaxPeriod = 15;
535 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
536 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
537 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
538 assertEquals(expectedStats, inputStats);
539
540 inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
541 // Invalid time is now since the start of the session is at the very edge of the window
542 // cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800543 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800544 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
545 expectedStats.bgJobCountInWindow = 15;
546 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
547 expectedStats.bgJobCountInMaxPeriod = 15;
548 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
549 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
550 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
551 assertEquals(expectedStats, inputStats);
552
Kweku Adams288e73b2019-01-17 13:53:24 -0800553 // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period.
Kweku Adams045fb5722018-12-11 14:29:10 -0800554 mQuotaController.getTimingSessions(0, "com.android.test")
555 .add(0,
556 createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
557 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
558 // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
559 // before the end of the max period cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800560 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800561 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
562 expectedStats.bgJobCountInWindow = 15;
563 expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
564 expectedStats.bgJobCountInMaxPeriod = 18;
565 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
566 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
567 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
568 assertEquals(expectedStats, inputStats);
569
570 mQuotaController.getTimingSessions(0, "com.android.test")
571 .add(0,
572 createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
573 2 * MINUTE_IN_MILLIS, 2));
574 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
575 // Invalid time is now since the earlist session straddles the max period cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800576 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800577 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
578 expectedStats.bgJobCountInWindow = 15;
579 expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
580 expectedStats.bgJobCountInMaxPeriod = 20;
581 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
582 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
583 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
584 assertEquals(expectedStats, inputStats);
585 }
586
587 /**
588 * Tests that getExecutionStatsLocked returns the correct stats.
589 */
590 @Test
591 public void testGetExecutionStatsLocked_Values() {
592 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
593 mQuotaController.saveTimingSession(0, "com.android.test",
594 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
595 mQuotaController.saveTimingSession(0, "com.android.test",
596 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
597 mQuotaController.saveTimingSession(0, "com.android.test",
598 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
599 mQuotaController.saveTimingSession(0, "com.android.test",
600 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
601
602 ExecutionStats expectedStats = new ExecutionStats();
603
604 // Active
605 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800606 expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800607 expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
608 expectedStats.bgJobCountInWindow = 5;
609 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
610 expectedStats.bgJobCountInMaxPeriod = 20;
611 assertEquals(expectedStats,
612 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
613
614 // Working
615 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800616 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800617 expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
618 expectedStats.bgJobCountInWindow = 10;
619 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
620 expectedStats.bgJobCountInMaxPeriod = 20;
621 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
622 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
623 assertEquals(expectedStats,
624 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
625
626 // Frequent
627 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800628 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800629 expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
630 expectedStats.bgJobCountInWindow = 15;
631 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
632 expectedStats.bgJobCountInMaxPeriod = 20;
633 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
634 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
635 assertEquals(expectedStats,
636 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
637
638 // Rare
639 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800640 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800641 expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
642 expectedStats.bgJobCountInWindow = 20;
643 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
644 expectedStats.bgJobCountInMaxPeriod = 20;
645 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
646 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
647 assertEquals(expectedStats,
648 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
649 }
650
651 /**
652 * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
653 */
654 @Test
655 public void testGetExecutionStatsLocked_Caching() {
656 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
657 mQuotaController.saveTimingSession(0, "com.android.test",
658 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
659 mQuotaController.saveTimingSession(0, "com.android.test",
660 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
661 mQuotaController.saveTimingSession(0, "com.android.test",
662 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
663 mQuotaController.saveTimingSession(0, "com.android.test",
664 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
665 final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0,
666 "com.android.test", ACTIVE_INDEX);
667 final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0,
668 "com.android.test", WORKING_INDEX);
669 final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
670 "com.android.test", FREQUENT_INDEX);
671 final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0,
672 "com.android.test", RARE_INDEX);
673
674 // Advance clock so that the working stats shouldn't be the same.
675 advanceElapsedClock(MINUTE_IN_MILLIS);
676 // Change frequent bucket size so that the stats need to be recalculated.
677 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS;
678 mQuotaController.onConstantsUpdatedLocked();
679
680 ExecutionStats expectedStats = new ExecutionStats();
681 expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800682 expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800683 expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
684 expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
685 expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
686 expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
687 expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
688 final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
689 "com.android.test", ACTIVE_INDEX);
690 // Stats for the same bucket should use the same object.
691 assertTrue(originalStatsActive == newStatsActive);
692 assertEquals(expectedStats, newStatsActive);
693
694 expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800695 expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800696 expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
697 expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
698 expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
699 final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
700 "com.android.test", WORKING_INDEX);
701 assertTrue(originalStatsWorking == newStatsWorking);
702 assertNotEquals(expectedStats, newStatsWorking);
703
704 expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800705 expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800706 expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
707 expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
708 expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
709 final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
710 "com.android.test", FREQUENT_INDEX);
711 assertTrue(originalStatsFrequent == newStatsFrequent);
712 assertNotEquals(expectedStats, newStatsFrequent);
713
714 expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800715 expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800716 expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
717 expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
718 expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
719 final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
720 "com.android.test", RARE_INDEX);
721 assertTrue(originalStatsRare == newStatsRare);
722 assertEquals(expectedStats, newStatsRare);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800723 }
724
Kweku Adamse3e16402019-03-26 15:48:51 -0700725 /**
726 * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket
727 * window.
728 */
729 @Test
730 public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() {
731 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
732 // Close to RARE boundary.
733 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
734 createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS),
735 30 * SECOND_IN_MILLIS, 5));
736 // Far away from FREQUENT boundary.
737 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
738 createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
739 // Overlap WORKING_SET boundary.
740 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
741 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
742 3 * MINUTE_IN_MILLIS, 5));
743 // Close to ACTIVE boundary.
744 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
745 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
746
747 setStandbyBucket(RARE_INDEX);
748 assertEquals(30 * SECOND_IN_MILLIS,
749 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
750 assertEquals(MINUTE_IN_MILLIS,
751 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
752
753 setStandbyBucket(FREQUENT_INDEX);
754 assertEquals(MINUTE_IN_MILLIS,
755 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
756 assertEquals(MINUTE_IN_MILLIS,
757 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
758
759 setStandbyBucket(WORKING_INDEX);
760 assertEquals(5 * MINUTE_IN_MILLIS,
761 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
762 assertEquals(7 * MINUTE_IN_MILLIS,
763 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
764
765 // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the
766 // max execution time.
767 setStandbyBucket(ACTIVE_INDEX);
768 assertEquals(7 * MINUTE_IN_MILLIS,
769 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
770 assertEquals(mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS,
771 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
772 }
773
774 /**
775 * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit.
776 */
777 @Test
778 public void testGetTimeUntilQuotaConsumedLocked_MaxExecution() {
779 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
780 // Overlap boundary.
781 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
782 createTimingSession(
783 now - (24 * HOUR_IN_MILLIS + 8 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5));
784
785 setStandbyBucket(WORKING_INDEX);
786 assertEquals(8 * MINUTE_IN_MILLIS,
787 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
788 // Max time will phase out, so should use bucket limit.
789 assertEquals(10 * MINUTE_IN_MILLIS,
790 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
791
792 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
793 // Close to boundary.
794 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
795 createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS),
796 4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5));
797
798 setStandbyBucket(WORKING_INDEX);
799 assertEquals(5 * MINUTE_IN_MILLIS,
800 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
801 assertEquals(10 * MINUTE_IN_MILLIS,
802 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
803
804 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
805 // Far from boundary.
806 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
807 createTimingSession(
808 now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5));
809
810 setStandbyBucket(WORKING_INDEX);
811 assertEquals(3 * MINUTE_IN_MILLIS,
812 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
813 assertEquals(3 * MINUTE_IN_MILLIS,
814 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
815 }
816
817 /**
818 * Test getTimeUntilQuotaConsumedLocked when the max execution time and bucket window time
819 * remaining are equal.
820 */
821 @Test
822 public void testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining() {
823 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
824 setStandbyBucket(FREQUENT_INDEX);
825
826 // Overlap boundary.
827 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
828 createTimingSession(
829 now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS),
830 4 * HOUR_IN_MILLIS,
831 5));
832 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
833 createTimingSession(
834 now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
835
836 // Both max and bucket time have 8 minutes left.
837 assertEquals(8 * MINUTE_IN_MILLIS,
838 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
839 // Max time essentially free. Bucket time has 2 min phase out plus original 8 minute
840 // window time.
841 assertEquals(10 * MINUTE_IN_MILLIS,
842 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
843
844 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
845 // Overlap boundary.
846 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
847 createTimingSession(
848 now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5));
849 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
850 createTimingSession(
851 now - (20 * HOUR_IN_MILLIS),
852 3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS,
853 5));
854 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
855 createTimingSession(
856 now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
857
858 // Both max and bucket time have 8 minutes left.
859 assertEquals(8 * MINUTE_IN_MILLIS,
860 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
861 // Max time only has one minute phase out. Bucket time has 2 minute phase out.
862 assertEquals(9 * MINUTE_IN_MILLIS,
863 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
864 }
865
Kweku Adams4836f9d2018-11-12 17:04:17 -0800866 @Test
Kweku Adams288e73b2019-01-17 13:53:24 -0800867 public void testIsWithinQuotaLocked_NeverApp() {
868 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
869 }
870
871 @Test
872 public void testIsWithinQuotaLocked_Charging() {
873 setCharging();
874 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
875 }
876
877 @Test
878 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() {
879 setDischarging();
880 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
881 mQuotaController.saveTimingSession(0, "com.android.test",
882 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
883 mQuotaController.saveTimingSession(0, "com.android.test",
884 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
885 mQuotaController.incrementJobCount(0, "com.android.test", 5);
886 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
887 }
888
889 @Test
890 public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
891 setDischarging();
892 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
893 final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
894 mQuotaController.saveTimingSession(0, "com.android.test.spam",
895 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
896 mQuotaController.saveTimingSession(0, "com.android.test.spam",
897 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
898 mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount);
899 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam",
900 WORKING_INDEX));
901
902 mQuotaController.saveTimingSession(0, "com.android.test.frequent",
903 createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000));
904 mQuotaController.saveTimingSession(0, "com.android.test.frequent",
905 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500));
906 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent",
907 FREQUENT_INDEX));
908 }
909
910 @Test
911 public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() {
912 setDischarging();
913 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
914 mQuotaController.saveTimingSession(0, "com.android.test",
915 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
916 mQuotaController.saveTimingSession(0, "com.android.test",
917 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
918 mQuotaController.saveTimingSession(0, "com.android.test",
919 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5));
920 mQuotaController.incrementJobCount(0, "com.android.test", 5);
921 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
922 }
923
924 @Test
925 public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
926 setDischarging();
927 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
928 final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
929 mQuotaController.saveTimingSession(0, "com.android.test",
930 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
931 mQuotaController.saveTimingSession(0, "com.android.test",
932 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
933 mQuotaController.incrementJobCount(0, "com.android.test", jobCount);
934 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
935 }
936
937 @Test
Kweku Adams4836f9d2018-11-12 17:04:17 -0800938 public void testMaybeScheduleCleanupAlarmLocked() {
939 // No sessions saved yet.
940 mQuotaController.maybeScheduleCleanupAlarmLocked();
941 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
942
943 // Test with only one timing session saved.
944 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
945 final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
946 mQuotaController.saveTimingSession(0, "com.android.test",
947 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1));
948 mQuotaController.maybeScheduleCleanupAlarmLocked();
949 verify(mAlarmManager, times(1))
950 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
951
952 // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
953 mQuotaController.saveTimingSession(0, "com.android.test",
954 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
955 mQuotaController.saveTimingSession(0, "com.android.test",
956 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
957 mQuotaController.maybeScheduleCleanupAlarmLocked();
958 verify(mAlarmManager, times(1))
959 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
960 }
961
962 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -0800963 public void testMaybeScheduleStartAlarmLocked_Active() {
964 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
965 // because it schedules an alarm too. Prevent it from doing so.
966 spyOn(mQuotaController);
967 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
968
969 // Active window size is 10 minutes.
970 final int standbyBucket = ACTIVE_INDEX;
Kweku Adams288e73b2019-01-17 13:53:24 -0800971 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
Kweku Adams045fb5722018-12-11 14:29:10 -0800972
973 // No sessions saved yet.
974 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
975 standbyBucket);
976 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
977
978 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
979 // Test with timing sessions out of window but still under max execution limit.
980 final long expectedAlarmTime =
981 (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS
982 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
983 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
984 createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
985 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
986 createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
987 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
988 createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
989 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
990 standbyBucket);
991 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
992
993 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
994 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1));
995 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
996 standbyBucket);
997 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
998
999 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001000 setStandbyBucket(standbyBucket, jobStatus);
Kweku Adams045fb5722018-12-11 14:29:10 -08001001 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1002 mQuotaController.prepareForExecutionLocked(jobStatus);
1003 advanceElapsedClock(5 * MINUTE_IN_MILLIS);
1004 // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window
1005 // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no
1006 // longer has quota.
1007 assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
1008 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1009 standbyBucket);
1010 verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK),
1011 any(), any());
1012 }
1013
1014 @Test
Kweku Adams4836f9d2018-11-12 17:04:17 -08001015 public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
1016 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1017 // because it schedules an alarm too. Prevent it from doing so.
1018 spyOn(mQuotaController);
1019 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1020
1021 // Working set window size is 2 hours.
1022 final int standbyBucket = WORKING_INDEX;
1023
1024 // No sessions saved yet.
1025 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1026 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1027
1028 // Test with timing sessions out of window.
1029 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1030 mQuotaController.saveTimingSession(0, "com.android.test",
1031 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1032 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1033 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1034
1035 // Test with timing sessions in window but still in quota.
1036 final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
1037 // Counting backwards, the quota will come back one minute before the end.
1038 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001039 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS
1040 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001041 mQuotaController.saveTimingSession(0, "com.android.test",
1042 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1));
1043 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1044 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1045
1046 // Add some more sessions, but still in quota.
1047 mQuotaController.saveTimingSession(0, "com.android.test",
1048 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1049 mQuotaController.saveTimingSession(0, "com.android.test",
1050 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1));
1051 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1052 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1053
1054 // Test when out of quota.
1055 mQuotaController.saveTimingSession(0, "com.android.test",
1056 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1057 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1058 verify(mAlarmManager, times(1))
1059 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1060
1061 // Alarm already scheduled, so make sure it's not scheduled again.
1062 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1063 verify(mAlarmManager, times(1))
1064 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1065 }
1066
1067 @Test
1068 public void testMaybeScheduleStartAlarmLocked_Frequent() {
1069 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1070 // because it schedules an alarm too. Prevent it from doing so.
1071 spyOn(mQuotaController);
1072 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1073
1074 // Frequent window size is 8 hours.
1075 final int standbyBucket = FREQUENT_INDEX;
1076
1077 // No sessions saved yet.
1078 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1079 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1080
1081 // Test with timing sessions out of window.
1082 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1083 mQuotaController.saveTimingSession(0, "com.android.test",
1084 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1085 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1086 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1087
1088 // Test with timing sessions in window but still in quota.
1089 final long start = now - (6 * HOUR_IN_MILLIS);
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001090 final long expectedAlarmTime =
1091 start + 8 * HOUR_IN_MILLIS + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001092 mQuotaController.saveTimingSession(0, "com.android.test",
1093 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
1094 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1095 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1096
1097 // Add some more sessions, but still in quota.
1098 mQuotaController.saveTimingSession(0, "com.android.test",
1099 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1100 mQuotaController.saveTimingSession(0, "com.android.test",
1101 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
1102 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1103 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1104
1105 // Test when out of quota.
1106 mQuotaController.saveTimingSession(0, "com.android.test",
1107 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1108 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1109 verify(mAlarmManager, times(1))
1110 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1111
1112 // Alarm already scheduled, so make sure it's not scheduled again.
1113 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1114 verify(mAlarmManager, times(1))
1115 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1116 }
1117
1118 @Test
1119 public void testMaybeScheduleStartAlarmLocked_Rare() {
1120 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1121 // because it schedules an alarm too. Prevent it from doing so.
1122 spyOn(mQuotaController);
1123 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1124
1125 // Rare window size is 24 hours.
1126 final int standbyBucket = RARE_INDEX;
1127
1128 // No sessions saved yet.
1129 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1130 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1131
1132 // Test with timing sessions out of window.
1133 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1134 mQuotaController.saveTimingSession(0, "com.android.test",
1135 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
1136 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1137 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1138
1139 // Test with timing sessions in window but still in quota.
1140 final long start = now - (6 * HOUR_IN_MILLIS);
1141 // Counting backwards, the first minute in the session is over the allowed time, so it
1142 // needs to be excluded.
1143 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001144 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
1145 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001146 mQuotaController.saveTimingSession(0, "com.android.test",
1147 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
1148 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1149 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1150
1151 // Add some more sessions, but still in quota.
1152 mQuotaController.saveTimingSession(0, "com.android.test",
1153 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1154 mQuotaController.saveTimingSession(0, "com.android.test",
1155 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
1156 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1157 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1158
1159 // Test when out of quota.
1160 mQuotaController.saveTimingSession(0, "com.android.test",
1161 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1));
1162 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1163 verify(mAlarmManager, times(1))
1164 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1165
1166 // Alarm already scheduled, so make sure it's not scheduled again.
1167 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1168 verify(mAlarmManager, times(1))
1169 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1170 }
1171
1172 /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
1173 @Test
1174 public void testMaybeScheduleStartAlarmLocked_BucketChange() {
1175 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1176 // because it schedules an alarm too. Prevent it from doing so.
1177 spyOn(mQuotaController);
1178 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1179
1180 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1181
1182 // Affects rare bucket
1183 mQuotaController.saveTimingSession(0, "com.android.test",
1184 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3));
1185 // Affects frequent and rare buckets
1186 mQuotaController.saveTimingSession(0, "com.android.test",
1187 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
1188 // Affects working, frequent, and rare buckets
1189 final long outOfQuotaTime = now - HOUR_IN_MILLIS;
1190 mQuotaController.saveTimingSession(0, "com.android.test",
1191 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10));
1192 // Affects all buckets
1193 mQuotaController.saveTimingSession(0, "com.android.test",
1194 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3));
1195
1196 InOrder inOrder = inOrder(mAlarmManager);
1197
1198 // Start in ACTIVE bucket.
1199 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
Kweku Adamsbffea5a2018-12-13 22:13:28 -08001200 inOrder.verify(mAlarmManager, never())
1201 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001202 inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
1203
1204 // And down from there.
1205 final long expectedWorkingAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001206 outOfQuotaTime + (2 * HOUR_IN_MILLIS)
1207 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001208 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
1209 inOrder.verify(mAlarmManager, times(1))
1210 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1211
1212 final long expectedFrequentAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001213 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
1214 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001215 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
1216 inOrder.verify(mAlarmManager, times(1))
1217 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1218
1219 final long expectedRareAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001220 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
1221 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001222 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
1223 inOrder.verify(mAlarmManager, times(1))
1224 .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1225
1226 // And back up again.
1227 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
1228 inOrder.verify(mAlarmManager, times(1))
1229 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1230
1231 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
1232 inOrder.verify(mAlarmManager, times(1))
1233 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1234
1235 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
Kweku Adams288e73b2019-01-17 13:53:24 -08001236 inOrder.verify(mAlarmManager, never())
1237 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001238 inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
1239 }
1240
Kweku Adams288e73b2019-01-17 13:53:24 -08001241 @Test
1242 public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
1243 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1244 final int standbyBucket = WORKING_INDEX;
1245 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1246 SOURCE_PACKAGE, standbyBucket);
1247 stats.jobCountInAllowedTime =
1248 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
1249
1250 // Invalid time in the past, so the count shouldn't be used.
1251 stats.jobCountExpirationTimeElapsed =
1252 now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
1253 mQuotaController.maybeScheduleStartAlarmLocked(
1254 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
1255 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1256
1257 // Invalid time in the future, so the count should be used.
1258 stats.jobCountExpirationTimeElapsed =
1259 now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
1260 final long expectedWorkingAlarmTime =
1261 stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
1262 mQuotaController.maybeScheduleStartAlarmLocked(
1263 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
1264 verify(mAlarmManager, times(1))
1265 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1266 }
Kweku Adams045fb5722018-12-11 14:29:10 -08001267
1268 /**
1269 * Tests that the start alarm is properly rescheduled if the earliest session that contributes
1270 * to the app being out of quota contributes less than the quota buffer time.
1271 */
1272 @Test
1273 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() {
1274 // Use the default values
1275 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1276 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1277 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1278 }
1279
1280 @Test
1281 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
1282 // Make sure any new value is used correctly.
1283 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
1284 mQuotaController.onConstantsUpdatedLocked();
1285 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1286 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1287 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1288 }
1289
1290 @Test
1291 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
1292 // Make sure any new value is used correctly.
1293 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
1294 mQuotaController.onConstantsUpdatedLocked();
1295 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1296 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1297 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1298 }
1299
1300 @Test
1301 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
1302 // Make sure any new value is used correctly.
1303 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
1304 mQuotaController.onConstantsUpdatedLocked();
1305 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1306 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1307 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1308 }
1309
1310 @Test
1311 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
1312 // Make sure any new value is used correctly.
1313 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
1314 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
1315 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
1316 mQuotaController.onConstantsUpdatedLocked();
1317 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1318 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1319 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1320 }
1321
1322 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() {
1323 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1324 // because it schedules an alarm too. Prevent it from doing so.
1325 spyOn(mQuotaController);
1326 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1327
1328 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1329 // Working set window size is 2 hours.
1330 final int standbyBucket = WORKING_INDEX;
1331 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1332 final long remainingTimeMs =
1333 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
1334
1335 // Session straddles edge of bucket window. Only the contribution should be counted towards
1336 // the quota.
1337 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1338 createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1339 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1340 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1341 createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2));
1342 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1343 // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1344 final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
1345 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1346 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1347 standbyBucket);
1348 verify(mAlarmManager, times(1))
1349 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1350 }
1351
1352
1353 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
1354 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1355 // because it schedules an alarm too. Prevent it from doing so.
1356 spyOn(mQuotaController);
1357 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1358
1359 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1360 // Working set window size is 2 hours.
1361 final int standbyBucket = WORKING_INDEX;
1362 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1363 final long remainingTimeMs =
1364 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - contributionMs;
1365
1366 // Session straddles edge of 24 hour window. Only the contribution should be counted towards
1367 // the quota.
1368 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1369 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1370 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1371 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1372 createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300));
1373 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1374 // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1375 final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS
1376 //+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS
1377 + 24 * HOUR_IN_MILLIS
1378 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1379 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1380 standbyBucket);
1381 verify(mAlarmManager, times(1))
1382 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1383 }
1384
Kweku Adams4836f9d2018-11-12 17:04:17 -08001385 /** Tests that QuotaController doesn't throttle if throttling is turned off. */
1386 @Test
1387 public void testThrottleToggling() throws Exception {
1388 setDischarging();
1389 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1390 createTimingSession(
1391 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1392 10 * MINUTE_IN_MILLIS, 4));
1393 JobStatus jobStatus = createJobStatus("testThrottleToggling", 1);
1394 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
1395 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1396 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1397
1398 mConstants.USE_HEARTBEATS = true;
1399 mQuotaController.onConstantsUpdatedLocked();
1400 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1401 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1402
1403 mConstants.USE_HEARTBEATS = false;
1404 mQuotaController.onConstantsUpdatedLocked();
1405 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1406 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1407 }
1408
1409 @Test
1410 public void testConstantsUpdating_ValidValues() {
1411 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS;
1412 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 2 * MINUTE_IN_MILLIS;
1413 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 15 * MINUTE_IN_MILLIS;
1414 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
1415 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
1416 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001417 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -08001418 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000;
1419 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000;
1420 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000;
1421 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000;
1422 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001423
1424 mQuotaController.onConstantsUpdatedLocked();
1425
1426 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1427 assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1428 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1429 assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1430 assertEquals(45 * MINUTE_IN_MILLIS,
1431 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1432 assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001433 assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams288e73b2019-01-17 13:53:24 -08001434 assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
1435 assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
1436 assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
1437 assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
1438 assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001439 }
1440
1441 @Test
1442 public void testConstantsUpdating_InvalidValues() {
Kweku Adams288e73b2019-01-17 13:53:24 -08001443 // Test negatives/too low.
Kweku Adams4836f9d2018-11-12 17:04:17 -08001444 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
1445 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
1446 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
1447 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
1448 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
1449 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001450 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -08001451 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1;
1452 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1;
1453 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1;
1454 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1;
1455 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001456
1457 mQuotaController.onConstantsUpdatedLocked();
1458
1459 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1460 assertEquals(0, mQuotaController.getInQuotaBufferMs());
1461 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1462 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1463 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1464 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001465 assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams288e73b2019-01-17 13:53:24 -08001466 assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
1467 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
1468 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
1469 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
1470 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001471
1472 // Test larger than a day. Controller should cap at one day.
1473 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
1474 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 25 * HOUR_IN_MILLIS;
1475 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 25 * HOUR_IN_MILLIS;
1476 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
1477 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
1478 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001479 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001480
1481 mQuotaController.onConstantsUpdatedLocked();
1482
1483 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1484 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1485 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1486 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1487 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1488 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001489 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001490 }
1491
1492 /** Tests that TimingSessions aren't saved when the device is charging. */
1493 @Test
1494 public void testTimerTracking_Charging() {
1495 setCharging();
1496
1497 JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1);
1498 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1499
1500 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1501
1502 mQuotaController.prepareForExecutionLocked(jobStatus);
1503 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1504 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1505 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1506 }
1507
1508 /** Tests that TimingSessions are saved properly when the device is discharging. */
1509 @Test
1510 public void testTimerTracking_Discharging() {
1511 setDischarging();
Kweku Adams288e73b2019-01-17 13:53:24 -08001512 setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001513
1514 JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
1515 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1516
1517 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1518
1519 List<TimingSession> expected = new ArrayList<>();
1520
1521 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1522 mQuotaController.prepareForExecutionLocked(jobStatus);
1523 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1524 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1525 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1526 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1527
1528 // Test overlapping jobs.
1529 JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2);
1530 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1531
1532 JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3);
1533 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1534
1535 advanceElapsedClock(SECOND_IN_MILLIS);
1536
1537 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1538 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1539 mQuotaController.prepareForExecutionLocked(jobStatus);
1540 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1541 mQuotaController.prepareForExecutionLocked(jobStatus2);
1542 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1543 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1544 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1545 mQuotaController.prepareForExecutionLocked(jobStatus3);
1546 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1547 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1548 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1549 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1550 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1551 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1552 }
1553
1554 /**
1555 * Tests that TimingSessions are saved properly when the device alternates between
1556 * charging and discharging.
1557 */
1558 @Test
1559 public void testTimerTracking_ChargingAndDischarging() {
Kweku Adams288e73b2019-01-17 13:53:24 -08001560 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1561
Kweku Adams4836f9d2018-11-12 17:04:17 -08001562 JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
1563 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1564 JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
1565 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1566 JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3);
1567 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1568 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1569 List<TimingSession> expected = new ArrayList<>();
1570
1571 // A job starting while charging. Only the portion that runs during the discharging period
1572 // should be counted.
1573 setCharging();
1574
1575 mQuotaController.prepareForExecutionLocked(jobStatus);
1576 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1577 setDischarging();
1578 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1579 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1580 mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
1581 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1582 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1583
1584 advanceElapsedClock(SECOND_IN_MILLIS);
1585
1586 // One job starts while discharging, spans a charging session, and ends after the charging
1587 // session. Only the portions during the discharging periods should be counted. This should
1588 // result in two TimingSessions. A second job starts while discharging and ends within the
1589 // charging session. Only the portion during the first discharging portion should be
1590 // counted. A third job starts and ends within the charging session. The third job
1591 // shouldn't be included in either job count.
1592 setDischarging();
1593 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1594 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1595 mQuotaController.prepareForExecutionLocked(jobStatus);
1596 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1597 mQuotaController.prepareForExecutionLocked(jobStatus2);
1598 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1599 setCharging();
1600 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
1601 mQuotaController.prepareForExecutionLocked(jobStatus3);
1602 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1603 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1604 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1605 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1606 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1607 setDischarging();
1608 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1609 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1610 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1611 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
1612 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1613
1614 // A job starting while discharging and ending while charging. Only the portion that runs
1615 // during the discharging period should be counted.
1616 setDischarging();
1617 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1618 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1619 mQuotaController.prepareForExecutionLocked(jobStatus2);
1620 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1621 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1622 setCharging();
1623 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1624 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1625 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1626 }
1627
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001628 /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
1629 @Test
1630 public void testTimerTracking_AllBackground() {
1631 setDischarging();
Kweku Adams288e73b2019-01-17 13:53:24 -08001632 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001633
1634 JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 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 // Test single job.
1642 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1643 mQuotaController.prepareForExecutionLocked(jobStatus);
1644 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1645 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1646 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1647 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1648
1649 // Test overlapping jobs.
1650 JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
1651 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1652
1653 JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
1654 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1655
1656 advanceElapsedClock(SECOND_IN_MILLIS);
1657
1658 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1659 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1660 mQuotaController.prepareForExecutionLocked(jobStatus);
1661 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1662 mQuotaController.prepareForExecutionLocked(jobStatus2);
1663 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1664 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1665 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1666 mQuotaController.prepareForExecutionLocked(jobStatus3);
1667 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1668 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1669 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1670 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1671 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1672 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1673 }
1674
1675 /** Tests that Timers don't count foreground jobs. */
1676 @Test
1677 public void testTimerTracking_AllForeground() {
1678 setDischarging();
1679
1680 JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001681 setProcessState(ActivityManager.PROCESS_STATE_TOP);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001682 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1683
1684 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1685
1686 mQuotaController.prepareForExecutionLocked(jobStatus);
1687 advanceElapsedClock(5 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001688 // Change to a state that should still be considered foreground.
1689 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1690 advanceElapsedClock(5 * SECOND_IN_MILLIS);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001691 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1692 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1693 }
1694
1695 /**
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001696 * Tests that Timers properly track sessions when switching between foreground and background
1697 * states.
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001698 */
1699 @Test
1700 public void testTimerTracking_ForegroundAndBackground() {
1701 setDischarging();
1702
1703 JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
1704 JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
1705 JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001706 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1707 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1708 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
1709 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1710 List<TimingSession> expected = new ArrayList<>();
1711
1712 // UID starts out inactive.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001713 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001714 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1715 mQuotaController.prepareForExecutionLocked(jobBg1);
1716 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1717 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1718 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1719 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1720
1721 advanceElapsedClock(SECOND_IN_MILLIS);
1722
1723 // Bg job starts while inactive, spans an entire active session, and ends after the
1724 // active session.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001725 // App switching to foreground state then fg job starts.
1726 // App remains in foreground state after coming to foreground, so there should only be one
1727 // session.
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001728 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1729 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1730 mQuotaController.prepareForExecutionLocked(jobBg2);
1731 advanceElapsedClock(10 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001732 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1733 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001734 mQuotaController.prepareForExecutionLocked(jobFg3);
1735 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1736 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1737 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1738 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001739 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1740
1741 advanceElapsedClock(SECOND_IN_MILLIS);
1742
1743 // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
1744 // "inactive" and then bg job 2 starts. Then fg job ends.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001745 // This should result in two TimingSessions:
1746 // * The first should have a count of 1
1747 // * The second should have a count of 2 since it will include both jobs
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001748 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1749 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1750 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1751 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001752 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001753 mQuotaController.prepareForExecutionLocked(jobBg1);
1754 advanceElapsedClock(10 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001755 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1756 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001757 mQuotaController.prepareForExecutionLocked(jobFg3);
1758 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1759 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001760 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
1761 start = JobSchedulerService.sElapsedRealtimeClock.millis();
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001762 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001763 mQuotaController.prepareForExecutionLocked(jobBg2);
1764 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1765 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1766 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1767 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001768 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001769 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1770 }
1771
Kweku Adams4836f9d2018-11-12 17:04:17 -08001772 /**
Kweku Adams288e73b2019-01-17 13:53:24 -08001773 * Tests that Timers don't track job counts while in the foreground.
1774 */
1775 @Test
1776 public void testTimerTracking_JobCount_Foreground() {
1777 setDischarging();
1778
1779 final int standbyBucket = ACTIVE_INDEX;
1780 JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1);
1781 JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2);
1782
1783 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
1784 mQuotaController.maybeStartTrackingJobLocked(jobFg2, null);
1785 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1786 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1787 SOURCE_PACKAGE, standbyBucket);
1788 assertEquals(0, stats.jobCountInAllowedTime);
1789
1790 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1791 mQuotaController.prepareForExecutionLocked(jobFg1);
1792 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1793 mQuotaController.prepareForExecutionLocked(jobFg2);
1794 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1795 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
1796 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1797 mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
1798 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1799
1800 assertEquals(0, stats.jobCountInAllowedTime);
1801 }
1802
1803 /**
1804 * Tests that Timers properly track job counts while in the background.
1805 */
1806 @Test
1807 public void testTimerTracking_JobCount_Background() {
1808 final int standbyBucket = WORKING_INDEX;
1809 JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1);
1810 JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2);
1811 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1812 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1813
1814 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1815 SOURCE_PACKAGE, standbyBucket);
1816 assertEquals(0, stats.jobCountInAllowedTime);
1817
1818 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
1819 mQuotaController.prepareForExecutionLocked(jobBg1);
1820 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1821 mQuotaController.prepareForExecutionLocked(jobBg2);
1822 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1823 mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
1824 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1825 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1826
1827 assertEquals(2, stats.jobCountInAllowedTime);
1828 }
1829
1830 /**
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001831 * Tests that Timers properly track overlapping top and background jobs.
1832 */
1833 @Test
1834 public void testTimerTracking_TopAndNonTop() {
1835 setDischarging();
1836
1837 JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1);
1838 JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2);
1839 JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3);
1840 JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4);
1841 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1842 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1843 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
1844 mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
1845 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1846 List<TimingSession> expected = new ArrayList<>();
1847
1848 // UID starts out inactive.
1849 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1850 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1851 mQuotaController.prepareForExecutionLocked(jobBg1);
1852 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1853 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1854 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1855 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1856
1857 advanceElapsedClock(SECOND_IN_MILLIS);
1858
1859 // Bg job starts while inactive, spans an entire active session, and ends after the
1860 // active session.
1861 // App switching to top state then fg job starts.
1862 // App remains in top state after coming to top, so there should only be one
1863 // session.
1864 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1865 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1866 mQuotaController.prepareForExecutionLocked(jobBg2);
1867 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1868 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1869 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1870 mQuotaController.prepareForExecutionLocked(jobTop);
1871 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1872 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
1873 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1874 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1875 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1876
1877 advanceElapsedClock(SECOND_IN_MILLIS);
1878
1879 // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
1880 // foreground_service and a new job starts. Shortly after, uid goes
1881 // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
1882 // This should result in two TimingSessions:
1883 // * The first should have a count of 1
1884 // * The second should have a count of 2, which accounts for the bg2 and fg, but not top
1885 // jobs.
1886 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1887 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1888 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1889 mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
1890 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
1891 mQuotaController.prepareForExecutionLocked(jobBg1);
1892 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1893 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1894 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1895 mQuotaController.prepareForExecutionLocked(jobTop);
1896 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1897 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1898 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1899 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1900 mQuotaController.prepareForExecutionLocked(jobFg1);
1901 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1902 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1903 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
1904 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1905 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
1906 mQuotaController.prepareForExecutionLocked(jobBg2);
1907 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1908 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
1909 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1910 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1911 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
1912 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
1913 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1914 }
1915
1916 /**
1917 * Tests that TOP jobs aren't stopped when an app runs out of quota.
1918 */
1919 @Test
1920 public void testTracking_OutOfQuota_ForegroundAndBackground() {
1921 setDischarging();
1922
1923 JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
1924 JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
1925 trackJobs(jobBg, jobTop);
1926 setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
1927 // Now the package only has 20 seconds to run.
1928 final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
1929 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1930 createTimingSession(
1931 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1932 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
1933
1934 InOrder inOrder = inOrder(mJobSchedulerService);
1935
1936 // UID starts out inactive.
1937 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
1938 // Start the job.
1939 mQuotaController.prepareForExecutionLocked(jobBg);
1940 advanceElapsedClock(remainingTimeMs / 2);
1941 // New job starts after UID is in the foreground. Since the app is now in the foreground, it
1942 // should continue to have remainingTimeMs / 2 time remaining.
1943 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1944 mQuotaController.prepareForExecutionLocked(jobTop);
1945 advanceElapsedClock(remainingTimeMs);
1946
1947 // Wait for some extra time to allow for job processing.
1948 inOrder.verify(mJobSchedulerService,
1949 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
1950 .onControllerStateChanged();
1951 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg));
1952 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop));
1953 // Go to a background state.
1954 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
1955 advanceElapsedClock(remainingTimeMs / 2 + 1);
1956 inOrder.verify(mJobSchedulerService,
1957 timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
1958 .onControllerStateChanged();
1959 // Top job should still be allowed to run.
1960 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1961 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1962
1963 // New jobs to run.
1964 JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
1965 JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
1966 JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
1967 setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
1968
1969 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1970 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1971 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
1972 .onControllerStateChanged();
1973 trackJobs(jobFg, jobTop);
1974 mQuotaController.prepareForExecutionLocked(jobTop);
1975 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1976 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1977 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1978
1979 // App still in foreground so everything should be in quota.
1980 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1981 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1982 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1983 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1984 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1985
1986 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1987 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
1988 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
1989 .onControllerStateChanged();
1990 // App is now in background and out of quota. Fg should now change to out of quota since it
1991 // wasn't started. Top should remain in quota since it started when the app was in TOP.
1992 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1993 assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1994 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1995 trackJobs(jobBg2);
1996 assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1997 }
1998
1999 /**
Kweku Adams4836f9d2018-11-12 17:04:17 -08002000 * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
2001 * its quota.
2002 */
2003 @Test
2004 public void testTracking_OutOfQuota() {
2005 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
2006 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2007 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
Kweku Adams288e73b2019-01-17 13:53:24 -08002008 setProcessState(ActivityManager.PROCESS_STATE_HOME);
Kweku Adams4836f9d2018-11-12 17:04:17 -08002009 // Now the package only has two seconds to run.
2010 final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
2011 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2012 createTimingSession(
2013 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
2014 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
2015
2016 // Start the job.
2017 mQuotaController.prepareForExecutionLocked(jobStatus);
2018 advanceElapsedClock(remainingTimeMs);
2019
2020 // Wait for some extra time to allow for job processing.
2021 verify(mJobSchedulerService,
2022 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
2023 .onControllerStateChanged();
2024 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2025 }
2026
2027 /**
2028 * Tests that a job is properly handled when it's at the edge of its quota and the old quota is
2029 * being phased out.
2030 */
2031 @Test
2032 public void testTracking_RollingQuota() {
2033 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
2034 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
2035 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
Kweku Adams288e73b2019-01-17 13:53:24 -08002036 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
Kweku Adams4836f9d2018-11-12 17:04:17 -08002037 Handler handler = mQuotaController.getHandler();
2038 spyOn(handler);
2039
2040 long now = JobSchedulerService.sElapsedRealtimeClock.millis();
2041 final long remainingTimeMs = SECOND_IN_MILLIS;
2042 // The package only has one second to run, but this session is at the edge of the rolling
2043 // window, so as the package "reaches its quota" it will have more to keep running.
2044 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2045 createTimingSession(now - 2 * HOUR_IN_MILLIS,
Kweku Adamse3e16402019-03-26 15:48:51 -07002046 10 * SECOND_IN_MILLIS - remainingTimeMs, 1));
2047 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
2048 createTimingSession(now - HOUR_IN_MILLIS,
2049 9 * MINUTE_IN_MILLIS + 50 * SECOND_IN_MILLIS, 1));
Kweku Adams4836f9d2018-11-12 17:04:17 -08002050
2051 assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
2052 // Start the job.
2053 mQuotaController.prepareForExecutionLocked(jobStatus);
2054 advanceElapsedClock(remainingTimeMs);
2055
2056 // Wait for some extra time to allow for job processing.
2057 verify(mJobSchedulerService,
2058 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
2059 .onControllerStateChanged();
2060 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2061 // The job used up the remaining quota, but in that time, the same amount of time in the
2062 // old TimingSession also fell out of the quota window, so it should still have the same
2063 // amount of remaining time left its quota.
2064 assertEquals(remainingTimeMs,
2065 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
Kweku Adamse3e16402019-03-26 15:48:51 -07002066 // Handler is told to check when the quota will be consumed, not when the initial
2067 // remaining time is over.
2068 verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(10 * SECOND_IN_MILLIS));
2069 verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs));
2070
2071 // After 10 seconds, the job should finally be out of quota.
2072 advanceElapsedClock(10 * SECOND_IN_MILLIS - remainingTimeMs);
2073 // Wait for some extra time to allow for job processing.
2074 verify(mJobSchedulerService,
2075 timeout(12 * SECOND_IN_MILLIS).times(1))
2076 .onControllerStateChanged();
2077 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
2078 verify(handler, never()).sendMessageDelayed(any(), anyInt());
Kweku Adams4836f9d2018-11-12 17:04:17 -08002079 }
2080}