blob: 8bbcd6fa209a62643aba1465453d70d17b7922d6 [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;
28import static com.android.server.job.JobSchedulerService.RARE_INDEX;
29import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
30
31import static org.junit.Assert.assertEquals;
32import static org.junit.Assert.assertFalse;
Kweku Adams045fb5722018-12-11 14:29:10 -080033import static org.junit.Assert.assertNotEquals;
Kweku Adams4836f9d2018-11-12 17:04:17 -080034import static org.junit.Assert.assertNull;
35import static org.junit.Assert.assertTrue;
36import static org.mockito.ArgumentMatchers.any;
37import static org.mockito.ArgumentMatchers.anyInt;
38import static org.mockito.ArgumentMatchers.anyLong;
39import static org.mockito.Mockito.atLeast;
40import static org.mockito.Mockito.eq;
41import static org.mockito.Mockito.never;
42import static org.mockito.Mockito.timeout;
43import static org.mockito.Mockito.times;
44import static org.mockito.Mockito.verify;
45
46import android.app.AlarmManager;
47import android.app.job.JobInfo;
48import android.app.usage.UsageStatsManager;
49import android.app.usage.UsageStatsManagerInternal;
50import android.content.BroadcastReceiver;
51import android.content.ComponentName;
52import android.content.Context;
53import android.content.Intent;
54import android.content.pm.PackageManagerInternal;
55import android.os.BatteryManager;
56import android.os.BatteryManagerInternal;
57import android.os.Handler;
58import android.os.Looper;
59import android.os.SystemClock;
60
61import androidx.test.runner.AndroidJUnit4;
62
63import com.android.server.LocalServices;
64import com.android.server.job.JobSchedulerService;
65import com.android.server.job.JobSchedulerService.Constants;
Kweku Adams045fb5722018-12-11 14:29:10 -080066import com.android.server.job.controllers.QuotaController.ExecutionStats;
Kweku Adams4836f9d2018-11-12 17:04:17 -080067import com.android.server.job.controllers.QuotaController.TimingSession;
68
69import org.junit.After;
70import org.junit.Before;
71import org.junit.Test;
72import org.junit.runner.RunWith;
73import org.mockito.ArgumentCaptor;
74import org.mockito.InOrder;
75import org.mockito.Mock;
76import org.mockito.MockitoSession;
77import org.mockito.quality.Strictness;
78
79import java.time.Clock;
80import java.time.Duration;
81import java.time.ZoneOffset;
82import java.util.ArrayList;
83import java.util.List;
84
85@RunWith(AndroidJUnit4.class)
86public class QuotaControllerTest {
87 private static final long SECOND_IN_MILLIS = 1000L;
88 private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
89 private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
90 private static final String TAG_CLEANUP = "*job.cleanup*";
91 private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
Kweku Adams4836f9d2018-11-12 17:04:17 -080092 private static final int CALLING_UID = 1000;
93 private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
94 private static final int SOURCE_USER_ID = 0;
95
96 private BroadcastReceiver mChargingReceiver;
97 private Constants mConstants;
98 private QuotaController mQuotaController;
99
100 private MockitoSession mMockingSession;
101 @Mock
102 private AlarmManager mAlarmManager;
103 @Mock
104 private Context mContext;
105 @Mock
106 private JobSchedulerService mJobSchedulerService;
107 @Mock
108 private UsageStatsManagerInternal mUsageStatsManager;
109
110 @Before
111 public void setUp() {
112 mMockingSession = mockitoSession()
113 .initMocks(this)
114 .strictness(Strictness.LENIENT)
115 .mockStatic(LocalServices.class)
116 .startMocking();
117 // Make sure constants turn on QuotaController.
118 mConstants = new Constants();
119 mConstants.USE_HEARTBEATS = false;
120
121 // Called in StateController constructor.
122 when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
123 when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
124 when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
125 // Called in QuotaController constructor.
126 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
127 when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
128 doReturn(mock(BatteryManagerInternal.class))
129 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
130 doReturn(mUsageStatsManager)
131 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
132 // Used in JobStatus.
133 doReturn(mock(PackageManagerInternal.class))
134 .when(() -> LocalServices.getService(PackageManagerInternal.class));
135
Kweku Adams045fb5722018-12-11 14:29:10 -0800136 // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
137 // in the past, and QuotaController sometimes floors values at 0, so if the test time
138 // causes sessions with negative timestamps, they will fail.
Kweku Adams4836f9d2018-11-12 17:04:17 -0800139 JobSchedulerService.sSystemClock =
Kweku Adams045fb5722018-12-11 14:29:10 -0800140 getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
141 24 * HOUR_IN_MILLIS);
142 JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
143 Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC),
144 24 * HOUR_IN_MILLIS);
145 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
146 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
147 24 * HOUR_IN_MILLIS);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800148
149 // Initialize real objects.
150 // Capture the listeners.
151 ArgumentCaptor<BroadcastReceiver> receiverCaptor =
152 ArgumentCaptor.forClass(BroadcastReceiver.class);
153 mQuotaController = new QuotaController(mJobSchedulerService);
154
155 verify(mContext).registerReceiver(receiverCaptor.capture(), any());
156 mChargingReceiver = receiverCaptor.getValue();
157 }
158
159 @After
160 public void tearDown() {
161 if (mMockingSession != null) {
162 mMockingSession.finishMocking();
163 }
164 }
165
166 private Clock getAdvancedClock(Clock clock, long incrementMs) {
167 return Clock.offset(clock, Duration.ofMillis(incrementMs));
168 }
169
170 private void advanceElapsedClock(long incrementMs) {
171 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
172 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
173 }
174
175 private void setCharging() {
176 Intent intent = new Intent(BatteryManager.ACTION_CHARGING);
177 mChargingReceiver.onReceive(mContext, intent);
178 }
179
180 private void setDischarging() {
181 Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING);
182 mChargingReceiver.onReceive(mContext, intent);
183 }
184
185 private void setStandbyBucket(int bucketIndex) {
186 int bucket;
187 switch (bucketIndex) {
188 case ACTIVE_INDEX:
189 bucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
190 break;
191 case WORKING_INDEX:
192 bucket = UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
193 break;
194 case FREQUENT_INDEX:
195 bucket = UsageStatsManager.STANDBY_BUCKET_FREQUENT;
196 break;
197 case RARE_INDEX:
198 bucket = UsageStatsManager.STANDBY_BUCKET_RARE;
199 break;
200 default:
201 bucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
202 }
203 when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
204 anyLong())).thenReturn(bucket);
205 }
206
207 private void setStandbyBucket(int bucketIndex, JobStatus job) {
208 setStandbyBucket(bucketIndex);
209 job.setStandbyBucket(bucketIndex);
210 }
211
212 private JobStatus createJobStatus(String testTag, int jobId) {
213 JobInfo jobInfo = new JobInfo.Builder(jobId,
214 new ComponentName(mContext, "TestQuotaJobService"))
215 .setMinimumLatency(Math.abs(jobId) + 1)
216 .build();
217 return JobStatus.createFromJobInfo(
218 jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
219 }
220
221 private TimingSession createTimingSession(long start, long duration, int count) {
222 return new TimingSession(start, start + duration, count);
223 }
224
225 @Test
226 public void testSaveTimingSession() {
227 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
228
229 List<TimingSession> expected = new ArrayList<>();
230 TimingSession one = new TimingSession(1, 10, 1);
231 TimingSession two = new TimingSession(11, 20, 2);
232 TimingSession thr = new TimingSession(21, 30, 3);
233
234 mQuotaController.saveTimingSession(0, "com.android.test", one);
235 expected.add(one);
236 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
237
238 mQuotaController.saveTimingSession(0, "com.android.test", two);
239 expected.add(two);
240 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
241
242 mQuotaController.saveTimingSession(0, "com.android.test", thr);
243 expected.add(thr);
244 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
245 }
246
247 @Test
248 public void testDeleteObsoleteSessionsLocked() {
249 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
250 TimingSession one = createTimingSession(
251 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
252 TimingSession two = createTimingSession(
253 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
254 TimingSession thr = createTimingSession(
255 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
256 // Overlaps 24 hour boundary.
257 TimingSession fou = createTimingSession(
258 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
259 // Way past the 24 hour boundary.
260 TimingSession fiv = createTimingSession(
261 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
262 List<TimingSession> expected = new ArrayList<>();
263 // Added in correct (chronological) order.
264 expected.add(fou);
265 expected.add(thr);
266 expected.add(two);
267 expected.add(one);
268 mQuotaController.saveTimingSession(0, "com.android.test", fiv);
269 mQuotaController.saveTimingSession(0, "com.android.test", fou);
270 mQuotaController.saveTimingSession(0, "com.android.test", thr);
271 mQuotaController.saveTimingSession(0, "com.android.test", two);
272 mQuotaController.saveTimingSession(0, "com.android.test", one);
273
274 mQuotaController.deleteObsoleteSessionsLocked();
275
276 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
277 }
278
279 @Test
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800280 public void testOnAppRemovedLocked() {
281 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
282 mQuotaController.saveTimingSession(0, "com.android.test.remove",
283 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
284 mQuotaController.saveTimingSession(0, "com.android.test.remove",
285 createTimingSession(
286 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
287 mQuotaController.saveTimingSession(0, "com.android.test.remove",
288 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
289 // Test that another app isn't affected.
290 TimingSession one = createTimingSession(
291 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
292 TimingSession two = createTimingSession(
293 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
294 List<TimingSession> expected = new ArrayList<>();
295 // Added in correct (chronological) order.
296 expected.add(two);
297 expected.add(one);
298 mQuotaController.saveTimingSession(0, "com.android.test.stay", two);
299 mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
300
Kweku Adams045fb5722018-12-11 14:29:10 -0800301 ExecutionStats expectedStats = new ExecutionStats();
302 expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
303 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
304
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800305 mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001);
306 assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
307 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800308 assertEquals(expectedStats,
309 mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
310 assertNotEquals(expectedStats,
311 mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800312 }
313
314 @Test
315 public void testOnUserRemovedLocked() {
316 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
317 mQuotaController.saveTimingSession(0, "com.android.test",
318 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
319 mQuotaController.saveTimingSession(0, "com.android.test",
320 createTimingSession(
321 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
322 mQuotaController.saveTimingSession(0, "com.android.test",
323 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
324 // Test that another user isn't affected.
325 TimingSession one = createTimingSession(
326 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
327 TimingSession two = createTimingSession(
328 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
329 List<TimingSession> expected = new ArrayList<>();
330 // Added in correct (chronological) order.
331 expected.add(two);
332 expected.add(one);
333 mQuotaController.saveTimingSession(10, "com.android.test", two);
334 mQuotaController.saveTimingSession(10, "com.android.test", one);
335
Kweku Adams045fb5722018-12-11 14:29:10 -0800336 ExecutionStats expectedStats = new ExecutionStats();
337 expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
338 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
339
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800340 mQuotaController.onUserRemovedLocked(0);
341 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
342 assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800343 assertEquals(expectedStats,
344 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
345 assertNotEquals(expectedStats,
346 mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800347 }
348
349 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -0800350 public void testUpdateExecutionStatsLocked_NoTimer() {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800351 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
352 // Added in chronological order.
353 mQuotaController.saveTimingSession(0, "com.android.test",
354 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
355 mQuotaController.saveTimingSession(0, "com.android.test",
356 createTimingSession(
357 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
358 mQuotaController.saveTimingSession(0, "com.android.test",
359 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
360 mQuotaController.saveTimingSession(0, "com.android.test",
361 createTimingSession(
362 now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1));
363 mQuotaController.saveTimingSession(0, "com.android.test",
364 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
365
Kweku Adams045fb5722018-12-11 14:29:10 -0800366 // Test an app that hasn't had any activity.
367 ExecutionStats expectedStats = new ExecutionStats();
368 ExecutionStats inputStats = new ExecutionStats();
369
370 inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
371 // Invalid time is now +24 hours since there are no sessions at all for the app.
372 expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
373 mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
374 assertEquals(expectedStats, inputStats);
375
376 inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
377 // Invalid time is now +18 hours since there are no sessions in the window but the earliest
378 // session is 6 hours ago.
379 expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS;
380 expectedStats.executionTimeInWindowMs = 0;
381 expectedStats.bgJobCountInWindow = 0;
382 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
383 expectedStats.bgJobCountInMaxPeriod = 15;
384 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
385 assertEquals(expectedStats, inputStats);
386
387 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
388 // Invalid time is now since the session straddles the window cutoff time.
389 expectedStats.invalidTimeElapsed = now;
390 expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
391 expectedStats.bgJobCountInWindow = 3;
392 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
393 expectedStats.bgJobCountInMaxPeriod = 15;
394 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
395 assertEquals(expectedStats, inputStats);
396
397 inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
398 // Invalid time is now since the start of the session is at the very edge of the window
399 // cutoff time.
400 expectedStats.invalidTimeElapsed = now;
401 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
402 expectedStats.bgJobCountInWindow = 3;
403 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
404 expectedStats.bgJobCountInMaxPeriod = 15;
405 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
406 assertEquals(expectedStats, inputStats);
407
408 inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
409 // Invalid time is now +44 minutes since the earliest session in the window is now-5
410 // minutes.
411 expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
412 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
413 expectedStats.bgJobCountInWindow = 3;
414 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
415 expectedStats.bgJobCountInMaxPeriod = 15;
416 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
417 assertEquals(expectedStats, inputStats);
418
419 inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
420 // Invalid time is now since the session is at the very edge of the window cutoff time.
421 expectedStats.invalidTimeElapsed = now;
422 expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
423 expectedStats.bgJobCountInWindow = 4;
424 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
425 expectedStats.bgJobCountInMaxPeriod = 15;
426 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
427 assertEquals(expectedStats, inputStats);
428
429 inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
430 // Invalid time is now since the start of the session is at the very edge of the window
431 // cutoff time.
432 expectedStats.invalidTimeElapsed = now;
433 expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
434 expectedStats.bgJobCountInWindow = 5;
435 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
436 expectedStats.bgJobCountInMaxPeriod = 15;
437 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
438 assertEquals(expectedStats, inputStats);
439
440 inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
441 // Invalid time is now since the session straddles the window cutoff time.
442 expectedStats.invalidTimeElapsed = now;
443 expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
444 expectedStats.bgJobCountInWindow = 10;
445 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
446 expectedStats.bgJobCountInMaxPeriod = 15;
447 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
448 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
449 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
450 assertEquals(expectedStats, inputStats);
451
452 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
453 // Invalid time is now +59 minutes since the earliest session in the window is now-121
454 // minutes.
455 expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
456 expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
457 expectedStats.bgJobCountInWindow = 10;
458 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
459 expectedStats.bgJobCountInMaxPeriod = 15;
460 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
461 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
462 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
463 assertEquals(expectedStats, inputStats);
464
465 inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
466 // Invalid time is now since the start of the session is at the very edge of the window
467 // cutoff time.
468 expectedStats.invalidTimeElapsed = now;
469 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
470 expectedStats.bgJobCountInWindow = 15;
471 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
472 expectedStats.bgJobCountInMaxPeriod = 15;
473 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
474 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
475 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
476 assertEquals(expectedStats, inputStats);
477
478 // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period.
479 mQuotaController.getTimingSessions(0, "com.android.test")
480 .add(0,
481 createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
482 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
483 // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
484 // before the end of the max period cutoff time.
485 expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
486 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
487 expectedStats.bgJobCountInWindow = 15;
488 expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
489 expectedStats.bgJobCountInMaxPeriod = 18;
490 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
491 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
492 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
493 assertEquals(expectedStats, inputStats);
494
495 mQuotaController.getTimingSessions(0, "com.android.test")
496 .add(0,
497 createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
498 2 * MINUTE_IN_MILLIS, 2));
499 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
500 // Invalid time is now since the earlist session straddles the max period cutoff time.
501 expectedStats.invalidTimeElapsed = now;
502 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
503 expectedStats.bgJobCountInWindow = 15;
504 expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
505 expectedStats.bgJobCountInMaxPeriod = 20;
506 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
507 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
508 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
509 assertEquals(expectedStats, inputStats);
510 }
511
512 /**
513 * Tests that getExecutionStatsLocked returns the correct stats.
514 */
515 @Test
516 public void testGetExecutionStatsLocked_Values() {
517 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
518 mQuotaController.saveTimingSession(0, "com.android.test",
519 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
520 mQuotaController.saveTimingSession(0, "com.android.test",
521 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
522 mQuotaController.saveTimingSession(0, "com.android.test",
523 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
524 mQuotaController.saveTimingSession(0, "com.android.test",
525 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
526
527 ExecutionStats expectedStats = new ExecutionStats();
528
529 // Active
530 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
531 expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
532 expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
533 expectedStats.bgJobCountInWindow = 5;
534 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
535 expectedStats.bgJobCountInMaxPeriod = 20;
536 assertEquals(expectedStats,
537 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
538
539 // Working
540 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
541 expectedStats.invalidTimeElapsed = now;
542 expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
543 expectedStats.bgJobCountInWindow = 10;
544 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
545 expectedStats.bgJobCountInMaxPeriod = 20;
546 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
547 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
548 assertEquals(expectedStats,
549 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
550
551 // Frequent
552 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
553 expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
554 expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
555 expectedStats.bgJobCountInWindow = 15;
556 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
557 expectedStats.bgJobCountInMaxPeriod = 20;
558 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
559 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
560 assertEquals(expectedStats,
561 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
562
563 // Rare
564 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
565 expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
566 expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
567 expectedStats.bgJobCountInWindow = 20;
568 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
569 expectedStats.bgJobCountInMaxPeriod = 20;
570 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
571 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
572 assertEquals(expectedStats,
573 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
574 }
575
576 /**
577 * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
578 */
579 @Test
580 public void testGetExecutionStatsLocked_Caching() {
581 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
582 mQuotaController.saveTimingSession(0, "com.android.test",
583 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
584 mQuotaController.saveTimingSession(0, "com.android.test",
585 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
586 mQuotaController.saveTimingSession(0, "com.android.test",
587 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
588 mQuotaController.saveTimingSession(0, "com.android.test",
589 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
590 final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0,
591 "com.android.test", ACTIVE_INDEX);
592 final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0,
593 "com.android.test", WORKING_INDEX);
594 final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
595 "com.android.test", FREQUENT_INDEX);
596 final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0,
597 "com.android.test", RARE_INDEX);
598
599 // Advance clock so that the working stats shouldn't be the same.
600 advanceElapsedClock(MINUTE_IN_MILLIS);
601 // Change frequent bucket size so that the stats need to be recalculated.
602 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS;
603 mQuotaController.onConstantsUpdatedLocked();
604
605 ExecutionStats expectedStats = new ExecutionStats();
606 expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
607 expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed;
608 expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
609 expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
610 expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
611 expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
612 expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
613 final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
614 "com.android.test", ACTIVE_INDEX);
615 // Stats for the same bucket should use the same object.
616 assertTrue(originalStatsActive == newStatsActive);
617 assertEquals(expectedStats, newStatsActive);
618
619 expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
620 expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed;
621 expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
622 expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
623 expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
624 final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
625 "com.android.test", WORKING_INDEX);
626 assertTrue(originalStatsWorking == newStatsWorking);
627 assertNotEquals(expectedStats, newStatsWorking);
628
629 expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
630 expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed;
631 expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
632 expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
633 expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
634 final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
635 "com.android.test", FREQUENT_INDEX);
636 assertTrue(originalStatsFrequent == newStatsFrequent);
637 assertNotEquals(expectedStats, newStatsFrequent);
638
639 expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
640 expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed;
641 expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
642 expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
643 expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
644 final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
645 "com.android.test", RARE_INDEX);
646 assertTrue(originalStatsRare == newStatsRare);
647 assertEquals(expectedStats, newStatsRare);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800648 }
649
650 @Test
651 public void testMaybeScheduleCleanupAlarmLocked() {
652 // No sessions saved yet.
653 mQuotaController.maybeScheduleCleanupAlarmLocked();
654 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
655
656 // Test with only one timing session saved.
657 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
658 final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
659 mQuotaController.saveTimingSession(0, "com.android.test",
660 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1));
661 mQuotaController.maybeScheduleCleanupAlarmLocked();
662 verify(mAlarmManager, times(1))
663 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
664
665 // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
666 mQuotaController.saveTimingSession(0, "com.android.test",
667 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
668 mQuotaController.saveTimingSession(0, "com.android.test",
669 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
670 mQuotaController.maybeScheduleCleanupAlarmLocked();
671 verify(mAlarmManager, times(1))
672 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
673 }
674
675 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -0800676 public void testMaybeScheduleStartAlarmLocked_Active() {
677 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
678 // because it schedules an alarm too. Prevent it from doing so.
679 spyOn(mQuotaController);
680 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
681
682 // Active window size is 10 minutes.
683 final int standbyBucket = ACTIVE_INDEX;
684
685 // No sessions saved yet.
686 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
687 standbyBucket);
688 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
689
690 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
691 // Test with timing sessions out of window but still under max execution limit.
692 final long expectedAlarmTime =
693 (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS
694 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
695 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
696 createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
697 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
698 createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
699 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
700 createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
701 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
702 standbyBucket);
703 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
704
705 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
706 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1));
707 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
708 standbyBucket);
709 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
710
711 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
712 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
713 mQuotaController.prepareForExecutionLocked(jobStatus);
714 advanceElapsedClock(5 * MINUTE_IN_MILLIS);
715 // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window
716 // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no
717 // longer has quota.
718 assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
719 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
720 standbyBucket);
721 verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK),
722 any(), any());
723 }
724
725 @Test
Kweku Adams4836f9d2018-11-12 17:04:17 -0800726 public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
727 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
728 // because it schedules an alarm too. Prevent it from doing so.
729 spyOn(mQuotaController);
730 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
731
732 // Working set window size is 2 hours.
733 final int standbyBucket = WORKING_INDEX;
734
735 // No sessions saved yet.
736 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
737 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
738
739 // Test with timing sessions out of window.
740 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
741 mQuotaController.saveTimingSession(0, "com.android.test",
742 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
743 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
744 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
745
746 // Test with timing sessions in window but still in quota.
747 final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
748 // Counting backwards, the quota will come back one minute before the end.
749 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800750 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS
751 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800752 mQuotaController.saveTimingSession(0, "com.android.test",
753 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1));
754 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
755 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
756
757 // Add some more sessions, but still in quota.
758 mQuotaController.saveTimingSession(0, "com.android.test",
759 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
760 mQuotaController.saveTimingSession(0, "com.android.test",
761 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1));
762 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
763 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
764
765 // Test when out of quota.
766 mQuotaController.saveTimingSession(0, "com.android.test",
767 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
768 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
769 verify(mAlarmManager, times(1))
770 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
771
772 // Alarm already scheduled, so make sure it's not scheduled again.
773 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
774 verify(mAlarmManager, times(1))
775 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
776 }
777
778 @Test
779 public void testMaybeScheduleStartAlarmLocked_Frequent() {
780 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
781 // because it schedules an alarm too. Prevent it from doing so.
782 spyOn(mQuotaController);
783 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
784
785 // Frequent window size is 8 hours.
786 final int standbyBucket = FREQUENT_INDEX;
787
788 // No sessions saved yet.
789 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
790 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
791
792 // Test with timing sessions out of window.
793 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
794 mQuotaController.saveTimingSession(0, "com.android.test",
795 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
796 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
797 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
798
799 // Test with timing sessions in window but still in quota.
800 final long start = now - (6 * HOUR_IN_MILLIS);
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800801 final long expectedAlarmTime =
802 start + 8 * HOUR_IN_MILLIS + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800803 mQuotaController.saveTimingSession(0, "com.android.test",
804 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
805 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
806 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
807
808 // Add some more sessions, but still in quota.
809 mQuotaController.saveTimingSession(0, "com.android.test",
810 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
811 mQuotaController.saveTimingSession(0, "com.android.test",
812 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
813 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
814 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
815
816 // Test when out of quota.
817 mQuotaController.saveTimingSession(0, "com.android.test",
818 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
819 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
820 verify(mAlarmManager, times(1))
821 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
822
823 // Alarm already scheduled, so make sure it's not scheduled again.
824 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
825 verify(mAlarmManager, times(1))
826 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
827 }
828
829 @Test
830 public void testMaybeScheduleStartAlarmLocked_Rare() {
831 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
832 // because it schedules an alarm too. Prevent it from doing so.
833 spyOn(mQuotaController);
834 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
835
836 // Rare window size is 24 hours.
837 final int standbyBucket = RARE_INDEX;
838
839 // No sessions saved yet.
840 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
841 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
842
843 // Test with timing sessions out of window.
844 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
845 mQuotaController.saveTimingSession(0, "com.android.test",
846 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
847 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
848 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
849
850 // Test with timing sessions in window but still in quota.
851 final long start = now - (6 * HOUR_IN_MILLIS);
852 // Counting backwards, the first minute in the session is over the allowed time, so it
853 // needs to be excluded.
854 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800855 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
856 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800857 mQuotaController.saveTimingSession(0, "com.android.test",
858 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
859 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
860 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
861
862 // Add some more sessions, but still in quota.
863 mQuotaController.saveTimingSession(0, "com.android.test",
864 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
865 mQuotaController.saveTimingSession(0, "com.android.test",
866 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
867 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
868 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
869
870 // Test when out of quota.
871 mQuotaController.saveTimingSession(0, "com.android.test",
872 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1));
873 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
874 verify(mAlarmManager, times(1))
875 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
876
877 // Alarm already scheduled, so make sure it's not scheduled again.
878 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
879 verify(mAlarmManager, times(1))
880 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
881 }
882
883 /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
884 @Test
885 public void testMaybeScheduleStartAlarmLocked_BucketChange() {
886 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
887 // because it schedules an alarm too. Prevent it from doing so.
888 spyOn(mQuotaController);
889 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
890
891 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
892
893 // Affects rare bucket
894 mQuotaController.saveTimingSession(0, "com.android.test",
895 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3));
896 // Affects frequent and rare buckets
897 mQuotaController.saveTimingSession(0, "com.android.test",
898 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
899 // Affects working, frequent, and rare buckets
900 final long outOfQuotaTime = now - HOUR_IN_MILLIS;
901 mQuotaController.saveTimingSession(0, "com.android.test",
902 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10));
903 // Affects all buckets
904 mQuotaController.saveTimingSession(0, "com.android.test",
905 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3));
906
907 InOrder inOrder = inOrder(mAlarmManager);
908
909 // Start in ACTIVE bucket.
910 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
911 inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
912 any());
913 inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
914
915 // And down from there.
916 final long expectedWorkingAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800917 outOfQuotaTime + (2 * HOUR_IN_MILLIS)
918 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800919 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
920 inOrder.verify(mAlarmManager, times(1))
921 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
922
923 final long expectedFrequentAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800924 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
925 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800926 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
927 inOrder.verify(mAlarmManager, times(1))
928 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
929
930 final long expectedRareAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800931 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
932 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800933 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
934 inOrder.verify(mAlarmManager, times(1))
935 .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
936
937 // And back up again.
938 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
939 inOrder.verify(mAlarmManager, times(1))
940 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
941
942 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
943 inOrder.verify(mAlarmManager, times(1))
944 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
945
946 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
947 inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
948 any());
949 inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
950 }
951
Kweku Adams045fb5722018-12-11 14:29:10 -0800952
953 /**
954 * Tests that the start alarm is properly rescheduled if the earliest session that contributes
955 * to the app being out of quota contributes less than the quota buffer time.
956 */
957 @Test
958 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() {
959 // Use the default values
960 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
961 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
962 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
963 }
964
965 @Test
966 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
967 // Make sure any new value is used correctly.
968 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
969 mQuotaController.onConstantsUpdatedLocked();
970 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
971 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
972 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
973 }
974
975 @Test
976 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
977 // Make sure any new value is used correctly.
978 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
979 mQuotaController.onConstantsUpdatedLocked();
980 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
981 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
982 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
983 }
984
985 @Test
986 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
987 // Make sure any new value is used correctly.
988 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
989 mQuotaController.onConstantsUpdatedLocked();
990 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
991 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
992 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
993 }
994
995 @Test
996 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
997 // Make sure any new value is used correctly.
998 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
999 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
1000 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
1001 mQuotaController.onConstantsUpdatedLocked();
1002 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1003 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1004 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1005 }
1006
1007 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() {
1008 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1009 // because it schedules an alarm too. Prevent it from doing so.
1010 spyOn(mQuotaController);
1011 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1012
1013 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1014 // Working set window size is 2 hours.
1015 final int standbyBucket = WORKING_INDEX;
1016 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1017 final long remainingTimeMs =
1018 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
1019
1020 // Session straddles edge of bucket window. Only the contribution should be counted towards
1021 // the quota.
1022 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1023 createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1024 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1025 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1026 createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2));
1027 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1028 // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1029 final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
1030 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1031 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1032 standbyBucket);
1033 verify(mAlarmManager, times(1))
1034 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1035 }
1036
1037
1038 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
1039 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1040 // because it schedules an alarm too. Prevent it from doing so.
1041 spyOn(mQuotaController);
1042 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1043
1044 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1045 // Working set window size is 2 hours.
1046 final int standbyBucket = WORKING_INDEX;
1047 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1048 final long remainingTimeMs =
1049 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - contributionMs;
1050
1051 // Session straddles edge of 24 hour window. Only the contribution should be counted towards
1052 // the quota.
1053 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1054 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1055 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1056 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1057 createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300));
1058 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1059 // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1060 final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS
1061 //+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS
1062 + 24 * HOUR_IN_MILLIS
1063 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1064 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1065 standbyBucket);
1066 verify(mAlarmManager, times(1))
1067 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1068 }
1069
Kweku Adams4836f9d2018-11-12 17:04:17 -08001070 /** Tests that QuotaController doesn't throttle if throttling is turned off. */
1071 @Test
1072 public void testThrottleToggling() throws Exception {
1073 setDischarging();
1074 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1075 createTimingSession(
1076 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1077 10 * MINUTE_IN_MILLIS, 4));
1078 JobStatus jobStatus = createJobStatus("testThrottleToggling", 1);
1079 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
1080 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1081 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1082
1083 mConstants.USE_HEARTBEATS = true;
1084 mQuotaController.onConstantsUpdatedLocked();
1085 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1086 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1087
1088 mConstants.USE_HEARTBEATS = false;
1089 mQuotaController.onConstantsUpdatedLocked();
1090 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1091 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1092 }
1093
1094 @Test
1095 public void testConstantsUpdating_ValidValues() {
1096 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS;
1097 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 2 * MINUTE_IN_MILLIS;
1098 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 15 * MINUTE_IN_MILLIS;
1099 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
1100 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
1101 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001102 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001103
1104 mQuotaController.onConstantsUpdatedLocked();
1105
1106 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1107 assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1108 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1109 assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1110 assertEquals(45 * MINUTE_IN_MILLIS,
1111 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1112 assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001113 assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001114 }
1115
1116 @Test
1117 public void testConstantsUpdating_InvalidValues() {
1118 // Test negatives
1119 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
1120 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
1121 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
1122 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
1123 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
1124 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001125 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001126
1127 mQuotaController.onConstantsUpdatedLocked();
1128
1129 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1130 assertEquals(0, mQuotaController.getInQuotaBufferMs());
1131 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1132 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1133 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1134 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001135 assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001136
1137 // Test larger than a day. Controller should cap at one day.
1138 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
1139 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 25 * HOUR_IN_MILLIS;
1140 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 25 * HOUR_IN_MILLIS;
1141 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
1142 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
1143 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001144 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001145
1146 mQuotaController.onConstantsUpdatedLocked();
1147
1148 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1149 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1150 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1151 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1152 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1153 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001154 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001155 }
1156
1157 /** Tests that TimingSessions aren't saved when the device is charging. */
1158 @Test
1159 public void testTimerTracking_Charging() {
1160 setCharging();
1161
1162 JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1);
1163 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1164
1165 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1166
1167 mQuotaController.prepareForExecutionLocked(jobStatus);
1168 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1169 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1170 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1171 }
1172
1173 /** Tests that TimingSessions are saved properly when the device is discharging. */
1174 @Test
1175 public void testTimerTracking_Discharging() {
1176 setDischarging();
1177
1178 JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
1179 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1180
1181 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1182
1183 List<TimingSession> expected = new ArrayList<>();
1184
1185 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1186 mQuotaController.prepareForExecutionLocked(jobStatus);
1187 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1188 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1189 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1190 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1191
1192 // Test overlapping jobs.
1193 JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2);
1194 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1195
1196 JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3);
1197 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1198
1199 advanceElapsedClock(SECOND_IN_MILLIS);
1200
1201 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1202 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1203 mQuotaController.prepareForExecutionLocked(jobStatus);
1204 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1205 mQuotaController.prepareForExecutionLocked(jobStatus2);
1206 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1207 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1208 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1209 mQuotaController.prepareForExecutionLocked(jobStatus3);
1210 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1211 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1212 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1213 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1214 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1215 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1216 }
1217
1218 /**
1219 * Tests that TimingSessions are saved properly when the device alternates between
1220 * charging and discharging.
1221 */
1222 @Test
1223 public void testTimerTracking_ChargingAndDischarging() {
1224 JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
1225 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1226 JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
1227 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1228 JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3);
1229 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1230 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1231 List<TimingSession> expected = new ArrayList<>();
1232
1233 // A job starting while charging. Only the portion that runs during the discharging period
1234 // should be counted.
1235 setCharging();
1236
1237 mQuotaController.prepareForExecutionLocked(jobStatus);
1238 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1239 setDischarging();
1240 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1241 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1242 mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
1243 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1244 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1245
1246 advanceElapsedClock(SECOND_IN_MILLIS);
1247
1248 // One job starts while discharging, spans a charging session, and ends after the charging
1249 // session. Only the portions during the discharging periods should be counted. This should
1250 // result in two TimingSessions. A second job starts while discharging and ends within the
1251 // charging session. Only the portion during the first discharging portion should be
1252 // counted. A third job starts and ends within the charging session. The third job
1253 // shouldn't be included in either job count.
1254 setDischarging();
1255 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1256 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1257 mQuotaController.prepareForExecutionLocked(jobStatus);
1258 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1259 mQuotaController.prepareForExecutionLocked(jobStatus2);
1260 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1261 setCharging();
1262 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
1263 mQuotaController.prepareForExecutionLocked(jobStatus3);
1264 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1265 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1266 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1267 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1268 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1269 setDischarging();
1270 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1271 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1272 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1273 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
1274 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1275
1276 // A job starting while discharging and ending while charging. Only the portion that runs
1277 // during the discharging period should be counted.
1278 setDischarging();
1279 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1280 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1281 mQuotaController.prepareForExecutionLocked(jobStatus2);
1282 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1283 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1284 setCharging();
1285 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1286 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1287 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1288 }
1289
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001290 /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
1291 @Test
1292 public void testTimerTracking_AllBackground() {
1293 setDischarging();
1294
1295 JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
1296 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1297
1298 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1299
1300 List<TimingSession> expected = new ArrayList<>();
1301
1302 // Test single job.
1303 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1304 mQuotaController.prepareForExecutionLocked(jobStatus);
1305 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1306 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1307 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1308 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1309
1310 // Test overlapping jobs.
1311 JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
1312 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1313
1314 JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
1315 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1316
1317 advanceElapsedClock(SECOND_IN_MILLIS);
1318
1319 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1320 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1321 mQuotaController.prepareForExecutionLocked(jobStatus);
1322 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1323 mQuotaController.prepareForExecutionLocked(jobStatus2);
1324 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1325 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1326 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1327 mQuotaController.prepareForExecutionLocked(jobStatus3);
1328 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1329 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1330 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1331 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1332 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1333 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1334 }
1335
1336 /** Tests that Timers don't count foreground jobs. */
1337 @Test
1338 public void testTimerTracking_AllForeground() {
1339 setDischarging();
1340
1341 JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
1342 jobStatus.uidActive = true;
1343 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1344
1345 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1346
1347 mQuotaController.prepareForExecutionLocked(jobStatus);
1348 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1349 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1350 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1351 }
1352
1353 /**
1354 * Tests that Timers properly track overlapping foreground and background jobs.
1355 */
1356 @Test
1357 public void testTimerTracking_ForegroundAndBackground() {
1358 setDischarging();
1359
1360 JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
1361 JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
1362 JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
1363 jobFg3.uidActive = true;
1364 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1365 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1366 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
1367 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1368 List<TimingSession> expected = new ArrayList<>();
1369
1370 // UID starts out inactive.
1371 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1372 mQuotaController.prepareForExecutionLocked(jobBg1);
1373 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1374 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1375 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1376 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1377
1378 advanceElapsedClock(SECOND_IN_MILLIS);
1379
1380 // Bg job starts while inactive, spans an entire active session, and ends after the
1381 // active session.
1382 // Fg job starts after the bg job and ends before the bg job.
1383 // Entire bg job duration should be counted since it started before active session. However,
1384 // count should only be 1 since Timer shouldn't count fg jobs.
1385 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1386 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1387 mQuotaController.prepareForExecutionLocked(jobBg2);
1388 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1389 mQuotaController.prepareForExecutionLocked(jobFg3);
1390 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1391 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1392 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1393 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1394 expected.add(createTimingSession(start, 30 * SECOND_IN_MILLIS, 1));
1395 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1396
1397 advanceElapsedClock(SECOND_IN_MILLIS);
1398
1399 // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
1400 // "inactive" and then bg job 2 starts. Then fg job ends.
1401 // This should result in two TimingSessions with a count of one each.
1402 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1403 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1404 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1405 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
1406 mQuotaController.prepareForExecutionLocked(jobBg1);
1407 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1408 mQuotaController.prepareForExecutionLocked(jobFg3);
1409 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1410 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1411 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
1412 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
1413 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1414 mQuotaController.prepareForExecutionLocked(jobBg2);
1415 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1416 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1417 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1418 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1419 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
1420 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1421 }
1422
Kweku Adams4836f9d2018-11-12 17:04:17 -08001423 /**
1424 * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
1425 * its quota.
1426 */
1427 @Test
1428 public void testTracking_OutOfQuota() {
1429 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
1430 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1431 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
1432 // Now the package only has two seconds to run.
1433 final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
1434 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1435 createTimingSession(
1436 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1437 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
1438
1439 // Start the job.
1440 mQuotaController.prepareForExecutionLocked(jobStatus);
1441 advanceElapsedClock(remainingTimeMs);
1442
1443 // Wait for some extra time to allow for job processing.
1444 verify(mJobSchedulerService,
1445 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
1446 .onControllerStateChanged();
1447 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1448 }
1449
1450 /**
1451 * Tests that a job is properly handled when it's at the edge of its quota and the old quota is
1452 * being phased out.
1453 */
1454 @Test
1455 public void testTracking_RollingQuota() {
1456 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
1457 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1458 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
1459 Handler handler = mQuotaController.getHandler();
1460 spyOn(handler);
1461
1462 long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1463 final long remainingTimeMs = SECOND_IN_MILLIS;
1464 // The package only has one second to run, but this session is at the edge of the rolling
1465 // window, so as the package "reaches its quota" it will have more to keep running.
1466 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1467 createTimingSession(now - 2 * HOUR_IN_MILLIS,
1468 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
1469
1470 assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
1471 // Start the job.
1472 mQuotaController.prepareForExecutionLocked(jobStatus);
1473 advanceElapsedClock(remainingTimeMs);
1474
1475 // Wait for some extra time to allow for job processing.
1476 verify(mJobSchedulerService,
1477 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
1478 .onControllerStateChanged();
1479 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1480 // The job used up the remaining quota, but in that time, the same amount of time in the
1481 // old TimingSession also fell out of the quota window, so it should still have the same
1482 // amount of remaining time left its quota.
1483 assertEquals(remainingTimeMs,
1484 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1485 verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(remainingTimeMs));
1486 }
1487}