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