blob: 57ee6dcad9f2eb2f484f9d4006f15f95819c0b8f [file] [log] [blame]
Kweku Adams4836f9d2018-11-12 17:04:17 -08001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.job.controllers;
18
19import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
20import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
21import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
22import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
23import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
24import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
25import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
26import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
27import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
28import static com.android.server.job.JobSchedulerService.RARE_INDEX;
29import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
30
31import static org.junit.Assert.assertEquals;
32import static org.junit.Assert.assertFalse;
Kweku Adams045fb5722018-12-11 14:29:10 -080033import static org.junit.Assert.assertNotEquals;
Kweku Adams4836f9d2018-11-12 17:04:17 -080034import static org.junit.Assert.assertNull;
35import static org.junit.Assert.assertTrue;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080036import static org.junit.Assert.fail;
Kweku Adams4836f9d2018-11-12 17:04:17 -080037import static org.mockito.ArgumentMatchers.any;
38import static org.mockito.ArgumentMatchers.anyInt;
39import static org.mockito.ArgumentMatchers.anyLong;
40import static org.mockito.Mockito.atLeast;
41import static org.mockito.Mockito.eq;
42import static org.mockito.Mockito.never;
43import static org.mockito.Mockito.timeout;
44import static org.mockito.Mockito.times;
45import static org.mockito.Mockito.verify;
46
Kweku Adamsd6625ff2019-01-10 12:06:21 -080047import android.app.ActivityManager;
48import android.app.ActivityManagerInternal;
Kweku Adams4836f9d2018-11-12 17:04:17 -080049import android.app.AlarmManager;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080050import android.app.AppGlobals;
51import android.app.IActivityManager;
52import android.app.IUidObserver;
Kweku Adams4836f9d2018-11-12 17:04:17 -080053import android.app.job.JobInfo;
54import android.app.usage.UsageStatsManager;
55import android.app.usage.UsageStatsManagerInternal;
56import android.content.BroadcastReceiver;
57import android.content.ComponentName;
58import android.content.Context;
59import android.content.Intent;
60import android.content.pm.PackageManagerInternal;
61import android.os.BatteryManager;
62import android.os.BatteryManagerInternal;
63import android.os.Handler;
64import android.os.Looper;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080065import android.os.RemoteException;
Kweku Adams4836f9d2018-11-12 17:04:17 -080066import android.os.SystemClock;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080067import android.util.SparseBooleanArray;
Kweku Adams4836f9d2018-11-12 17:04:17 -080068
69import androidx.test.runner.AndroidJUnit4;
70
71import com.android.server.LocalServices;
72import com.android.server.job.JobSchedulerService;
73import com.android.server.job.JobSchedulerService.Constants;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080074import com.android.server.job.JobStore;
Kweku Adams045fb5722018-12-11 14:29:10 -080075import com.android.server.job.controllers.QuotaController.ExecutionStats;
Kweku Adams4836f9d2018-11-12 17:04:17 -080076import com.android.server.job.controllers.QuotaController.TimingSession;
77
78import org.junit.After;
79import org.junit.Before;
80import org.junit.Test;
81import org.junit.runner.RunWith;
82import org.mockito.ArgumentCaptor;
83import org.mockito.InOrder;
84import org.mockito.Mock;
85import org.mockito.MockitoSession;
86import org.mockito.quality.Strictness;
87
88import java.time.Clock;
89import java.time.Duration;
90import java.time.ZoneOffset;
91import java.util.ArrayList;
92import java.util.List;
93
94@RunWith(AndroidJUnit4.class)
95public class QuotaControllerTest {
96 private static final long SECOND_IN_MILLIS = 1000L;
97 private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
98 private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
99 private static final String TAG_CLEANUP = "*job.cleanup*";
100 private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
Kweku Adams4836f9d2018-11-12 17:04:17 -0800101 private static final int CALLING_UID = 1000;
102 private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
103 private static final int SOURCE_USER_ID = 0;
104
105 private BroadcastReceiver mChargingReceiver;
106 private Constants mConstants;
107 private QuotaController mQuotaController;
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800108 private int mSourceUid;
109 private IUidObserver mUidObserver;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800110
111 private MockitoSession mMockingSession;
112 @Mock
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800113 private ActivityManagerInternal mActivityMangerInternal;
114 @Mock
Kweku Adams4836f9d2018-11-12 17:04:17 -0800115 private AlarmManager mAlarmManager;
116 @Mock
117 private Context mContext;
118 @Mock
119 private JobSchedulerService mJobSchedulerService;
120 @Mock
121 private UsageStatsManagerInternal mUsageStatsManager;
122
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800123 private JobStore mJobStore;
124
Kweku Adams4836f9d2018-11-12 17:04:17 -0800125 @Before
126 public void setUp() {
127 mMockingSession = mockitoSession()
128 .initMocks(this)
129 .strictness(Strictness.LENIENT)
130 .mockStatic(LocalServices.class)
131 .startMocking();
132 // Make sure constants turn on QuotaController.
133 mConstants = new Constants();
134 mConstants.USE_HEARTBEATS = false;
135
136 // Called in StateController constructor.
137 when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
138 when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
139 when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
140 // Called in QuotaController constructor.
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800141 IActivityManager activityManager = ActivityManager.getService();
142 spyOn(activityManager);
143 try {
144 doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
145 } catch (RemoteException e) {
146 fail("registerUidObserver threw exception: " + e.getMessage());
147 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800148 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
149 when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800150 doReturn(mActivityMangerInternal)
151 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
Kweku Adams4836f9d2018-11-12 17:04:17 -0800152 doReturn(mock(BatteryManagerInternal.class))
153 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
154 doReturn(mUsageStatsManager)
155 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
156 // Used in JobStatus.
157 doReturn(mock(PackageManagerInternal.class))
158 .when(() -> LocalServices.getService(PackageManagerInternal.class));
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800159 // Used in QuotaController.Handler.
160 mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
161 when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800162
Kweku Adams045fb5722018-12-11 14:29:10 -0800163 // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
164 // in the past, and QuotaController sometimes floors values at 0, so if the test time
165 // causes sessions with negative timestamps, they will fail.
Kweku Adams4836f9d2018-11-12 17:04:17 -0800166 JobSchedulerService.sSystemClock =
Kweku Adams045fb5722018-12-11 14:29:10 -0800167 getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
168 24 * HOUR_IN_MILLIS);
169 JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
170 Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC),
171 24 * HOUR_IN_MILLIS);
172 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
173 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
174 24 * HOUR_IN_MILLIS);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800175
176 // Initialize real objects.
177 // Capture the listeners.
178 ArgumentCaptor<BroadcastReceiver> receiverCaptor =
179 ArgumentCaptor.forClass(BroadcastReceiver.class);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800180 ArgumentCaptor<IUidObserver> uidObserverCaptor =
181 ArgumentCaptor.forClass(IUidObserver.class);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800182 mQuotaController = new QuotaController(mJobSchedulerService);
183
184 verify(mContext).registerReceiver(receiverCaptor.capture(), any());
185 mChargingReceiver = receiverCaptor.getValue();
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800186 try {
187 verify(activityManager).registerUidObserver(
188 uidObserverCaptor.capture(),
189 eq(ActivityManager.UID_OBSERVER_PROCSTATE),
190 eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE),
191 any());
192 mUidObserver = uidObserverCaptor.getValue();
193 mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
194 } catch (RemoteException e) {
195 fail(e.getMessage());
196 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800197 }
198
199 @After
200 public void tearDown() {
201 if (mMockingSession != null) {
202 mMockingSession.finishMocking();
203 }
204 }
205
206 private Clock getAdvancedClock(Clock clock, long incrementMs) {
207 return Clock.offset(clock, Duration.ofMillis(incrementMs));
208 }
209
210 private void advanceElapsedClock(long incrementMs) {
211 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
212 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
213 }
214
215 private void setCharging() {
216 Intent intent = new Intent(BatteryManager.ACTION_CHARGING);
217 mChargingReceiver.onReceive(mContext, intent);
218 }
219
220 private void setDischarging() {
221 Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING);
222 mChargingReceiver.onReceive(mContext, intent);
223 }
224
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800225 private void setProcessState(int procState) {
226 try {
227 doReturn(procState).when(mActivityMangerInternal).getUidProcessState(mSourceUid);
228 SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
229 spyOn(foregroundUids);
230 mUidObserver.onUidStateChanged(mSourceUid, procState, 0);
231 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
232 verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1))
233 .put(eq(mSourceUid), eq(true));
234 assertTrue(foregroundUids.get(mSourceUid));
235 } else {
236 verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1)).delete(eq(mSourceUid));
237 assertFalse(foregroundUids.get(mSourceUid));
238 }
239 } catch (RemoteException e) {
240 fail("registerUidObserver threw exception: " + e.getMessage());
241 }
242 }
243
Kweku Adams4836f9d2018-11-12 17:04:17 -0800244 private void setStandbyBucket(int bucketIndex) {
245 int bucket;
246 switch (bucketIndex) {
247 case ACTIVE_INDEX:
248 bucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
249 break;
250 case WORKING_INDEX:
251 bucket = UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
252 break;
253 case FREQUENT_INDEX:
254 bucket = UsageStatsManager.STANDBY_BUCKET_FREQUENT;
255 break;
256 case RARE_INDEX:
257 bucket = UsageStatsManager.STANDBY_BUCKET_RARE;
258 break;
259 default:
260 bucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
261 }
262 when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
263 anyLong())).thenReturn(bucket);
264 }
265
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800266 private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800267 setStandbyBucket(bucketIndex);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800268 for (JobStatus job : jobs) {
269 job.setStandbyBucket(bucketIndex);
270 }
271 }
272
273 private void trackJobs(JobStatus... jobs) {
274 for (JobStatus job : jobs) {
275 mJobStore.add(job);
276 mQuotaController.maybeStartTrackingJobLocked(job, null);
277 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800278 }
279
280 private JobStatus createJobStatus(String testTag, int jobId) {
281 JobInfo jobInfo = new JobInfo.Builder(jobId,
282 new ComponentName(mContext, "TestQuotaJobService"))
283 .setMinimumLatency(Math.abs(jobId) + 1)
284 .build();
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800285 JobStatus js = JobStatus.createFromJobInfo(
Kweku Adams4836f9d2018-11-12 17:04:17 -0800286 jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800287 // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
288 js.setStandbyBucket(FREQUENT_INDEX);
289 return js;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800290 }
291
292 private TimingSession createTimingSession(long start, long duration, int count) {
293 return new TimingSession(start, start + duration, count);
294 }
295
296 @Test
297 public void testSaveTimingSession() {
298 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
299
300 List<TimingSession> expected = new ArrayList<>();
301 TimingSession one = new TimingSession(1, 10, 1);
302 TimingSession two = new TimingSession(11, 20, 2);
303 TimingSession thr = new TimingSession(21, 30, 3);
304
305 mQuotaController.saveTimingSession(0, "com.android.test", one);
306 expected.add(one);
307 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
308
309 mQuotaController.saveTimingSession(0, "com.android.test", two);
310 expected.add(two);
311 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
312
313 mQuotaController.saveTimingSession(0, "com.android.test", thr);
314 expected.add(thr);
315 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
316 }
317
318 @Test
319 public void testDeleteObsoleteSessionsLocked() {
320 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
321 TimingSession one = createTimingSession(
322 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
323 TimingSession two = createTimingSession(
324 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
325 TimingSession thr = createTimingSession(
326 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
327 // Overlaps 24 hour boundary.
328 TimingSession fou = createTimingSession(
329 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
330 // Way past the 24 hour boundary.
331 TimingSession fiv = createTimingSession(
332 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
333 List<TimingSession> expected = new ArrayList<>();
334 // Added in correct (chronological) order.
335 expected.add(fou);
336 expected.add(thr);
337 expected.add(two);
338 expected.add(one);
339 mQuotaController.saveTimingSession(0, "com.android.test", fiv);
340 mQuotaController.saveTimingSession(0, "com.android.test", fou);
341 mQuotaController.saveTimingSession(0, "com.android.test", thr);
342 mQuotaController.saveTimingSession(0, "com.android.test", two);
343 mQuotaController.saveTimingSession(0, "com.android.test", one);
344
345 mQuotaController.deleteObsoleteSessionsLocked();
346
347 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
348 }
349
350 @Test
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800351 public void testOnAppRemovedLocked() {
352 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
353 mQuotaController.saveTimingSession(0, "com.android.test.remove",
354 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
355 mQuotaController.saveTimingSession(0, "com.android.test.remove",
356 createTimingSession(
357 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
358 mQuotaController.saveTimingSession(0, "com.android.test.remove",
359 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
360 // Test that another app isn't affected.
361 TimingSession one = createTimingSession(
362 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
363 TimingSession two = createTimingSession(
364 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
365 List<TimingSession> expected = new ArrayList<>();
366 // Added in correct (chronological) order.
367 expected.add(two);
368 expected.add(one);
369 mQuotaController.saveTimingSession(0, "com.android.test.stay", two);
370 mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
371
Kweku Adams045fb5722018-12-11 14:29:10 -0800372 ExecutionStats expectedStats = new ExecutionStats();
373 expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
374 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
375
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800376 mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001);
377 assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
378 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800379 assertEquals(expectedStats,
380 mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
381 assertNotEquals(expectedStats,
382 mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800383 }
384
385 @Test
386 public void testOnUserRemovedLocked() {
387 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
388 mQuotaController.saveTimingSession(0, "com.android.test",
389 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
390 mQuotaController.saveTimingSession(0, "com.android.test",
391 createTimingSession(
392 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
393 mQuotaController.saveTimingSession(0, "com.android.test",
394 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
395 // Test that another user isn't affected.
396 TimingSession one = createTimingSession(
397 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
398 TimingSession two = createTimingSession(
399 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
400 List<TimingSession> expected = new ArrayList<>();
401 // Added in correct (chronological) order.
402 expected.add(two);
403 expected.add(one);
404 mQuotaController.saveTimingSession(10, "com.android.test", two);
405 mQuotaController.saveTimingSession(10, "com.android.test", one);
406
Kweku Adams045fb5722018-12-11 14:29:10 -0800407 ExecutionStats expectedStats = new ExecutionStats();
408 expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
409 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
410
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800411 mQuotaController.onUserRemovedLocked(0);
412 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
413 assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800414 assertEquals(expectedStats,
415 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
416 assertNotEquals(expectedStats,
417 mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800418 }
419
420 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -0800421 public void testUpdateExecutionStatsLocked_NoTimer() {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800422 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
423 // Added in chronological order.
424 mQuotaController.saveTimingSession(0, "com.android.test",
425 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
426 mQuotaController.saveTimingSession(0, "com.android.test",
427 createTimingSession(
428 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
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(
433 now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1));
434 mQuotaController.saveTimingSession(0, "com.android.test",
435 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
436
Kweku Adams045fb5722018-12-11 14:29:10 -0800437 // Test an app that hasn't had any activity.
438 ExecutionStats expectedStats = new ExecutionStats();
439 ExecutionStats inputStats = new ExecutionStats();
440
441 inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
442 // Invalid time is now +24 hours since there are no sessions at all for the app.
443 expectedStats.invalidTimeElapsed = now + 24 * HOUR_IN_MILLIS;
444 mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
445 assertEquals(expectedStats, inputStats);
446
447 inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
448 // Invalid time is now +18 hours since there are no sessions in the window but the earliest
449 // session is 6 hours ago.
450 expectedStats.invalidTimeElapsed = now + 18 * HOUR_IN_MILLIS;
451 expectedStats.executionTimeInWindowMs = 0;
452 expectedStats.bgJobCountInWindow = 0;
453 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
454 expectedStats.bgJobCountInMaxPeriod = 15;
455 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
456 assertEquals(expectedStats, inputStats);
457
458 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
459 // Invalid time is now since the session straddles the window cutoff time.
460 expectedStats.invalidTimeElapsed = now;
461 expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
462 expectedStats.bgJobCountInWindow = 3;
463 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
464 expectedStats.bgJobCountInMaxPeriod = 15;
465 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
466 assertEquals(expectedStats, inputStats);
467
468 inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
469 // Invalid time is now since the start of the session is at the very edge of the window
470 // cutoff time.
471 expectedStats.invalidTimeElapsed = now;
472 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
473 expectedStats.bgJobCountInWindow = 3;
474 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
475 expectedStats.bgJobCountInMaxPeriod = 15;
476 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
477 assertEquals(expectedStats, inputStats);
478
479 inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
480 // Invalid time is now +44 minutes since the earliest session in the window is now-5
481 // minutes.
482 expectedStats.invalidTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
483 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
484 expectedStats.bgJobCountInWindow = 3;
485 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
486 expectedStats.bgJobCountInMaxPeriod = 15;
487 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
488 assertEquals(expectedStats, inputStats);
489
490 inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
491 // Invalid time is now since the session is at the very edge of the window cutoff time.
492 expectedStats.invalidTimeElapsed = now;
493 expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
494 expectedStats.bgJobCountInWindow = 4;
495 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
496 expectedStats.bgJobCountInMaxPeriod = 15;
497 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
498 assertEquals(expectedStats, inputStats);
499
500 inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
501 // Invalid time is now since the start of the session is at the very edge of the window
502 // cutoff time.
503 expectedStats.invalidTimeElapsed = now;
504 expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
505 expectedStats.bgJobCountInWindow = 5;
506 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
507 expectedStats.bgJobCountInMaxPeriod = 15;
508 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
509 assertEquals(expectedStats, inputStats);
510
511 inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
512 // Invalid time is now since the session straddles the window cutoff time.
513 expectedStats.invalidTimeElapsed = now;
514 expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
515 expectedStats.bgJobCountInWindow = 10;
516 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
517 expectedStats.bgJobCountInMaxPeriod = 15;
518 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
519 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
520 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
521 assertEquals(expectedStats, inputStats);
522
523 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
524 // Invalid time is now +59 minutes since the earliest session in the window is now-121
525 // minutes.
526 expectedStats.invalidTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
527 expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
528 expectedStats.bgJobCountInWindow = 10;
529 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
530 expectedStats.bgJobCountInMaxPeriod = 15;
531 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
532 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
533 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
534 assertEquals(expectedStats, inputStats);
535
536 inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
537 // Invalid time is now since the start of the session is at the very edge of the window
538 // cutoff time.
539 expectedStats.invalidTimeElapsed = now;
540 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
541 expectedStats.bgJobCountInWindow = 15;
542 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
543 expectedStats.bgJobCountInMaxPeriod = 15;
544 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
545 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
546 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
547 assertEquals(expectedStats, inputStats);
548
549 // Make sure invalidTimeElapsed is set correctly when it's dependent on the max period.
550 mQuotaController.getTimingSessions(0, "com.android.test")
551 .add(0,
552 createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
553 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
554 // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
555 // before the end of the max period cutoff time.
556 expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
557 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
558 expectedStats.bgJobCountInWindow = 15;
559 expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
560 expectedStats.bgJobCountInMaxPeriod = 18;
561 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
562 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
563 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
564 assertEquals(expectedStats, inputStats);
565
566 mQuotaController.getTimingSessions(0, "com.android.test")
567 .add(0,
568 createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
569 2 * MINUTE_IN_MILLIS, 2));
570 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
571 // Invalid time is now since the earlist session straddles the max period cutoff time.
572 expectedStats.invalidTimeElapsed = now;
573 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
574 expectedStats.bgJobCountInWindow = 15;
575 expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
576 expectedStats.bgJobCountInMaxPeriod = 20;
577 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
578 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
579 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
580 assertEquals(expectedStats, inputStats);
581 }
582
583 /**
584 * Tests that getExecutionStatsLocked returns the correct stats.
585 */
586 @Test
587 public void testGetExecutionStatsLocked_Values() {
588 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
589 mQuotaController.saveTimingSession(0, "com.android.test",
590 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
591 mQuotaController.saveTimingSession(0, "com.android.test",
592 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
593 mQuotaController.saveTimingSession(0, "com.android.test",
594 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
595 mQuotaController.saveTimingSession(0, "com.android.test",
596 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
597
598 ExecutionStats expectedStats = new ExecutionStats();
599
600 // Active
601 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
602 expectedStats.invalidTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
603 expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
604 expectedStats.bgJobCountInWindow = 5;
605 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
606 expectedStats.bgJobCountInMaxPeriod = 20;
607 assertEquals(expectedStats,
608 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
609
610 // Working
611 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
612 expectedStats.invalidTimeElapsed = now;
613 expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
614 expectedStats.bgJobCountInWindow = 10;
615 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
616 expectedStats.bgJobCountInMaxPeriod = 20;
617 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
618 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
619 assertEquals(expectedStats,
620 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
621
622 // Frequent
623 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
624 expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
625 expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
626 expectedStats.bgJobCountInWindow = 15;
627 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
628 expectedStats.bgJobCountInMaxPeriod = 20;
629 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
630 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
631 assertEquals(expectedStats,
632 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
633
634 // Rare
635 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
636 expectedStats.invalidTimeElapsed = now + HOUR_IN_MILLIS;
637 expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
638 expectedStats.bgJobCountInWindow = 20;
639 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
640 expectedStats.bgJobCountInMaxPeriod = 20;
641 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
642 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
643 assertEquals(expectedStats,
644 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
645 }
646
647 /**
648 * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
649 */
650 @Test
651 public void testGetExecutionStatsLocked_Caching() {
652 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
653 mQuotaController.saveTimingSession(0, "com.android.test",
654 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
655 mQuotaController.saveTimingSession(0, "com.android.test",
656 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
657 mQuotaController.saveTimingSession(0, "com.android.test",
658 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
659 mQuotaController.saveTimingSession(0, "com.android.test",
660 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
661 final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0,
662 "com.android.test", ACTIVE_INDEX);
663 final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0,
664 "com.android.test", WORKING_INDEX);
665 final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
666 "com.android.test", FREQUENT_INDEX);
667 final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0,
668 "com.android.test", RARE_INDEX);
669
670 // Advance clock so that the working stats shouldn't be the same.
671 advanceElapsedClock(MINUTE_IN_MILLIS);
672 // Change frequent bucket size so that the stats need to be recalculated.
673 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS;
674 mQuotaController.onConstantsUpdatedLocked();
675
676 ExecutionStats expectedStats = new ExecutionStats();
677 expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
678 expectedStats.invalidTimeElapsed = originalStatsActive.invalidTimeElapsed;
679 expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
680 expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
681 expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
682 expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
683 expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
684 final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
685 "com.android.test", ACTIVE_INDEX);
686 // Stats for the same bucket should use the same object.
687 assertTrue(originalStatsActive == newStatsActive);
688 assertEquals(expectedStats, newStatsActive);
689
690 expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
691 expectedStats.invalidTimeElapsed = originalStatsWorking.invalidTimeElapsed;
692 expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
693 expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
694 expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
695 final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
696 "com.android.test", WORKING_INDEX);
697 assertTrue(originalStatsWorking == newStatsWorking);
698 assertNotEquals(expectedStats, newStatsWorking);
699
700 expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
701 expectedStats.invalidTimeElapsed = originalStatsFrequent.invalidTimeElapsed;
702 expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
703 expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
704 expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
705 final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
706 "com.android.test", FREQUENT_INDEX);
707 assertTrue(originalStatsFrequent == newStatsFrequent);
708 assertNotEquals(expectedStats, newStatsFrequent);
709
710 expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
711 expectedStats.invalidTimeElapsed = originalStatsRare.invalidTimeElapsed;
712 expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
713 expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
714 expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
715 final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
716 "com.android.test", RARE_INDEX);
717 assertTrue(originalStatsRare == newStatsRare);
718 assertEquals(expectedStats, newStatsRare);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800719 }
720
721 @Test
722 public void testMaybeScheduleCleanupAlarmLocked() {
723 // No sessions saved yet.
724 mQuotaController.maybeScheduleCleanupAlarmLocked();
725 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
726
727 // Test with only one timing session saved.
728 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
729 final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
730 mQuotaController.saveTimingSession(0, "com.android.test",
731 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1));
732 mQuotaController.maybeScheduleCleanupAlarmLocked();
733 verify(mAlarmManager, times(1))
734 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
735
736 // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
737 mQuotaController.saveTimingSession(0, "com.android.test",
738 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
739 mQuotaController.saveTimingSession(0, "com.android.test",
740 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
741 mQuotaController.maybeScheduleCleanupAlarmLocked();
742 verify(mAlarmManager, times(1))
743 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
744 }
745
746 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -0800747 public void testMaybeScheduleStartAlarmLocked_Active() {
748 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
749 // because it schedules an alarm too. Prevent it from doing so.
750 spyOn(mQuotaController);
751 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
752
753 // Active window size is 10 minutes.
754 final int standbyBucket = ACTIVE_INDEX;
755
756 // No sessions saved yet.
757 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
758 standbyBucket);
759 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
760
761 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
762 // Test with timing sessions out of window but still under max execution limit.
763 final long expectedAlarmTime =
764 (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS
765 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
766 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
767 createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
768 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
769 createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
770 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
771 createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
772 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
773 standbyBucket);
774 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
775
776 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
777 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1));
778 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
779 standbyBucket);
780 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
781
782 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800783 setStandbyBucket(standbyBucket, jobStatus);
Kweku Adams045fb5722018-12-11 14:29:10 -0800784 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
785 mQuotaController.prepareForExecutionLocked(jobStatus);
786 advanceElapsedClock(5 * MINUTE_IN_MILLIS);
787 // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window
788 // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no
789 // longer has quota.
790 assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
791 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
792 standbyBucket);
793 verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK),
794 any(), any());
795 }
796
797 @Test
Kweku Adams4836f9d2018-11-12 17:04:17 -0800798 public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
799 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
800 // because it schedules an alarm too. Prevent it from doing so.
801 spyOn(mQuotaController);
802 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
803
804 // Working set window size is 2 hours.
805 final int standbyBucket = WORKING_INDEX;
806
807 // No sessions saved yet.
808 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
809 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
810
811 // Test with timing sessions out of window.
812 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
813 mQuotaController.saveTimingSession(0, "com.android.test",
814 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
815 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
816 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
817
818 // Test with timing sessions in window but still in quota.
819 final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
820 // Counting backwards, the quota will come back one minute before the end.
821 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800822 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS
823 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800824 mQuotaController.saveTimingSession(0, "com.android.test",
825 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1));
826 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
827 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
828
829 // Add some more sessions, but still in quota.
830 mQuotaController.saveTimingSession(0, "com.android.test",
831 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
832 mQuotaController.saveTimingSession(0, "com.android.test",
833 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1));
834 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
835 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
836
837 // Test when out of quota.
838 mQuotaController.saveTimingSession(0, "com.android.test",
839 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
840 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
841 verify(mAlarmManager, times(1))
842 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
843
844 // Alarm already scheduled, so make sure it's not scheduled again.
845 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
846 verify(mAlarmManager, times(1))
847 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
848 }
849
850 @Test
851 public void testMaybeScheduleStartAlarmLocked_Frequent() {
852 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
853 // because it schedules an alarm too. Prevent it from doing so.
854 spyOn(mQuotaController);
855 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
856
857 // Frequent window size is 8 hours.
858 final int standbyBucket = FREQUENT_INDEX;
859
860 // No sessions saved yet.
861 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
862 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
863
864 // Test with timing sessions out of window.
865 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
866 mQuotaController.saveTimingSession(0, "com.android.test",
867 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
868 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
869 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
870
871 // Test with timing sessions in window but still in quota.
872 final long start = now - (6 * HOUR_IN_MILLIS);
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800873 final long expectedAlarmTime =
874 start + 8 * HOUR_IN_MILLIS + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800875 mQuotaController.saveTimingSession(0, "com.android.test",
876 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
877 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
878 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
879
880 // Add some more sessions, but still in quota.
881 mQuotaController.saveTimingSession(0, "com.android.test",
882 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
883 mQuotaController.saveTimingSession(0, "com.android.test",
884 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
885 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
886 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
887
888 // Test when out of quota.
889 mQuotaController.saveTimingSession(0, "com.android.test",
890 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
891 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
892 verify(mAlarmManager, times(1))
893 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
894
895 // Alarm already scheduled, so make sure it's not scheduled again.
896 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
897 verify(mAlarmManager, times(1))
898 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
899 }
900
901 @Test
902 public void testMaybeScheduleStartAlarmLocked_Rare() {
903 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
904 // because it schedules an alarm too. Prevent it from doing so.
905 spyOn(mQuotaController);
906 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
907
908 // Rare window size is 24 hours.
909 final int standbyBucket = RARE_INDEX;
910
911 // No sessions saved yet.
912 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
913 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
914
915 // Test with timing sessions out of window.
916 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
917 mQuotaController.saveTimingSession(0, "com.android.test",
918 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
919 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
920 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
921
922 // Test with timing sessions in window but still in quota.
923 final long start = now - (6 * HOUR_IN_MILLIS);
924 // Counting backwards, the first minute in the session is over the allowed time, so it
925 // needs to be excluded.
926 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800927 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
928 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800929 mQuotaController.saveTimingSession(0, "com.android.test",
930 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
931 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
932 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
933
934 // Add some more sessions, but still in quota.
935 mQuotaController.saveTimingSession(0, "com.android.test",
936 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
937 mQuotaController.saveTimingSession(0, "com.android.test",
938 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
939 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
940 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
941
942 // Test when out of quota.
943 mQuotaController.saveTimingSession(0, "com.android.test",
944 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1));
945 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
946 verify(mAlarmManager, times(1))
947 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
948
949 // Alarm already scheduled, so make sure it's not scheduled again.
950 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
951 verify(mAlarmManager, times(1))
952 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
953 }
954
955 /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
956 @Test
957 public void testMaybeScheduleStartAlarmLocked_BucketChange() {
958 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
959 // because it schedules an alarm too. Prevent it from doing so.
960 spyOn(mQuotaController);
961 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
962
963 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
964
965 // Affects rare bucket
966 mQuotaController.saveTimingSession(0, "com.android.test",
967 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3));
968 // Affects frequent and rare buckets
969 mQuotaController.saveTimingSession(0, "com.android.test",
970 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
971 // Affects working, frequent, and rare buckets
972 final long outOfQuotaTime = now - HOUR_IN_MILLIS;
973 mQuotaController.saveTimingSession(0, "com.android.test",
974 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10));
975 // Affects all buckets
976 mQuotaController.saveTimingSession(0, "com.android.test",
977 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3));
978
979 InOrder inOrder = inOrder(mAlarmManager);
980
981 // Start in ACTIVE bucket.
982 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
Kweku Adamsbffea5a2018-12-13 22:13:28 -0800983 inOrder.verify(mAlarmManager, never())
984 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
Kweku Adams4836f9d2018-11-12 17:04:17 -0800985 inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
986
987 // And down from there.
988 final long expectedWorkingAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800989 outOfQuotaTime + (2 * HOUR_IN_MILLIS)
990 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800991 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
992 inOrder.verify(mAlarmManager, times(1))
993 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
994
995 final long expectedFrequentAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800996 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
997 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800998 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
999 inOrder.verify(mAlarmManager, times(1))
1000 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1001
1002 final long expectedRareAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001003 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
1004 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001005 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
1006 inOrder.verify(mAlarmManager, times(1))
1007 .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1008
1009 // And back up again.
1010 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
1011 inOrder.verify(mAlarmManager, times(1))
1012 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1013
1014 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
1015 inOrder.verify(mAlarmManager, times(1))
1016 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1017
1018 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
1019 inOrder.verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
1020 any());
1021 inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
1022 }
1023
Kweku Adams045fb5722018-12-11 14:29:10 -08001024
1025 /**
1026 * Tests that the start alarm is properly rescheduled if the earliest session that contributes
1027 * to the app being out of quota contributes less than the quota buffer time.
1028 */
1029 @Test
1030 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() {
1031 // Use the default values
1032 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1033 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1034 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1035 }
1036
1037 @Test
1038 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
1039 // Make sure any new value is used correctly.
1040 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
1041 mQuotaController.onConstantsUpdatedLocked();
1042 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1043 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1044 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1045 }
1046
1047 @Test
1048 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
1049 // Make sure any new value is used correctly.
1050 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
1051 mQuotaController.onConstantsUpdatedLocked();
1052 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1053 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1054 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1055 }
1056
1057 @Test
1058 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
1059 // Make sure any new value is used correctly.
1060 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
1061 mQuotaController.onConstantsUpdatedLocked();
1062 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1063 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1064 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1065 }
1066
1067 @Test
1068 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
1069 // Make sure any new value is used correctly.
1070 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
1071 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
1072 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
1073 mQuotaController.onConstantsUpdatedLocked();
1074 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1075 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1076 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1077 }
1078
1079 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() {
1080 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1081 // because it schedules an alarm too. Prevent it from doing so.
1082 spyOn(mQuotaController);
1083 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1084
1085 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1086 // Working set window size is 2 hours.
1087 final int standbyBucket = WORKING_INDEX;
1088 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1089 final long remainingTimeMs =
1090 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
1091
1092 // Session straddles edge of bucket window. Only the contribution should be counted towards
1093 // the quota.
1094 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1095 createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1096 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1097 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1098 createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2));
1099 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1100 // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1101 final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
1102 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1103 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1104 standbyBucket);
1105 verify(mAlarmManager, times(1))
1106 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1107 }
1108
1109
1110 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
1111 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1112 // because it schedules an alarm too. Prevent it from doing so.
1113 spyOn(mQuotaController);
1114 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1115
1116 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1117 // Working set window size is 2 hours.
1118 final int standbyBucket = WORKING_INDEX;
1119 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1120 final long remainingTimeMs =
1121 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - contributionMs;
1122
1123 // Session straddles edge of 24 hour window. Only the contribution should be counted towards
1124 // the quota.
1125 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1126 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1127 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1128 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1129 createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300));
1130 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1131 // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1132 final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS
1133 //+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS
1134 + 24 * HOUR_IN_MILLIS
1135 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1136 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1137 standbyBucket);
1138 verify(mAlarmManager, times(1))
1139 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1140 }
1141
Kweku Adams4836f9d2018-11-12 17:04:17 -08001142 /** Tests that QuotaController doesn't throttle if throttling is turned off. */
1143 @Test
1144 public void testThrottleToggling() throws Exception {
1145 setDischarging();
1146 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1147 createTimingSession(
1148 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1149 10 * MINUTE_IN_MILLIS, 4));
1150 JobStatus jobStatus = createJobStatus("testThrottleToggling", 1);
1151 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
1152 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1153 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1154
1155 mConstants.USE_HEARTBEATS = true;
1156 mQuotaController.onConstantsUpdatedLocked();
1157 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1158 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1159
1160 mConstants.USE_HEARTBEATS = false;
1161 mQuotaController.onConstantsUpdatedLocked();
1162 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1163 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1164 }
1165
1166 @Test
1167 public void testConstantsUpdating_ValidValues() {
1168 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS;
1169 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 2 * MINUTE_IN_MILLIS;
1170 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 15 * MINUTE_IN_MILLIS;
1171 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
1172 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
1173 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001174 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001175
1176 mQuotaController.onConstantsUpdatedLocked();
1177
1178 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1179 assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1180 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1181 assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1182 assertEquals(45 * MINUTE_IN_MILLIS,
1183 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1184 assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001185 assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001186 }
1187
1188 @Test
1189 public void testConstantsUpdating_InvalidValues() {
1190 // Test negatives
1191 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
1192 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
1193 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
1194 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
1195 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
1196 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001197 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001198
1199 mQuotaController.onConstantsUpdatedLocked();
1200
1201 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1202 assertEquals(0, mQuotaController.getInQuotaBufferMs());
1203 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1204 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1205 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1206 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001207 assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001208
1209 // Test larger than a day. Controller should cap at one day.
1210 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
1211 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 25 * HOUR_IN_MILLIS;
1212 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 25 * HOUR_IN_MILLIS;
1213 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
1214 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
1215 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001216 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001217
1218 mQuotaController.onConstantsUpdatedLocked();
1219
1220 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1221 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1222 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1223 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1224 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1225 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001226 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001227 }
1228
1229 /** Tests that TimingSessions aren't saved when the device is charging. */
1230 @Test
1231 public void testTimerTracking_Charging() {
1232 setCharging();
1233
1234 JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1);
1235 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1236
1237 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1238
1239 mQuotaController.prepareForExecutionLocked(jobStatus);
1240 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1241 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1242 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1243 }
1244
1245 /** Tests that TimingSessions are saved properly when the device is discharging. */
1246 @Test
1247 public void testTimerTracking_Discharging() {
1248 setDischarging();
1249
1250 JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
1251 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1252
1253 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1254
1255 List<TimingSession> expected = new ArrayList<>();
1256
1257 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1258 mQuotaController.prepareForExecutionLocked(jobStatus);
1259 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1260 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1261 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1262 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1263
1264 // Test overlapping jobs.
1265 JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2);
1266 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1267
1268 JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3);
1269 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1270
1271 advanceElapsedClock(SECOND_IN_MILLIS);
1272
1273 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1274 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1275 mQuotaController.prepareForExecutionLocked(jobStatus);
1276 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1277 mQuotaController.prepareForExecutionLocked(jobStatus2);
1278 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1279 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1280 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1281 mQuotaController.prepareForExecutionLocked(jobStatus3);
1282 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1283 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1284 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1285 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1286 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1287 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1288 }
1289
1290 /**
1291 * Tests that TimingSessions are saved properly when the device alternates between
1292 * charging and discharging.
1293 */
1294 @Test
1295 public void testTimerTracking_ChargingAndDischarging() {
1296 JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
1297 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1298 JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
1299 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1300 JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3);
1301 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1302 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1303 List<TimingSession> expected = new ArrayList<>();
1304
1305 // A job starting while charging. Only the portion that runs during the discharging period
1306 // should be counted.
1307 setCharging();
1308
1309 mQuotaController.prepareForExecutionLocked(jobStatus);
1310 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1311 setDischarging();
1312 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1313 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1314 mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
1315 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1316 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1317
1318 advanceElapsedClock(SECOND_IN_MILLIS);
1319
1320 // One job starts while discharging, spans a charging session, and ends after the charging
1321 // session. Only the portions during the discharging periods should be counted. This should
1322 // result in two TimingSessions. A second job starts while discharging and ends within the
1323 // charging session. Only the portion during the first discharging portion should be
1324 // counted. A third job starts and ends within the charging session. The third job
1325 // shouldn't be included in either job count.
1326 setDischarging();
1327 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1328 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1329 mQuotaController.prepareForExecutionLocked(jobStatus);
1330 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1331 mQuotaController.prepareForExecutionLocked(jobStatus2);
1332 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1333 setCharging();
1334 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
1335 mQuotaController.prepareForExecutionLocked(jobStatus3);
1336 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1337 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1338 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1339 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1340 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1341 setDischarging();
1342 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1343 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1344 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1345 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
1346 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1347
1348 // A job starting while discharging and ending while charging. Only the portion that runs
1349 // during the discharging period should be counted.
1350 setDischarging();
1351 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1352 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1353 mQuotaController.prepareForExecutionLocked(jobStatus2);
1354 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1355 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1356 setCharging();
1357 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1358 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1359 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1360 }
1361
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001362 /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
1363 @Test
1364 public void testTimerTracking_AllBackground() {
1365 setDischarging();
1366
1367 JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
1368 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1369
1370 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1371
1372 List<TimingSession> expected = new ArrayList<>();
1373
1374 // Test single job.
1375 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1376 mQuotaController.prepareForExecutionLocked(jobStatus);
1377 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1378 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1379 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1380 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1381
1382 // Test overlapping jobs.
1383 JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
1384 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1385
1386 JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
1387 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1388
1389 advanceElapsedClock(SECOND_IN_MILLIS);
1390
1391 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1392 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1393 mQuotaController.prepareForExecutionLocked(jobStatus);
1394 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1395 mQuotaController.prepareForExecutionLocked(jobStatus2);
1396 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1397 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1398 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1399 mQuotaController.prepareForExecutionLocked(jobStatus3);
1400 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1401 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1402 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1403 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1404 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1405 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1406 }
1407
1408 /** Tests that Timers don't count foreground jobs. */
1409 @Test
1410 public void testTimerTracking_AllForeground() {
1411 setDischarging();
1412
1413 JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001414 setProcessState(ActivityManager.PROCESS_STATE_TOP);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001415 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1416
1417 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1418
1419 mQuotaController.prepareForExecutionLocked(jobStatus);
1420 advanceElapsedClock(5 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001421 // Change to a state that should still be considered foreground.
1422 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1423 advanceElapsedClock(5 * SECOND_IN_MILLIS);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001424 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1425 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1426 }
1427
1428 /**
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001429 * Tests that Timers properly track sessions when switching between foreground and background
1430 * states.
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001431 */
1432 @Test
1433 public void testTimerTracking_ForegroundAndBackground() {
1434 setDischarging();
1435
1436 JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
1437 JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
1438 JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001439 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1440 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1441 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
1442 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1443 List<TimingSession> expected = new ArrayList<>();
1444
1445 // UID starts out inactive.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001446 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001447 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1448 mQuotaController.prepareForExecutionLocked(jobBg1);
1449 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1450 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1451 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1452 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1453
1454 advanceElapsedClock(SECOND_IN_MILLIS);
1455
1456 // Bg job starts while inactive, spans an entire active session, and ends after the
1457 // active session.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001458 // App switching to foreground state then fg job starts.
1459 // App remains in foreground state after coming to foreground, so there should only be one
1460 // session.
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001461 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1462 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1463 mQuotaController.prepareForExecutionLocked(jobBg2);
1464 advanceElapsedClock(10 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001465 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1466 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001467 mQuotaController.prepareForExecutionLocked(jobFg3);
1468 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1469 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1470 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1471 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001472 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1473
1474 advanceElapsedClock(SECOND_IN_MILLIS);
1475
1476 // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
1477 // "inactive" and then bg job 2 starts. Then fg job ends.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001478 // This should result in two TimingSessions:
1479 // * The first should have a count of 1
1480 // * The second should have a count of 2 since it will include both jobs
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001481 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1482 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1483 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1484 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001485 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001486 mQuotaController.prepareForExecutionLocked(jobBg1);
1487 advanceElapsedClock(10 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001488 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1489 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001490 mQuotaController.prepareForExecutionLocked(jobFg3);
1491 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1492 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001493 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
1494 start = JobSchedulerService.sElapsedRealtimeClock.millis();
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001495 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001496 mQuotaController.prepareForExecutionLocked(jobBg2);
1497 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1498 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1499 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1500 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001501 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001502 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1503 }
1504
Kweku Adams4836f9d2018-11-12 17:04:17 -08001505 /**
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001506 * Tests that Timers properly track overlapping top and background jobs.
1507 */
1508 @Test
1509 public void testTimerTracking_TopAndNonTop() {
1510 setDischarging();
1511
1512 JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1);
1513 JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2);
1514 JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3);
1515 JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4);
1516 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1517 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1518 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
1519 mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
1520 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1521 List<TimingSession> expected = new ArrayList<>();
1522
1523 // UID starts out inactive.
1524 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1525 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1526 mQuotaController.prepareForExecutionLocked(jobBg1);
1527 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1528 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1529 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1530 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1531
1532 advanceElapsedClock(SECOND_IN_MILLIS);
1533
1534 // Bg job starts while inactive, spans an entire active session, and ends after the
1535 // active session.
1536 // App switching to top state then fg job starts.
1537 // App remains in top state after coming to top, so there should only be one
1538 // session.
1539 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1540 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1541 mQuotaController.prepareForExecutionLocked(jobBg2);
1542 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1543 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1544 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1545 mQuotaController.prepareForExecutionLocked(jobTop);
1546 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1547 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
1548 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1549 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1550 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1551
1552 advanceElapsedClock(SECOND_IN_MILLIS);
1553
1554 // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
1555 // foreground_service and a new job starts. Shortly after, uid goes
1556 // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
1557 // This should result in two TimingSessions:
1558 // * The first should have a count of 1
1559 // * The second should have a count of 2, which accounts for the bg2 and fg, but not top
1560 // jobs.
1561 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1562 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1563 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1564 mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
1565 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
1566 mQuotaController.prepareForExecutionLocked(jobBg1);
1567 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1568 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1569 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1570 mQuotaController.prepareForExecutionLocked(jobTop);
1571 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1572 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1573 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1574 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1575 mQuotaController.prepareForExecutionLocked(jobFg1);
1576 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1577 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1578 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
1579 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1580 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
1581 mQuotaController.prepareForExecutionLocked(jobBg2);
1582 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1583 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
1584 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1585 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1586 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
1587 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
1588 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1589 }
1590
1591 /**
1592 * Tests that TOP jobs aren't stopped when an app runs out of quota.
1593 */
1594 @Test
1595 public void testTracking_OutOfQuota_ForegroundAndBackground() {
1596 setDischarging();
1597
1598 JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
1599 JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
1600 trackJobs(jobBg, jobTop);
1601 setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
1602 // Now the package only has 20 seconds to run.
1603 final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
1604 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1605 createTimingSession(
1606 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1607 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
1608
1609 InOrder inOrder = inOrder(mJobSchedulerService);
1610
1611 // UID starts out inactive.
1612 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
1613 // Start the job.
1614 mQuotaController.prepareForExecutionLocked(jobBg);
1615 advanceElapsedClock(remainingTimeMs / 2);
1616 // New job starts after UID is in the foreground. Since the app is now in the foreground, it
1617 // should continue to have remainingTimeMs / 2 time remaining.
1618 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1619 mQuotaController.prepareForExecutionLocked(jobTop);
1620 advanceElapsedClock(remainingTimeMs);
1621
1622 // Wait for some extra time to allow for job processing.
1623 inOrder.verify(mJobSchedulerService,
1624 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
1625 .onControllerStateChanged();
1626 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg));
1627 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop));
1628 // Go to a background state.
1629 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
1630 advanceElapsedClock(remainingTimeMs / 2 + 1);
1631 inOrder.verify(mJobSchedulerService,
1632 timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
1633 .onControllerStateChanged();
1634 // Top job should still be allowed to run.
1635 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1636 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1637
1638 // New jobs to run.
1639 JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
1640 JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
1641 JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
1642 setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
1643
1644 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1645 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1646 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
1647 .onControllerStateChanged();
1648 trackJobs(jobFg, jobTop);
1649 mQuotaController.prepareForExecutionLocked(jobTop);
1650 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1651 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1652 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1653
1654 // App still in foreground so everything should be in quota.
1655 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1656 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1657 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1658 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1659 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1660
1661 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1662 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
1663 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
1664 .onControllerStateChanged();
1665 // App is now in background and out of quota. Fg should now change to out of quota since it
1666 // wasn't started. Top should remain in quota since it started when the app was in TOP.
1667 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1668 assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1669 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1670 trackJobs(jobBg2);
1671 assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1672 }
1673
1674 /**
Kweku Adams4836f9d2018-11-12 17:04:17 -08001675 * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
1676 * its quota.
1677 */
1678 @Test
1679 public void testTracking_OutOfQuota() {
1680 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
1681 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1682 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
1683 // Now the package only has two seconds to run.
1684 final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
1685 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1686 createTimingSession(
1687 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1688 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
1689
1690 // Start the job.
1691 mQuotaController.prepareForExecutionLocked(jobStatus);
1692 advanceElapsedClock(remainingTimeMs);
1693
1694 // Wait for some extra time to allow for job processing.
1695 verify(mJobSchedulerService,
1696 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
1697 .onControllerStateChanged();
1698 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1699 }
1700
1701 /**
1702 * Tests that a job is properly handled when it's at the edge of its quota and the old quota is
1703 * being phased out.
1704 */
1705 @Test
1706 public void testTracking_RollingQuota() {
1707 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
1708 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1709 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
1710 Handler handler = mQuotaController.getHandler();
1711 spyOn(handler);
1712
1713 long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1714 final long remainingTimeMs = SECOND_IN_MILLIS;
1715 // The package only has one second to run, but this session is at the edge of the rolling
1716 // window, so as the package "reaches its quota" it will have more to keep running.
1717 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1718 createTimingSession(now - 2 * HOUR_IN_MILLIS,
1719 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
1720
1721 assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
1722 // Start the job.
1723 mQuotaController.prepareForExecutionLocked(jobStatus);
1724 advanceElapsedClock(remainingTimeMs);
1725
1726 // Wait for some extra time to allow for job processing.
1727 verify(mJobSchedulerService,
1728 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
1729 .onControllerStateChanged();
1730 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1731 // The job used up the remaining quota, but in that time, the same amount of time in the
1732 // old TimingSession also fell out of the quota window, so it should still have the same
1733 // amount of remaining time left its quota.
1734 assertEquals(remainingTimeMs,
1735 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1736 verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(remainingTimeMs));
1737 }
1738}