blob: 95da13fca7acba989f0c0afe2ee87fd140a04fea [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;
33import static org.junit.Assert.assertNull;
34import static org.junit.Assert.assertTrue;
35import static org.mockito.ArgumentMatchers.any;
36import static org.mockito.ArgumentMatchers.anyInt;
37import static org.mockito.ArgumentMatchers.anyLong;
38import static org.mockito.Mockito.atLeast;
39import static org.mockito.Mockito.eq;
40import static org.mockito.Mockito.never;
41import static org.mockito.Mockito.timeout;
42import static org.mockito.Mockito.times;
43import static org.mockito.Mockito.verify;
44
45import android.app.AlarmManager;
46import android.app.job.JobInfo;
47import android.app.usage.UsageStatsManager;
48import android.app.usage.UsageStatsManagerInternal;
49import android.content.BroadcastReceiver;
50import android.content.ComponentName;
51import android.content.Context;
52import android.content.Intent;
53import android.content.pm.PackageManagerInternal;
54import android.os.BatteryManager;
55import android.os.BatteryManagerInternal;
56import android.os.Handler;
57import android.os.Looper;
58import android.os.SystemClock;
59
60import androidx.test.runner.AndroidJUnit4;
61
62import com.android.server.LocalServices;
63import com.android.server.job.JobSchedulerService;
64import com.android.server.job.JobSchedulerService.Constants;
65import com.android.server.job.controllers.QuotaController.TimingSession;
66
67import org.junit.After;
68import org.junit.Before;
69import org.junit.Test;
70import org.junit.runner.RunWith;
71import org.mockito.ArgumentCaptor;
72import org.mockito.InOrder;
73import org.mockito.Mock;
74import org.mockito.MockitoSession;
75import org.mockito.quality.Strictness;
76
77import java.time.Clock;
78import java.time.Duration;
79import java.time.ZoneOffset;
80import java.util.ArrayList;
81import java.util.List;
82
83@RunWith(AndroidJUnit4.class)
84public class QuotaControllerTest {
85 private static final long SECOND_IN_MILLIS = 1000L;
86 private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
87 private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
88 private static final String TAG_CLEANUP = "*job.cleanup*";
89 private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
90 private static final long IN_QUOTA_BUFFER_MILLIS = 30 * SECOND_IN_MILLIS;
91 private static final int CALLING_UID = 1000;
92 private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
93 private static final int SOURCE_USER_ID = 0;
94
95 private BroadcastReceiver mChargingReceiver;
96 private Constants mConstants;
97 private QuotaController mQuotaController;
98
99 private MockitoSession mMockingSession;
100 @Mock
101 private AlarmManager mAlarmManager;
102 @Mock
103 private Context mContext;
104 @Mock
105 private JobSchedulerService mJobSchedulerService;
106 @Mock
107 private UsageStatsManagerInternal mUsageStatsManager;
108
109 @Before
110 public void setUp() {
111 mMockingSession = mockitoSession()
112 .initMocks(this)
113 .strictness(Strictness.LENIENT)
114 .mockStatic(LocalServices.class)
115 .startMocking();
116 // Make sure constants turn on QuotaController.
117 mConstants = new Constants();
118 mConstants.USE_HEARTBEATS = false;
119
120 // Called in StateController constructor.
121 when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
122 when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
123 when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
124 // Called in QuotaController constructor.
125 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
126 when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
127 doReturn(mock(BatteryManagerInternal.class))
128 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
129 doReturn(mUsageStatsManager)
130 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
131 // Used in JobStatus.
132 doReturn(mock(PackageManagerInternal.class))
133 .when(() -> LocalServices.getService(PackageManagerInternal.class));
134
135 // Freeze the clocks at this moment in time
136 JobSchedulerService.sSystemClock =
137 Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC);
138 JobSchedulerService.sUptimeMillisClock =
139 Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC);
140 JobSchedulerService.sElapsedRealtimeClock =
141 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC);
142
143 // Initialize real objects.
144 // Capture the listeners.
145 ArgumentCaptor<BroadcastReceiver> receiverCaptor =
146 ArgumentCaptor.forClass(BroadcastReceiver.class);
147 mQuotaController = new QuotaController(mJobSchedulerService);
148
149 verify(mContext).registerReceiver(receiverCaptor.capture(), any());
150 mChargingReceiver = receiverCaptor.getValue();
151 }
152
153 @After
154 public void tearDown() {
155 if (mMockingSession != null) {
156 mMockingSession.finishMocking();
157 }
158 }
159
160 private Clock getAdvancedClock(Clock clock, long incrementMs) {
161 return Clock.offset(clock, Duration.ofMillis(incrementMs));
162 }
163
164 private void advanceElapsedClock(long incrementMs) {
165 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
166 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
167 }
168
169 private void setCharging() {
170 Intent intent = new Intent(BatteryManager.ACTION_CHARGING);
171 mChargingReceiver.onReceive(mContext, intent);
172 }
173
174 private void setDischarging() {
175 Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING);
176 mChargingReceiver.onReceive(mContext, intent);
177 }
178
179 private void setStandbyBucket(int bucketIndex) {
180 int bucket;
181 switch (bucketIndex) {
182 case ACTIVE_INDEX:
183 bucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
184 break;
185 case WORKING_INDEX:
186 bucket = UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
187 break;
188 case FREQUENT_INDEX:
189 bucket = UsageStatsManager.STANDBY_BUCKET_FREQUENT;
190 break;
191 case RARE_INDEX:
192 bucket = UsageStatsManager.STANDBY_BUCKET_RARE;
193 break;
194 default:
195 bucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
196 }
197 when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
198 anyLong())).thenReturn(bucket);
199 }
200
201 private void setStandbyBucket(int bucketIndex, JobStatus job) {
202 setStandbyBucket(bucketIndex);
203 job.setStandbyBucket(bucketIndex);
204 }
205
206 private JobStatus createJobStatus(String testTag, int jobId) {
207 JobInfo jobInfo = new JobInfo.Builder(jobId,
208 new ComponentName(mContext, "TestQuotaJobService"))
209 .setMinimumLatency(Math.abs(jobId) + 1)
210 .build();
211 return JobStatus.createFromJobInfo(
212 jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
213 }
214
215 private TimingSession createTimingSession(long start, long duration, int count) {
216 return new TimingSession(start, start + duration, count);
217 }
218
219 @Test
220 public void testSaveTimingSession() {
221 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
222
223 List<TimingSession> expected = new ArrayList<>();
224 TimingSession one = new TimingSession(1, 10, 1);
225 TimingSession two = new TimingSession(11, 20, 2);
226 TimingSession thr = new TimingSession(21, 30, 3);
227
228 mQuotaController.saveTimingSession(0, "com.android.test", one);
229 expected.add(one);
230 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
231
232 mQuotaController.saveTimingSession(0, "com.android.test", two);
233 expected.add(two);
234 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
235
236 mQuotaController.saveTimingSession(0, "com.android.test", thr);
237 expected.add(thr);
238 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
239 }
240
241 @Test
242 public void testDeleteObsoleteSessionsLocked() {
243 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
244 TimingSession one = createTimingSession(
245 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
246 TimingSession two = createTimingSession(
247 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
248 TimingSession thr = createTimingSession(
249 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
250 // Overlaps 24 hour boundary.
251 TimingSession fou = createTimingSession(
252 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
253 // Way past the 24 hour boundary.
254 TimingSession fiv = createTimingSession(
255 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
256 List<TimingSession> expected = new ArrayList<>();
257 // Added in correct (chronological) order.
258 expected.add(fou);
259 expected.add(thr);
260 expected.add(two);
261 expected.add(one);
262 mQuotaController.saveTimingSession(0, "com.android.test", fiv);
263 mQuotaController.saveTimingSession(0, "com.android.test", fou);
264 mQuotaController.saveTimingSession(0, "com.android.test", thr);
265 mQuotaController.saveTimingSession(0, "com.android.test", two);
266 mQuotaController.saveTimingSession(0, "com.android.test", one);
267
268 mQuotaController.deleteObsoleteSessionsLocked();
269
270 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
271 }
272
273 @Test
274 public void testGetTrailingExecutionTimeLocked_NoTimer() {
275 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
276 // Added in chronological order.
277 mQuotaController.saveTimingSession(0, "com.android.test",
278 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
279 mQuotaController.saveTimingSession(0, "com.android.test",
280 createTimingSession(
281 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
282 mQuotaController.saveTimingSession(0, "com.android.test",
283 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
284 mQuotaController.saveTimingSession(0, "com.android.test",
285 createTimingSession(
286 now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1));
287 mQuotaController.saveTimingSession(0, "com.android.test",
288 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
289
290 assertEquals(0, mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
291 MINUTE_IN_MILLIS));
292 assertEquals(2 * MINUTE_IN_MILLIS,
293 mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
294 3 * MINUTE_IN_MILLIS));
295 assertEquals(4 * MINUTE_IN_MILLIS,
296 mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
297 5 * MINUTE_IN_MILLIS));
298 assertEquals(4 * MINUTE_IN_MILLIS,
299 mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
300 49 * MINUTE_IN_MILLIS));
301 assertEquals(5 * MINUTE_IN_MILLIS,
302 mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
303 50 * MINUTE_IN_MILLIS));
304 assertEquals(6 * MINUTE_IN_MILLIS,
305 mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
306 HOUR_IN_MILLIS));
307 assertEquals(11 * MINUTE_IN_MILLIS,
308 mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
309 2 * HOUR_IN_MILLIS));
310 assertEquals(12 * MINUTE_IN_MILLIS,
311 mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
312 3 * HOUR_IN_MILLIS));
313 assertEquals(22 * MINUTE_IN_MILLIS,
314 mQuotaController.getTrailingExecutionTimeLocked(0, "com.android.test",
315 6 * HOUR_IN_MILLIS));
316 }
317
318 @Test
319 public void testMaybeScheduleCleanupAlarmLocked() {
320 // No sessions saved yet.
321 mQuotaController.maybeScheduleCleanupAlarmLocked();
322 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
323
324 // Test with only one timing session saved.
325 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
326 final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
327 mQuotaController.saveTimingSession(0, "com.android.test",
328 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1));
329 mQuotaController.maybeScheduleCleanupAlarmLocked();
330 verify(mAlarmManager, times(1))
331 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
332
333 // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
334 mQuotaController.saveTimingSession(0, "com.android.test",
335 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
336 mQuotaController.saveTimingSession(0, "com.android.test",
337 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
338 mQuotaController.maybeScheduleCleanupAlarmLocked();
339 verify(mAlarmManager, times(1))
340 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
341 }
342
343 @Test
344 public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
345 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
346 // because it schedules an alarm too. Prevent it from doing so.
347 spyOn(mQuotaController);
348 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
349
350 // Working set window size is 2 hours.
351 final int standbyBucket = WORKING_INDEX;
352
353 // No sessions saved yet.
354 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
355 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
356
357 // Test with timing sessions out of window.
358 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
359 mQuotaController.saveTimingSession(0, "com.android.test",
360 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
361 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
362 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
363
364 // Test with timing sessions in window but still in quota.
365 final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
366 // Counting backwards, the quota will come back one minute before the end.
367 final long expectedAlarmTime =
368 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS;
369 mQuotaController.saveTimingSession(0, "com.android.test",
370 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1));
371 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
372 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
373
374 // Add some more sessions, but still in quota.
375 mQuotaController.saveTimingSession(0, "com.android.test",
376 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
377 mQuotaController.saveTimingSession(0, "com.android.test",
378 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1));
379 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
380 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
381
382 // Test when out of quota.
383 mQuotaController.saveTimingSession(0, "com.android.test",
384 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
385 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
386 verify(mAlarmManager, times(1))
387 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
388
389 // Alarm already scheduled, so make sure it's not scheduled again.
390 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
391 verify(mAlarmManager, times(1))
392 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
393 }
394
395 @Test
396 public void testMaybeScheduleStartAlarmLocked_Frequent() {
397 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
398 // because it schedules an alarm too. Prevent it from doing so.
399 spyOn(mQuotaController);
400 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
401
402 // Frequent window size is 8 hours.
403 final int standbyBucket = FREQUENT_INDEX;
404
405 // No sessions saved yet.
406 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
407 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
408
409 // Test with timing sessions out of window.
410 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
411 mQuotaController.saveTimingSession(0, "com.android.test",
412 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
413 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
414 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
415
416 // Test with timing sessions in window but still in quota.
417 final long start = now - (6 * HOUR_IN_MILLIS);
418 final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS;
419 mQuotaController.saveTimingSession(0, "com.android.test",
420 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
421 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
422 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
423
424 // Add some more sessions, but still in quota.
425 mQuotaController.saveTimingSession(0, "com.android.test",
426 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
427 mQuotaController.saveTimingSession(0, "com.android.test",
428 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
429 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
430 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
431
432 // Test when out of quota.
433 mQuotaController.saveTimingSession(0, "com.android.test",
434 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
435 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
436 verify(mAlarmManager, times(1))
437 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
438
439 // Alarm already scheduled, so make sure it's not scheduled again.
440 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
441 verify(mAlarmManager, times(1))
442 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
443 }
444
445 @Test
446 public void testMaybeScheduleStartAlarmLocked_Rare() {
447 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
448 // because it schedules an alarm too. Prevent it from doing so.
449 spyOn(mQuotaController);
450 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
451
452 // Rare window size is 24 hours.
453 final int standbyBucket = RARE_INDEX;
454
455 // No sessions saved yet.
456 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
457 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
458
459 // Test with timing sessions out of window.
460 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
461 mQuotaController.saveTimingSession(0, "com.android.test",
462 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
463 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
464 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
465
466 // Test with timing sessions in window but still in quota.
467 final long start = now - (6 * HOUR_IN_MILLIS);
468 // Counting backwards, the first minute in the session is over the allowed time, so it
469 // needs to be excluded.
470 final long expectedAlarmTime =
471 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS + IN_QUOTA_BUFFER_MILLIS;
472 mQuotaController.saveTimingSession(0, "com.android.test",
473 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
474 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
475 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
476
477 // Add some more sessions, but still in quota.
478 mQuotaController.saveTimingSession(0, "com.android.test",
479 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
480 mQuotaController.saveTimingSession(0, "com.android.test",
481 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
482 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
483 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
484
485 // Test when out of quota.
486 mQuotaController.saveTimingSession(0, "com.android.test",
487 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1));
488 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
489 verify(mAlarmManager, times(1))
490 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
491
492 // Alarm already scheduled, so make sure it's not scheduled again.
493 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
494 verify(mAlarmManager, times(1))
495 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
496 }
497
498 /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
499 @Test
500 public void testMaybeScheduleStartAlarmLocked_BucketChange() {
501 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
502 // because it schedules an alarm too. Prevent it from doing so.
503 spyOn(mQuotaController);
504 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
505
506 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
507
508 // Affects rare bucket
509 mQuotaController.saveTimingSession(0, "com.android.test",
510 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3));
511 // Affects frequent and rare buckets
512 mQuotaController.saveTimingSession(0, "com.android.test",
513 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
514 // Affects working, frequent, and rare buckets
515 final long outOfQuotaTime = now - HOUR_IN_MILLIS;
516 mQuotaController.saveTimingSession(0, "com.android.test",
517 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10));
518 // Affects all buckets
519 mQuotaController.saveTimingSession(0, "com.android.test",
520 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3));
521
522 InOrder inOrder = inOrder(mAlarmManager);
523
524 // Start in ACTIVE bucket.
525 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
526 inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
527 any());
528 inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
529
530 // And down from there.
531 final long expectedWorkingAlarmTime =
532 outOfQuotaTime + (2 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS;
533 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
534 inOrder.verify(mAlarmManager, times(1))
535 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
536
537 final long expectedFrequentAlarmTime =
538 outOfQuotaTime + (8 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS;
539 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
540 inOrder.verify(mAlarmManager, times(1))
541 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
542
543 final long expectedRareAlarmTime =
544 outOfQuotaTime + (24 * HOUR_IN_MILLIS) + IN_QUOTA_BUFFER_MILLIS;
545 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
546 inOrder.verify(mAlarmManager, times(1))
547 .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
548
549 // And back up again.
550 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
551 inOrder.verify(mAlarmManager, times(1))
552 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
553
554 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
555 inOrder.verify(mAlarmManager, times(1))
556 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
557
558 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
559 inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
560 any());
561 inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
562 }
563
564 /** Tests that QuotaController doesn't throttle if throttling is turned off. */
565 @Test
566 public void testThrottleToggling() throws Exception {
567 setDischarging();
568 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
569 createTimingSession(
570 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
571 10 * MINUTE_IN_MILLIS, 4));
572 JobStatus jobStatus = createJobStatus("testThrottleToggling", 1);
573 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
574 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
575 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
576
577 mConstants.USE_HEARTBEATS = true;
578 mQuotaController.onConstantsUpdatedLocked();
579 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
580 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
581
582 mConstants.USE_HEARTBEATS = false;
583 mQuotaController.onConstantsUpdatedLocked();
584 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
585 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
586 }
587
588 @Test
589 public void testConstantsUpdating_ValidValues() {
590 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS;
591 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 2 * MINUTE_IN_MILLIS;
592 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 15 * MINUTE_IN_MILLIS;
593 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
594 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
595 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
596
597 mQuotaController.onConstantsUpdatedLocked();
598
599 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
600 assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
601 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
602 assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
603 assertEquals(45 * MINUTE_IN_MILLIS,
604 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
605 assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
606 }
607
608 @Test
609 public void testConstantsUpdating_InvalidValues() {
610 // Test negatives
611 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
612 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
613 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
614 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
615 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
616 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
617
618 mQuotaController.onConstantsUpdatedLocked();
619
620 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
621 assertEquals(0, mQuotaController.getInQuotaBufferMs());
622 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
623 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
624 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
625 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
626
627 // Test larger than a day. Controller should cap at one day.
628 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
629 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 25 * HOUR_IN_MILLIS;
630 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 25 * HOUR_IN_MILLIS;
631 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
632 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
633 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
634
635 mQuotaController.onConstantsUpdatedLocked();
636
637 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
638 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
639 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
640 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
641 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
642 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
643 }
644
645 /** Tests that TimingSessions aren't saved when the device is charging. */
646 @Test
647 public void testTimerTracking_Charging() {
648 setCharging();
649
650 JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1);
651 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
652
653 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
654
655 mQuotaController.prepareForExecutionLocked(jobStatus);
656 advanceElapsedClock(5 * SECOND_IN_MILLIS);
657 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
658 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
659 }
660
661 /** Tests that TimingSessions are saved properly when the device is discharging. */
662 @Test
663 public void testTimerTracking_Discharging() {
664 setDischarging();
665
666 JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
667 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
668
669 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
670
671 List<TimingSession> expected = new ArrayList<>();
672
673 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
674 mQuotaController.prepareForExecutionLocked(jobStatus);
675 advanceElapsedClock(5 * SECOND_IN_MILLIS);
676 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
677 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
678 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
679
680 // Test overlapping jobs.
681 JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2);
682 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
683
684 JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3);
685 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
686
687 advanceElapsedClock(SECOND_IN_MILLIS);
688
689 start = JobSchedulerService.sElapsedRealtimeClock.millis();
690 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
691 mQuotaController.prepareForExecutionLocked(jobStatus);
692 advanceElapsedClock(10 * SECOND_IN_MILLIS);
693 mQuotaController.prepareForExecutionLocked(jobStatus2);
694 advanceElapsedClock(10 * SECOND_IN_MILLIS);
695 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
696 advanceElapsedClock(10 * SECOND_IN_MILLIS);
697 mQuotaController.prepareForExecutionLocked(jobStatus3);
698 advanceElapsedClock(20 * SECOND_IN_MILLIS);
699 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
700 advanceElapsedClock(10 * SECOND_IN_MILLIS);
701 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
702 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
703 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
704 }
705
706 /**
707 * Tests that TimingSessions are saved properly when the device alternates between
708 * charging and discharging.
709 */
710 @Test
711 public void testTimerTracking_ChargingAndDischarging() {
712 JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
713 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
714 JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
715 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
716 JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3);
717 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
718 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
719 List<TimingSession> expected = new ArrayList<>();
720
721 // A job starting while charging. Only the portion that runs during the discharging period
722 // should be counted.
723 setCharging();
724
725 mQuotaController.prepareForExecutionLocked(jobStatus);
726 advanceElapsedClock(10 * SECOND_IN_MILLIS);
727 setDischarging();
728 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
729 advanceElapsedClock(10 * SECOND_IN_MILLIS);
730 mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
731 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
732 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
733
734 advanceElapsedClock(SECOND_IN_MILLIS);
735
736 // One job starts while discharging, spans a charging session, and ends after the charging
737 // session. Only the portions during the discharging periods should be counted. This should
738 // result in two TimingSessions. A second job starts while discharging and ends within the
739 // charging session. Only the portion during the first discharging portion should be
740 // counted. A third job starts and ends within the charging session. The third job
741 // shouldn't be included in either job count.
742 setDischarging();
743 start = JobSchedulerService.sElapsedRealtimeClock.millis();
744 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
745 mQuotaController.prepareForExecutionLocked(jobStatus);
746 advanceElapsedClock(10 * SECOND_IN_MILLIS);
747 mQuotaController.prepareForExecutionLocked(jobStatus2);
748 advanceElapsedClock(10 * SECOND_IN_MILLIS);
749 setCharging();
750 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
751 mQuotaController.prepareForExecutionLocked(jobStatus3);
752 advanceElapsedClock(10 * SECOND_IN_MILLIS);
753 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
754 advanceElapsedClock(10 * SECOND_IN_MILLIS);
755 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
756 advanceElapsedClock(10 * SECOND_IN_MILLIS);
757 setDischarging();
758 start = JobSchedulerService.sElapsedRealtimeClock.millis();
759 advanceElapsedClock(20 * SECOND_IN_MILLIS);
760 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
761 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
762 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
763
764 // A job starting while discharging and ending while charging. Only the portion that runs
765 // during the discharging period should be counted.
766 setDischarging();
767 start = JobSchedulerService.sElapsedRealtimeClock.millis();
768 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
769 mQuotaController.prepareForExecutionLocked(jobStatus2);
770 advanceElapsedClock(10 * SECOND_IN_MILLIS);
771 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
772 setCharging();
773 advanceElapsedClock(10 * SECOND_IN_MILLIS);
774 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
775 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
776 }
777
Kweku Adamscc5afbc2018-12-11 15:24:25 -0800778 /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
779 @Test
780 public void testTimerTracking_AllBackground() {
781 setDischarging();
782
783 JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
784 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
785
786 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
787
788 List<TimingSession> expected = new ArrayList<>();
789
790 // Test single job.
791 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
792 mQuotaController.prepareForExecutionLocked(jobStatus);
793 advanceElapsedClock(5 * SECOND_IN_MILLIS);
794 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
795 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
796 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
797
798 // Test overlapping jobs.
799 JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
800 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
801
802 JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
803 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
804
805 advanceElapsedClock(SECOND_IN_MILLIS);
806
807 start = JobSchedulerService.sElapsedRealtimeClock.millis();
808 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
809 mQuotaController.prepareForExecutionLocked(jobStatus);
810 advanceElapsedClock(10 * SECOND_IN_MILLIS);
811 mQuotaController.prepareForExecutionLocked(jobStatus2);
812 advanceElapsedClock(10 * SECOND_IN_MILLIS);
813 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
814 advanceElapsedClock(10 * SECOND_IN_MILLIS);
815 mQuotaController.prepareForExecutionLocked(jobStatus3);
816 advanceElapsedClock(20 * SECOND_IN_MILLIS);
817 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
818 advanceElapsedClock(10 * SECOND_IN_MILLIS);
819 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
820 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
821 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
822 }
823
824 /** Tests that Timers don't count foreground jobs. */
825 @Test
826 public void testTimerTracking_AllForeground() {
827 setDischarging();
828
829 JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
830 jobStatus.uidActive = true;
831 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
832
833 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
834
835 mQuotaController.prepareForExecutionLocked(jobStatus);
836 advanceElapsedClock(5 * SECOND_IN_MILLIS);
837 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
838 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
839 }
840
841 /**
842 * Tests that Timers properly track overlapping foreground and background jobs.
843 */
844 @Test
845 public void testTimerTracking_ForegroundAndBackground() {
846 setDischarging();
847
848 JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
849 JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
850 JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
851 jobFg3.uidActive = true;
852 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
853 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
854 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
855 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
856 List<TimingSession> expected = new ArrayList<>();
857
858 // UID starts out inactive.
859 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
860 mQuotaController.prepareForExecutionLocked(jobBg1);
861 advanceElapsedClock(10 * SECOND_IN_MILLIS);
862 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
863 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
864 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
865
866 advanceElapsedClock(SECOND_IN_MILLIS);
867
868 // Bg job starts while inactive, spans an entire active session, and ends after the
869 // active session.
870 // Fg job starts after the bg job and ends before the bg job.
871 // Entire bg job duration should be counted since it started before active session. However,
872 // count should only be 1 since Timer shouldn't count fg jobs.
873 start = JobSchedulerService.sElapsedRealtimeClock.millis();
874 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
875 mQuotaController.prepareForExecutionLocked(jobBg2);
876 advanceElapsedClock(10 * SECOND_IN_MILLIS);
877 mQuotaController.prepareForExecutionLocked(jobFg3);
878 advanceElapsedClock(10 * SECOND_IN_MILLIS);
879 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
880 advanceElapsedClock(10 * SECOND_IN_MILLIS);
881 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
882 expected.add(createTimingSession(start, 30 * SECOND_IN_MILLIS, 1));
883 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
884
885 advanceElapsedClock(SECOND_IN_MILLIS);
886
887 // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
888 // "inactive" and then bg job 2 starts. Then fg job ends.
889 // This should result in two TimingSessions with a count of one each.
890 start = JobSchedulerService.sElapsedRealtimeClock.millis();
891 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
892 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
893 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
894 mQuotaController.prepareForExecutionLocked(jobBg1);
895 advanceElapsedClock(10 * SECOND_IN_MILLIS);
896 mQuotaController.prepareForExecutionLocked(jobFg3);
897 advanceElapsedClock(10 * SECOND_IN_MILLIS);
898 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
899 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
900 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
901 start = JobSchedulerService.sElapsedRealtimeClock.millis();
902 mQuotaController.prepareForExecutionLocked(jobBg2);
903 advanceElapsedClock(10 * SECOND_IN_MILLIS);
904 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
905 advanceElapsedClock(10 * SECOND_IN_MILLIS);
906 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
907 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
908 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
909 }
910
Kweku Adams4836f9d2018-11-12 17:04:17 -0800911 /**
912 * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
913 * its quota.
914 */
915 @Test
916 public void testTracking_OutOfQuota() {
917 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
918 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
919 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
920 // Now the package only has two seconds to run.
921 final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
922 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
923 createTimingSession(
924 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
925 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
926
927 // Start the job.
928 mQuotaController.prepareForExecutionLocked(jobStatus);
929 advanceElapsedClock(remainingTimeMs);
930
931 // Wait for some extra time to allow for job processing.
932 verify(mJobSchedulerService,
933 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
934 .onControllerStateChanged();
935 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
936 }
937
938 /**
939 * Tests that a job is properly handled when it's at the edge of its quota and the old quota is
940 * being phased out.
941 */
942 @Test
943 public void testTracking_RollingQuota() {
944 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
945 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
946 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
947 Handler handler = mQuotaController.getHandler();
948 spyOn(handler);
949
950 long now = JobSchedulerService.sElapsedRealtimeClock.millis();
951 final long remainingTimeMs = SECOND_IN_MILLIS;
952 // The package only has one second to run, but this session is at the edge of the rolling
953 // window, so as the package "reaches its quota" it will have more to keep running.
954 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
955 createTimingSession(now - 2 * HOUR_IN_MILLIS,
956 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
957
958 assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
959 // Start the job.
960 mQuotaController.prepareForExecutionLocked(jobStatus);
961 advanceElapsedClock(remainingTimeMs);
962
963 // Wait for some extra time to allow for job processing.
964 verify(mJobSchedulerService,
965 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
966 .onControllerStateChanged();
967 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
968 // The job used up the remaining quota, but in that time, the same amount of time in the
969 // old TimingSession also fell out of the quota window, so it should still have the same
970 // amount of remaining time left its quota.
971 assertEquals(remainingTimeMs,
972 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
973 verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(remainingTimeMs));
974 }
975}