blob: cad71a26a76b721b54136d5d5e9be7f548103bd5 [file] [log] [blame]
Kweku Adams4836f9d2018-11-12 17:04:17 -08001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.job.controllers;
18
19import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
20import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
21import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
22import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
23import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
24import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
25import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
26import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX;
27import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX;
Kweku Adams288e73b2019-01-17 13:53:24 -080028import static com.android.server.job.JobSchedulerService.NEVER_INDEX;
Kweku Adams4836f9d2018-11-12 17:04:17 -080029import static com.android.server.job.JobSchedulerService.RARE_INDEX;
30import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
31
32import static org.junit.Assert.assertEquals;
33import static org.junit.Assert.assertFalse;
Kweku Adams045fb5722018-12-11 14:29:10 -080034import static org.junit.Assert.assertNotEquals;
Kweku Adams4836f9d2018-11-12 17:04:17 -080035import static org.junit.Assert.assertNull;
36import static org.junit.Assert.assertTrue;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080037import static org.junit.Assert.fail;
Kweku Adams4836f9d2018-11-12 17:04:17 -080038import static org.mockito.ArgumentMatchers.any;
39import static org.mockito.ArgumentMatchers.anyInt;
40import static org.mockito.ArgumentMatchers.anyLong;
41import static org.mockito.Mockito.atLeast;
42import static org.mockito.Mockito.eq;
43import static org.mockito.Mockito.never;
44import static org.mockito.Mockito.timeout;
45import static org.mockito.Mockito.times;
46import static org.mockito.Mockito.verify;
47
Kweku Adamsd6625ff2019-01-10 12:06:21 -080048import android.app.ActivityManager;
49import android.app.ActivityManagerInternal;
Kweku Adams4836f9d2018-11-12 17:04:17 -080050import android.app.AlarmManager;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080051import android.app.AppGlobals;
52import android.app.IActivityManager;
53import android.app.IUidObserver;
Kweku Adams4836f9d2018-11-12 17:04:17 -080054import android.app.job.JobInfo;
55import android.app.usage.UsageStatsManager;
56import android.app.usage.UsageStatsManagerInternal;
57import android.content.BroadcastReceiver;
58import android.content.ComponentName;
59import android.content.Context;
60import android.content.Intent;
61import android.content.pm.PackageManagerInternal;
62import android.os.BatteryManager;
63import android.os.BatteryManagerInternal;
64import android.os.Handler;
65import android.os.Looper;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080066import android.os.RemoteException;
Kweku Adams4836f9d2018-11-12 17:04:17 -080067import android.os.SystemClock;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080068import android.util.SparseBooleanArray;
Kweku Adams4836f9d2018-11-12 17:04:17 -080069
70import androidx.test.runner.AndroidJUnit4;
71
72import com.android.server.LocalServices;
73import com.android.server.job.JobSchedulerService;
74import com.android.server.job.JobSchedulerService.Constants;
Kweku Adamsd6625ff2019-01-10 12:06:21 -080075import com.android.server.job.JobStore;
Kweku Adams045fb5722018-12-11 14:29:10 -080076import com.android.server.job.controllers.QuotaController.ExecutionStats;
Kweku Adams4836f9d2018-11-12 17:04:17 -080077import com.android.server.job.controllers.QuotaController.TimingSession;
78
79import org.junit.After;
80import org.junit.Before;
81import org.junit.Test;
82import org.junit.runner.RunWith;
83import org.mockito.ArgumentCaptor;
84import org.mockito.InOrder;
85import org.mockito.Mock;
86import org.mockito.MockitoSession;
87import org.mockito.quality.Strictness;
88
89import java.time.Clock;
90import java.time.Duration;
91import java.time.ZoneOffset;
92import java.util.ArrayList;
93import java.util.List;
94
95@RunWith(AndroidJUnit4.class)
96public class QuotaControllerTest {
97 private static final long SECOND_IN_MILLIS = 1000L;
98 private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
99 private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
100 private static final String TAG_CLEANUP = "*job.cleanup*";
101 private static final String TAG_QUOTA_CHECK = "*job.quota_check*";
Kweku Adams4836f9d2018-11-12 17:04:17 -0800102 private static final int CALLING_UID = 1000;
103 private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests";
104 private static final int SOURCE_USER_ID = 0;
105
106 private BroadcastReceiver mChargingReceiver;
107 private Constants mConstants;
108 private QuotaController mQuotaController;
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800109 private int mSourceUid;
110 private IUidObserver mUidObserver;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800111
112 private MockitoSession mMockingSession;
113 @Mock
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800114 private ActivityManagerInternal mActivityMangerInternal;
115 @Mock
Kweku Adams4836f9d2018-11-12 17:04:17 -0800116 private AlarmManager mAlarmManager;
117 @Mock
118 private Context mContext;
119 @Mock
120 private JobSchedulerService mJobSchedulerService;
121 @Mock
122 private UsageStatsManagerInternal mUsageStatsManager;
123
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800124 private JobStore mJobStore;
125
Kweku Adams4836f9d2018-11-12 17:04:17 -0800126 @Before
127 public void setUp() {
128 mMockingSession = mockitoSession()
129 .initMocks(this)
130 .strictness(Strictness.LENIENT)
131 .mockStatic(LocalServices.class)
132 .startMocking();
133 // Make sure constants turn on QuotaController.
134 mConstants = new Constants();
135 mConstants.USE_HEARTBEATS = false;
136
137 // Called in StateController constructor.
138 when(mJobSchedulerService.getTestableContext()).thenReturn(mContext);
139 when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService);
140 when(mJobSchedulerService.getConstants()).thenReturn(mConstants);
141 // Called in QuotaController constructor.
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800142 IActivityManager activityManager = ActivityManager.getService();
143 spyOn(activityManager);
144 try {
145 doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any());
146 } catch (RemoteException e) {
147 fail("registerUidObserver threw exception: " + e.getMessage());
148 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800149 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
150 when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800151 doReturn(mActivityMangerInternal)
152 .when(() -> LocalServices.getService(ActivityManagerInternal.class));
Kweku Adams4836f9d2018-11-12 17:04:17 -0800153 doReturn(mock(BatteryManagerInternal.class))
154 .when(() -> LocalServices.getService(BatteryManagerInternal.class));
155 doReturn(mUsageStatsManager)
156 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class));
157 // Used in JobStatus.
158 doReturn(mock(PackageManagerInternal.class))
159 .when(() -> LocalServices.getService(PackageManagerInternal.class));
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800160 // Used in QuotaController.Handler.
161 mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir());
162 when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800163
Kweku Adams045fb5722018-12-11 14:29:10 -0800164 // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
165 // in the past, and QuotaController sometimes floors values at 0, so if the test time
166 // causes sessions with negative timestamps, they will fail.
Kweku Adams4836f9d2018-11-12 17:04:17 -0800167 JobSchedulerService.sSystemClock =
Kweku Adams045fb5722018-12-11 14:29:10 -0800168 getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC),
169 24 * HOUR_IN_MILLIS);
170 JobSchedulerService.sUptimeMillisClock = getAdvancedClock(
171 Clock.fixed(SystemClock.uptimeMillisClock().instant(), ZoneOffset.UTC),
172 24 * HOUR_IN_MILLIS);
173 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
174 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC),
175 24 * HOUR_IN_MILLIS);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800176
177 // Initialize real objects.
178 // Capture the listeners.
179 ArgumentCaptor<BroadcastReceiver> receiverCaptor =
180 ArgumentCaptor.forClass(BroadcastReceiver.class);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800181 ArgumentCaptor<IUidObserver> uidObserverCaptor =
182 ArgumentCaptor.forClass(IUidObserver.class);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800183 mQuotaController = new QuotaController(mJobSchedulerService);
184
185 verify(mContext).registerReceiver(receiverCaptor.capture(), any());
186 mChargingReceiver = receiverCaptor.getValue();
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800187 try {
188 verify(activityManager).registerUidObserver(
189 uidObserverCaptor.capture(),
190 eq(ActivityManager.UID_OBSERVER_PROCSTATE),
191 eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE),
192 any());
193 mUidObserver = uidObserverCaptor.getValue();
194 mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0);
195 } catch (RemoteException e) {
196 fail(e.getMessage());
197 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800198 }
199
200 @After
201 public void tearDown() {
202 if (mMockingSession != null) {
203 mMockingSession.finishMocking();
204 }
205 }
206
207 private Clock getAdvancedClock(Clock clock, long incrementMs) {
208 return Clock.offset(clock, Duration.ofMillis(incrementMs));
209 }
210
211 private void advanceElapsedClock(long incrementMs) {
212 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock(
213 JobSchedulerService.sElapsedRealtimeClock, incrementMs);
214 }
215
216 private void setCharging() {
217 Intent intent = new Intent(BatteryManager.ACTION_CHARGING);
218 mChargingReceiver.onReceive(mContext, intent);
219 }
220
221 private void setDischarging() {
222 Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING);
223 mChargingReceiver.onReceive(mContext, intent);
224 }
225
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800226 private void setProcessState(int procState) {
227 try {
228 doReturn(procState).when(mActivityMangerInternal).getUidProcessState(mSourceUid);
229 SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids();
230 spyOn(foregroundUids);
231 mUidObserver.onUidStateChanged(mSourceUid, procState, 0);
232 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
233 verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1))
234 .put(eq(mSourceUid), eq(true));
235 assertTrue(foregroundUids.get(mSourceUid));
236 } else {
237 verify(foregroundUids, timeout(SECOND_IN_MILLIS).times(1)).delete(eq(mSourceUid));
238 assertFalse(foregroundUids.get(mSourceUid));
239 }
240 } catch (RemoteException e) {
241 fail("registerUidObserver threw exception: " + e.getMessage());
242 }
243 }
244
Kweku Adams4836f9d2018-11-12 17:04:17 -0800245 private void setStandbyBucket(int bucketIndex) {
246 int bucket;
247 switch (bucketIndex) {
248 case ACTIVE_INDEX:
249 bucket = UsageStatsManager.STANDBY_BUCKET_ACTIVE;
250 break;
251 case WORKING_INDEX:
252 bucket = UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
253 break;
254 case FREQUENT_INDEX:
255 bucket = UsageStatsManager.STANDBY_BUCKET_FREQUENT;
256 break;
257 case RARE_INDEX:
258 bucket = UsageStatsManager.STANDBY_BUCKET_RARE;
259 break;
260 default:
261 bucket = UsageStatsManager.STANDBY_BUCKET_NEVER;
262 }
263 when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID),
264 anyLong())).thenReturn(bucket);
265 }
266
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800267 private void setStandbyBucket(int bucketIndex, JobStatus... jobs) {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800268 setStandbyBucket(bucketIndex);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800269 for (JobStatus job : jobs) {
270 job.setStandbyBucket(bucketIndex);
271 }
272 }
273
274 private void trackJobs(JobStatus... jobs) {
275 for (JobStatus job : jobs) {
276 mJobStore.add(job);
277 mQuotaController.maybeStartTrackingJobLocked(job, null);
278 }
Kweku Adams4836f9d2018-11-12 17:04:17 -0800279 }
280
281 private JobStatus createJobStatus(String testTag, int jobId) {
282 JobInfo jobInfo = new JobInfo.Builder(jobId,
283 new ComponentName(mContext, "TestQuotaJobService"))
284 .setMinimumLatency(Math.abs(jobId) + 1)
285 .build();
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800286 JobStatus js = JobStatus.createFromJobInfo(
Kweku Adams4836f9d2018-11-12 17:04:17 -0800287 jobInfo, CALLING_UID, SOURCE_PACKAGE, SOURCE_USER_ID, testTag);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800288 // Make sure tests aren't passing just because the default bucket is likely ACTIVE.
289 js.setStandbyBucket(FREQUENT_INDEX);
290 return js;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800291 }
292
293 private TimingSession createTimingSession(long start, long duration, int count) {
294 return new TimingSession(start, start + duration, count);
295 }
296
297 @Test
298 public void testSaveTimingSession() {
299 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
300
301 List<TimingSession> expected = new ArrayList<>();
302 TimingSession one = new TimingSession(1, 10, 1);
303 TimingSession two = new TimingSession(11, 20, 2);
304 TimingSession thr = new TimingSession(21, 30, 3);
305
306 mQuotaController.saveTimingSession(0, "com.android.test", one);
307 expected.add(one);
308 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
309
310 mQuotaController.saveTimingSession(0, "com.android.test", two);
311 expected.add(two);
312 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
313
314 mQuotaController.saveTimingSession(0, "com.android.test", thr);
315 expected.add(thr);
316 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
317 }
318
319 @Test
320 public void testDeleteObsoleteSessionsLocked() {
321 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
322 TimingSession one = createTimingSession(
323 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
324 TimingSession two = createTimingSession(
325 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
326 TimingSession thr = createTimingSession(
327 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
328 // Overlaps 24 hour boundary.
329 TimingSession fou = createTimingSession(
330 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1);
331 // Way past the 24 hour boundary.
332 TimingSession fiv = createTimingSession(
333 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4);
334 List<TimingSession> expected = new ArrayList<>();
335 // Added in correct (chronological) order.
336 expected.add(fou);
337 expected.add(thr);
338 expected.add(two);
339 expected.add(one);
340 mQuotaController.saveTimingSession(0, "com.android.test", fiv);
341 mQuotaController.saveTimingSession(0, "com.android.test", fou);
342 mQuotaController.saveTimingSession(0, "com.android.test", thr);
343 mQuotaController.saveTimingSession(0, "com.android.test", two);
344 mQuotaController.saveTimingSession(0, "com.android.test", one);
345
346 mQuotaController.deleteObsoleteSessionsLocked();
347
348 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test"));
349 }
350
351 @Test
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800352 public void testOnAppRemovedLocked() {
353 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
354 mQuotaController.saveTimingSession(0, "com.android.test.remove",
355 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
356 mQuotaController.saveTimingSession(0, "com.android.test.remove",
357 createTimingSession(
358 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
359 mQuotaController.saveTimingSession(0, "com.android.test.remove",
360 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
361 // Test that another app isn't affected.
362 TimingSession one = createTimingSession(
363 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
364 TimingSession two = createTimingSession(
365 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
366 List<TimingSession> expected = new ArrayList<>();
367 // Added in correct (chronological) order.
368 expected.add(two);
369 expected.add(one);
370 mQuotaController.saveTimingSession(0, "com.android.test.stay", two);
371 mQuotaController.saveTimingSession(0, "com.android.test.stay", one);
372
Kweku Adams045fb5722018-12-11 14:29:10 -0800373 ExecutionStats expectedStats = new ExecutionStats();
Kweku Adams288e73b2019-01-17 13:53:24 -0800374 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800375 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
376
Kweku Adams288e73b2019-01-17 13:53:24 -0800377 final int uid = 10001;
378 mQuotaController.onAppRemovedLocked("com.android.test.remove", uid);
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800379 assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove"));
380 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800381 assertEquals(expectedStats,
382 mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX));
383 assertNotEquals(expectedStats,
384 mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX));
Kweku Adams288e73b2019-01-17 13:53:24 -0800385
386 assertFalse(mQuotaController.getForegroundUids().get(uid));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800387 }
388
389 @Test
390 public void testOnUserRemovedLocked() {
391 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
392 mQuotaController.saveTimingSession(0, "com.android.test",
393 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
394 mQuotaController.saveTimingSession(0, "com.android.test",
395 createTimingSession(
396 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
397 mQuotaController.saveTimingSession(0, "com.android.test",
398 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
399 // Test that another user isn't affected.
400 TimingSession one = createTimingSession(
401 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3);
402 TimingSession two = createTimingSession(
403 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1);
404 List<TimingSession> expected = new ArrayList<>();
405 // Added in correct (chronological) order.
406 expected.add(two);
407 expected.add(one);
408 mQuotaController.saveTimingSession(10, "com.android.test", two);
409 mQuotaController.saveTimingSession(10, "com.android.test", one);
410
Kweku Adams045fb5722018-12-11 14:29:10 -0800411 ExecutionStats expectedStats = new ExecutionStats();
Kweku Adams288e73b2019-01-17 13:53:24 -0800412 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800413 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
414
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800415 mQuotaController.onUserRemovedLocked(0);
416 assertNull(mQuotaController.getTimingSessions(0, "com.android.test"));
417 assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test"));
Kweku Adams045fb5722018-12-11 14:29:10 -0800418 assertEquals(expectedStats,
419 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
420 assertNotEquals(expectedStats,
421 mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX));
Kweku Adamsa9f8e1f2018-12-12 16:06:54 -0800422 }
423
424 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -0800425 public void testUpdateExecutionStatsLocked_NoTimer() {
Kweku Adams4836f9d2018-11-12 17:04:17 -0800426 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
427 // Added in chronological order.
428 mQuotaController.saveTimingSession(0, "com.android.test",
429 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
430 mQuotaController.saveTimingSession(0, "com.android.test",
431 createTimingSession(
432 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5));
433 mQuotaController.saveTimingSession(0, "com.android.test",
434 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1));
435 mQuotaController.saveTimingSession(0, "com.android.test",
436 createTimingSession(
437 now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1));
438 mQuotaController.saveTimingSession(0, "com.android.test",
439 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
440
Kweku Adams045fb5722018-12-11 14:29:10 -0800441 // Test an app that hasn't had any activity.
442 ExecutionStats expectedStats = new ExecutionStats();
443 ExecutionStats inputStats = new ExecutionStats();
444
445 inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
446 // Invalid time is now +24 hours since there are no sessions at all for the app.
Kweku Adams288e73b2019-01-17 13:53:24 -0800447 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800448 mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats);
449 assertEquals(expectedStats, inputStats);
450
451 inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
452 // Invalid time is now +18 hours since there are no sessions in the window but the earliest
453 // session is 6 hours ago.
Kweku Adams288e73b2019-01-17 13:53:24 -0800454 expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800455 expectedStats.executionTimeInWindowMs = 0;
456 expectedStats.bgJobCountInWindow = 0;
457 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
458 expectedStats.bgJobCountInMaxPeriod = 15;
459 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
460 assertEquals(expectedStats, inputStats);
461
462 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
463 // Invalid time is now since the session straddles the window cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800464 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800465 expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS;
466 expectedStats.bgJobCountInWindow = 3;
467 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
468 expectedStats.bgJobCountInMaxPeriod = 15;
469 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
470 assertEquals(expectedStats, inputStats);
471
472 inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS;
473 // Invalid time is now since the start of the session is at the very edge of the window
474 // cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800475 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800476 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
477 expectedStats.bgJobCountInWindow = 3;
478 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
479 expectedStats.bgJobCountInMaxPeriod = 15;
480 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
481 assertEquals(expectedStats, inputStats);
482
483 inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
484 // Invalid time is now +44 minutes since the earliest session in the window is now-5
485 // minutes.
Kweku Adams288e73b2019-01-17 13:53:24 -0800486 expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800487 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS;
488 expectedStats.bgJobCountInWindow = 3;
489 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
490 expectedStats.bgJobCountInMaxPeriod = 15;
491 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
492 assertEquals(expectedStats, inputStats);
493
494 inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
495 // Invalid time is now since the session is at the very edge of the window cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800496 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800497 expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS;
498 expectedStats.bgJobCountInWindow = 4;
499 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
500 expectedStats.bgJobCountInMaxPeriod = 15;
501 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
502 assertEquals(expectedStats, inputStats);
503
504 inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
505 // Invalid time is now since the start of the session is at the very edge of the window
506 // cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800507 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800508 expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS;
509 expectedStats.bgJobCountInWindow = 5;
510 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
511 expectedStats.bgJobCountInMaxPeriod = 15;
512 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
513 assertEquals(expectedStats, inputStats);
514
515 inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
516 // Invalid time is now since the session straddles the window cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800517 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800518 expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS;
519 expectedStats.bgJobCountInWindow = 10;
520 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
521 expectedStats.bgJobCountInMaxPeriod = 15;
522 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
523 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
524 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
525 assertEquals(expectedStats, inputStats);
526
527 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS;
528 // Invalid time is now +59 minutes since the earliest session in the window is now-121
529 // minutes.
Kweku Adams288e73b2019-01-17 13:53:24 -0800530 expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800531 expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS;
532 expectedStats.bgJobCountInWindow = 10;
533 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
534 expectedStats.bgJobCountInMaxPeriod = 15;
535 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
536 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
537 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
538 assertEquals(expectedStats, inputStats);
539
540 inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
541 // Invalid time is now since the start of the session is at the very edge of the window
542 // cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800543 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800544 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
545 expectedStats.bgJobCountInWindow = 15;
546 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS;
547 expectedStats.bgJobCountInMaxPeriod = 15;
548 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
549 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
550 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
551 assertEquals(expectedStats, inputStats);
552
Kweku Adams288e73b2019-01-17 13:53:24 -0800553 // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period.
Kweku Adams045fb5722018-12-11 14:29:10 -0800554 mQuotaController.getTimingSessions(0, "com.android.test")
555 .add(0,
556 createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3));
557 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
558 // Invalid time is now +1 hour since the earliest session in the max period is 1 hour
559 // before the end of the max period cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800560 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800561 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
562 expectedStats.bgJobCountInWindow = 15;
563 expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS;
564 expectedStats.bgJobCountInMaxPeriod = 18;
565 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
566 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
567 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
568 assertEquals(expectedStats, inputStats);
569
570 mQuotaController.getTimingSessions(0, "com.android.test")
571 .add(0,
572 createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS),
573 2 * MINUTE_IN_MILLIS, 2));
574 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
575 // Invalid time is now since the earlist session straddles the max period cutoff time.
Kweku Adams288e73b2019-01-17 13:53:24 -0800576 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800577 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS;
578 expectedStats.bgJobCountInWindow = 15;
579 expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS;
580 expectedStats.bgJobCountInMaxPeriod = 20;
581 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS)
582 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
583 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats);
584 assertEquals(expectedStats, inputStats);
585 }
586
587 /**
588 * Tests that getExecutionStatsLocked returns the correct stats.
589 */
590 @Test
591 public void testGetExecutionStatsLocked_Values() {
592 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
593 mQuotaController.saveTimingSession(0, "com.android.test",
594 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
595 mQuotaController.saveTimingSession(0, "com.android.test",
596 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
597 mQuotaController.saveTimingSession(0, "com.android.test",
598 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
599 mQuotaController.saveTimingSession(0, "com.android.test",
600 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
601
602 ExecutionStats expectedStats = new ExecutionStats();
603
604 // Active
605 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800606 expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800607 expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS;
608 expectedStats.bgJobCountInWindow = 5;
609 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
610 expectedStats.bgJobCountInMaxPeriod = 20;
611 assertEquals(expectedStats,
612 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX));
613
614 // Working
615 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800616 expectedStats.expirationTimeElapsed = now;
Kweku Adams045fb5722018-12-11 14:29:10 -0800617 expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS;
618 expectedStats.bgJobCountInWindow = 10;
619 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
620 expectedStats.bgJobCountInMaxPeriod = 20;
621 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
622 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
623 assertEquals(expectedStats,
624 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX));
625
626 // Frequent
627 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800628 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800629 expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS;
630 expectedStats.bgJobCountInWindow = 15;
631 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
632 expectedStats.bgJobCountInMaxPeriod = 20;
633 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
634 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
635 assertEquals(expectedStats,
636 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX));
637
638 // Rare
639 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -0800640 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -0800641 expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS;
642 expectedStats.bgJobCountInWindow = 20;
643 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS;
644 expectedStats.bgJobCountInMaxPeriod = 20;
645 expectedStats.quotaCutoffTimeElapsed = now - (2 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS)
646 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
647 assertEquals(expectedStats,
648 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX));
649 }
650
651 /**
652 * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object.
653 */
654 @Test
655 public void testGetExecutionStatsLocked_Caching() {
656 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
657 mQuotaController.saveTimingSession(0, "com.android.test",
658 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
659 mQuotaController.saveTimingSession(0, "com.android.test",
660 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
661 mQuotaController.saveTimingSession(0, "com.android.test",
662 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5));
663 mQuotaController.saveTimingSession(0, "com.android.test",
664 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
665 final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0,
666 "com.android.test", ACTIVE_INDEX);
667 final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0,
668 "com.android.test", WORKING_INDEX);
669 final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
670 "com.android.test", FREQUENT_INDEX);
671 final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0,
672 "com.android.test", RARE_INDEX);
673
674 // Advance clock so that the working stats shouldn't be the same.
675 advanceElapsedClock(MINUTE_IN_MILLIS);
676 // Change frequent bucket size so that the stats need to be recalculated.
677 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS;
678 mQuotaController.onConstantsUpdatedLocked();
679
680 ExecutionStats expectedStats = new ExecutionStats();
681 expectedStats.windowSizeMs = originalStatsActive.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800682 expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800683 expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs;
684 expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow;
685 expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs;
686 expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod;
687 expectedStats.quotaCutoffTimeElapsed = originalStatsActive.quotaCutoffTimeElapsed;
688 final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0,
689 "com.android.test", ACTIVE_INDEX);
690 // Stats for the same bucket should use the same object.
691 assertTrue(originalStatsActive == newStatsActive);
692 assertEquals(expectedStats, newStatsActive);
693
694 expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800695 expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800696 expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs;
697 expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow;
698 expectedStats.quotaCutoffTimeElapsed = originalStatsWorking.quotaCutoffTimeElapsed;
699 final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0,
700 "com.android.test", WORKING_INDEX);
701 assertTrue(originalStatsWorking == newStatsWorking);
702 assertNotEquals(expectedStats, newStatsWorking);
703
704 expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800705 expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800706 expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs;
707 expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow;
708 expectedStats.quotaCutoffTimeElapsed = originalStatsFrequent.quotaCutoffTimeElapsed;
709 final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0,
710 "com.android.test", FREQUENT_INDEX);
711 assertTrue(originalStatsFrequent == newStatsFrequent);
712 assertNotEquals(expectedStats, newStatsFrequent);
713
714 expectedStats.windowSizeMs = originalStatsRare.windowSizeMs;
Kweku Adams288e73b2019-01-17 13:53:24 -0800715 expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed;
Kweku Adams045fb5722018-12-11 14:29:10 -0800716 expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs;
717 expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow;
718 expectedStats.quotaCutoffTimeElapsed = originalStatsRare.quotaCutoffTimeElapsed;
719 final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0,
720 "com.android.test", RARE_INDEX);
721 assertTrue(originalStatsRare == newStatsRare);
722 assertEquals(expectedStats, newStatsRare);
Kweku Adams4836f9d2018-11-12 17:04:17 -0800723 }
724
725 @Test
Kweku Adams288e73b2019-01-17 13:53:24 -0800726 public void testIsWithinQuotaLocked_NeverApp() {
727 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX));
728 }
729
730 @Test
731 public void testIsWithinQuotaLocked_Charging() {
732 setCharging();
733 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX));
734 }
735
736 @Test
737 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() {
738 setDischarging();
739 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
740 mQuotaController.saveTimingSession(0, "com.android.test",
741 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
742 mQuotaController.saveTimingSession(0, "com.android.test",
743 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
744 mQuotaController.incrementJobCount(0, "com.android.test", 5);
745 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
746 }
747
748 @Test
749 public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() {
750 setDischarging();
751 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
752 final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
753 mQuotaController.saveTimingSession(0, "com.android.test.spam",
754 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
755 mQuotaController.saveTimingSession(0, "com.android.test.spam",
756 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
757 mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount);
758 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam",
759 WORKING_INDEX));
760
761 mQuotaController.saveTimingSession(0, "com.android.test.frequent",
762 createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000));
763 mQuotaController.saveTimingSession(0, "com.android.test.frequent",
764 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500));
765 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent",
766 FREQUENT_INDEX));
767 }
768
769 @Test
770 public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() {
771 setDischarging();
772 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
773 mQuotaController.saveTimingSession(0, "com.android.test",
774 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
775 mQuotaController.saveTimingSession(0, "com.android.test",
776 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5));
777 mQuotaController.saveTimingSession(0, "com.android.test",
778 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5));
779 mQuotaController.incrementJobCount(0, "com.android.test", 5);
780 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
781 }
782
783 @Test
784 public void testIsWithinQuotaLocked_OverDuration_OverJobCount() {
785 setDischarging();
786 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
787 final int jobCount = mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME;
788 mQuotaController.saveTimingSession(0, "com.android.test",
789 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25));
790 mQuotaController.saveTimingSession(0, "com.android.test",
791 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount));
792 mQuotaController.incrementJobCount(0, "com.android.test", jobCount);
793 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX));
794 }
795
796 @Test
Kweku Adams4836f9d2018-11-12 17:04:17 -0800797 public void testMaybeScheduleCleanupAlarmLocked() {
798 // No sessions saved yet.
799 mQuotaController.maybeScheduleCleanupAlarmLocked();
800 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
801
802 // Test with only one timing session saved.
803 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
804 final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
805 mQuotaController.saveTimingSession(0, "com.android.test",
806 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1));
807 mQuotaController.maybeScheduleCleanupAlarmLocked();
808 verify(mAlarmManager, times(1))
809 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
810
811 // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
812 mQuotaController.saveTimingSession(0, "com.android.test",
813 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
814 mQuotaController.saveTimingSession(0, "com.android.test",
815 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
816 mQuotaController.maybeScheduleCleanupAlarmLocked();
817 verify(mAlarmManager, times(1))
818 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
819 }
820
821 @Test
Kweku Adams045fb5722018-12-11 14:29:10 -0800822 public void testMaybeScheduleStartAlarmLocked_Active() {
823 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
824 // because it schedules an alarm too. Prevent it from doing so.
825 spyOn(mQuotaController);
826 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
827
828 // Active window size is 10 minutes.
829 final int standbyBucket = ACTIVE_INDEX;
Kweku Adams288e73b2019-01-17 13:53:24 -0800830 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND);
Kweku Adams045fb5722018-12-11 14:29:10 -0800831
832 // No sessions saved yet.
833 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
834 standbyBucket);
835 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
836
837 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
838 // Test with timing sessions out of window but still under max execution limit.
839 final long expectedAlarmTime =
840 (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS
841 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
842 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
843 createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
844 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
845 createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
846 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
847 createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1));
848 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
849 standbyBucket);
850 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
851
852 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
853 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1));
854 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
855 standbyBucket);
856 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
857
858 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1);
Kweku Adamsd6625ff2019-01-10 12:06:21 -0800859 setStandbyBucket(standbyBucket, jobStatus);
Kweku Adams045fb5722018-12-11 14:29:10 -0800860 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
861 mQuotaController.prepareForExecutionLocked(jobStatus);
862 advanceElapsedClock(5 * MINUTE_IN_MILLIS);
863 // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window
864 // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no
865 // longer has quota.
866 assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
867 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
868 standbyBucket);
869 verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK),
870 any(), any());
871 }
872
873 @Test
Kweku Adams4836f9d2018-11-12 17:04:17 -0800874 public void testMaybeScheduleStartAlarmLocked_WorkingSet() {
875 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
876 // because it schedules an alarm too. Prevent it from doing so.
877 spyOn(mQuotaController);
878 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
879
880 // Working set window size is 2 hours.
881 final int standbyBucket = WORKING_INDEX;
882
883 // No sessions saved yet.
884 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
885 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
886
887 // Test with timing sessions out of window.
888 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
889 mQuotaController.saveTimingSession(0, "com.android.test",
890 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
891 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
892 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
893
894 // Test with timing sessions in window but still in quota.
895 final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
896 // Counting backwards, the quota will come back one minute before the end.
897 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800898 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS
899 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800900 mQuotaController.saveTimingSession(0, "com.android.test",
901 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1));
902 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
903 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
904
905 // Add some more sessions, but still in quota.
906 mQuotaController.saveTimingSession(0, "com.android.test",
907 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
908 mQuotaController.saveTimingSession(0, "com.android.test",
909 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1));
910 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
911 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
912
913 // Test when out of quota.
914 mQuotaController.saveTimingSession(0, "com.android.test",
915 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
916 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
917 verify(mAlarmManager, times(1))
918 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
919
920 // Alarm already scheduled, so make sure it's not scheduled again.
921 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
922 verify(mAlarmManager, times(1))
923 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
924 }
925
926 @Test
927 public void testMaybeScheduleStartAlarmLocked_Frequent() {
928 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
929 // because it schedules an alarm too. Prevent it from doing so.
930 spyOn(mQuotaController);
931 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
932
933 // Frequent window size is 8 hours.
934 final int standbyBucket = FREQUENT_INDEX;
935
936 // No sessions saved yet.
937 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
938 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
939
940 // Test with timing sessions out of window.
941 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
942 mQuotaController.saveTimingSession(0, "com.android.test",
943 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
944 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
945 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
946
947 // Test with timing sessions in window but still in quota.
948 final long start = now - (6 * HOUR_IN_MILLIS);
Kweku Adamscdbfcb92018-12-06 17:05:15 -0800949 final long expectedAlarmTime =
950 start + 8 * HOUR_IN_MILLIS + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -0800951 mQuotaController.saveTimingSession(0, "com.android.test",
952 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
953 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
954 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
955
956 // Add some more sessions, but still in quota.
957 mQuotaController.saveTimingSession(0, "com.android.test",
958 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
959 mQuotaController.saveTimingSession(0, "com.android.test",
960 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
961 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
962 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
963
964 // Test when out of quota.
965 mQuotaController.saveTimingSession(0, "com.android.test",
966 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
967 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
968 verify(mAlarmManager, times(1))
969 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
970
971 // Alarm already scheduled, so make sure it's not scheduled again.
972 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
973 verify(mAlarmManager, times(1))
974 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
975 }
976
977 @Test
978 public void testMaybeScheduleStartAlarmLocked_Rare() {
979 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
980 // because it schedules an alarm too. Prevent it from doing so.
981 spyOn(mQuotaController);
982 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
983
984 // Rare window size is 24 hours.
985 final int standbyBucket = RARE_INDEX;
986
987 // No sessions saved yet.
988 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
989 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
990
991 // Test with timing sessions out of window.
992 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
993 mQuotaController.saveTimingSession(0, "com.android.test",
994 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1));
995 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
996 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
997
998 // Test with timing sessions in window but still in quota.
999 final long start = now - (6 * HOUR_IN_MILLIS);
1000 // Counting backwards, the first minute in the session is over the allowed time, so it
1001 // needs to be excluded.
1002 final long expectedAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001003 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS
1004 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001005 mQuotaController.saveTimingSession(0, "com.android.test",
1006 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1));
1007 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1008 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1009
1010 // Add some more sessions, but still in quota.
1011 mQuotaController.saveTimingSession(0, "com.android.test",
1012 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1));
1013 mQuotaController.saveTimingSession(0, "com.android.test",
1014 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1));
1015 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1016 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1017
1018 // Test when out of quota.
1019 mQuotaController.saveTimingSession(0, "com.android.test",
1020 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1));
1021 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1022 verify(mAlarmManager, times(1))
1023 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1024
1025 // Alarm already scheduled, so make sure it's not scheduled again.
1026 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
1027 verify(mAlarmManager, times(1))
1028 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1029 }
1030
1031 /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
1032 @Test
1033 public void testMaybeScheduleStartAlarmLocked_BucketChange() {
1034 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1035 // because it schedules an alarm too. Prevent it from doing so.
1036 spyOn(mQuotaController);
1037 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1038
1039 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1040
1041 // Affects rare bucket
1042 mQuotaController.saveTimingSession(0, "com.android.test",
1043 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3));
1044 // Affects frequent and rare buckets
1045 mQuotaController.saveTimingSession(0, "com.android.test",
1046 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3));
1047 // Affects working, frequent, and rare buckets
1048 final long outOfQuotaTime = now - HOUR_IN_MILLIS;
1049 mQuotaController.saveTimingSession(0, "com.android.test",
1050 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10));
1051 // Affects all buckets
1052 mQuotaController.saveTimingSession(0, "com.android.test",
1053 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3));
1054
1055 InOrder inOrder = inOrder(mAlarmManager);
1056
1057 // Start in ACTIVE bucket.
1058 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
Kweku Adamsbffea5a2018-12-13 22:13:28 -08001059 inOrder.verify(mAlarmManager, never())
1060 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001061 inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
1062
1063 // And down from there.
1064 final long expectedWorkingAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001065 outOfQuotaTime + (2 * HOUR_IN_MILLIS)
1066 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001067 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
1068 inOrder.verify(mAlarmManager, times(1))
1069 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1070
1071 final long expectedFrequentAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001072 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
1073 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001074 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
1075 inOrder.verify(mAlarmManager, times(1))
1076 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1077
1078 final long expectedRareAlarmTime =
Kweku Adamscdbfcb92018-12-06 17:05:15 -08001079 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
1080 + mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001081 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX);
1082 inOrder.verify(mAlarmManager, times(1))
1083 .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1084
1085 // And back up again.
1086 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX);
1087 inOrder.verify(mAlarmManager, times(1))
1088 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1089
1090 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX);
1091 inOrder.verify(mAlarmManager, times(1))
1092 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1093
1094 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX);
Kweku Adams288e73b2019-01-17 13:53:24 -08001095 inOrder.verify(mAlarmManager, never())
1096 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001097 inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class));
1098 }
1099
Kweku Adams288e73b2019-01-17 13:53:24 -08001100 @Test
1101 public void testMaybeScheduleStartAlarmLocked_JobCount_AllowedTime() {
1102 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1103 final int standbyBucket = WORKING_INDEX;
1104 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1105 SOURCE_PACKAGE, standbyBucket);
1106 stats.jobCountInAllowedTime =
1107 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME + 2;
1108
1109 // Invalid time in the past, so the count shouldn't be used.
1110 stats.jobCountExpirationTimeElapsed =
1111 now - mQuotaController.getAllowedTimePerPeriodMs() / 2;
1112 mQuotaController.maybeScheduleStartAlarmLocked(
1113 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
1114 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
1115
1116 // Invalid time in the future, so the count should be used.
1117 stats.jobCountExpirationTimeElapsed =
1118 now + mQuotaController.getAllowedTimePerPeriodMs() / 2;
1119 final long expectedWorkingAlarmTime =
1120 stats.jobCountExpirationTimeElapsed + mQuotaController.getAllowedTimePerPeriodMs();
1121 mQuotaController.maybeScheduleStartAlarmLocked(
1122 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
1123 verify(mAlarmManager, times(1))
1124 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1125 }
Kweku Adams045fb5722018-12-11 14:29:10 -08001126
1127 /**
1128 * Tests that the start alarm is properly rescheduled if the earliest session that contributes
1129 * to the app being out of quota contributes less than the quota buffer time.
1130 */
1131 @Test
1132 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() {
1133 // Use the default values
1134 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1135 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1136 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1137 }
1138
1139 @Test
1140 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() {
1141 // Make sure any new value is used correctly.
1142 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
1143 mQuotaController.onConstantsUpdatedLocked();
1144 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1145 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1146 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1147 }
1148
1149 @Test
1150 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() {
1151 // Make sure any new value is used correctly.
1152 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
1153 mQuotaController.onConstantsUpdatedLocked();
1154 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1155 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1156 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1157 }
1158
1159 @Test
1160 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() {
1161 // Make sure any new value is used correctly.
1162 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
1163 mQuotaController.onConstantsUpdatedLocked();
1164 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1165 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1166 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1167 }
1168
1169 @Test
1170 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() {
1171 // Make sure any new value is used correctly.
1172 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS *= 2;
1173 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS /= 2;
1174 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS /= 2;
1175 mQuotaController.onConstantsUpdatedLocked();
1176 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck();
1177 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear();
1178 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck();
1179 }
1180
1181 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() {
1182 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1183 // because it schedules an alarm too. Prevent it from doing so.
1184 spyOn(mQuotaController);
1185 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1186
1187 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1188 // Working set window size is 2 hours.
1189 final int standbyBucket = WORKING_INDEX;
1190 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1191 final long remainingTimeMs =
1192 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS - contributionMs;
1193
1194 // Session straddles edge of bucket window. Only the contribution should be counted towards
1195 // the quota.
1196 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1197 createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1198 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1199 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1200 createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2));
1201 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1202 // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1203 final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS
1204 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1205 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1206 standbyBucket);
1207 verify(mAlarmManager, times(1))
1208 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1209 }
1210
1211
1212 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
1213 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests
1214 // because it schedules an alarm too. Prevent it from doing so.
1215 spyOn(mQuotaController);
1216 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked();
1217
1218 final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1219 // Working set window size is 2 hours.
1220 final int standbyBucket = WORKING_INDEX;
1221 final long contributionMs = mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS / 2;
1222 final long remainingTimeMs =
1223 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS - contributionMs;
1224
1225 // Session straddles edge of 24 hour window. Only the contribution should be counted towards
1226 // the quota.
1227 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1228 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS),
1229 3 * MINUTE_IN_MILLIS + contributionMs, 3));
1230 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1231 createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300));
1232 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which
1233 // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session.
1234 final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS
1235 //+ mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS
1236 + 24 * HOUR_IN_MILLIS
1237 + (mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS - contributionMs);
1238 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE,
1239 standbyBucket);
1240 verify(mAlarmManager, times(1))
1241 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
1242 }
1243
Kweku Adams4836f9d2018-11-12 17:04:17 -08001244 /** Tests that QuotaController doesn't throttle if throttling is turned off. */
1245 @Test
1246 public void testThrottleToggling() throws Exception {
1247 setDischarging();
1248 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1249 createTimingSession(
1250 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1251 10 * MINUTE_IN_MILLIS, 4));
1252 JobStatus jobStatus = createJobStatus("testThrottleToggling", 1);
1253 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
1254 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1255 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1256
1257 mConstants.USE_HEARTBEATS = true;
1258 mQuotaController.onConstantsUpdatedLocked();
1259 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1260 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1261
1262 mConstants.USE_HEARTBEATS = false;
1263 mQuotaController.onConstantsUpdatedLocked();
1264 Thread.sleep(SECOND_IN_MILLIS); // Job updates are done in the background.
1265 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1266 }
1267
1268 @Test
1269 public void testConstantsUpdating_ValidValues() {
1270 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS;
1271 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 2 * MINUTE_IN_MILLIS;
1272 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 15 * MINUTE_IN_MILLIS;
1273 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS;
1274 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS;
1275 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001276 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -08001277 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = 5000;
1278 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 4000;
1279 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 3000;
1280 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 2000;
1281 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 500;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001282
1283 mQuotaController.onConstantsUpdatedLocked();
1284
1285 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1286 assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1287 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1288 assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1289 assertEquals(45 * MINUTE_IN_MILLIS,
1290 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1291 assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001292 assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams288e73b2019-01-17 13:53:24 -08001293 assertEquals(500, mQuotaController.getMaxJobCountPerAllowedTime());
1294 assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
1295 assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
1296 assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
1297 assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001298 }
1299
1300 @Test
1301 public void testConstantsUpdating_InvalidValues() {
Kweku Adams288e73b2019-01-17 13:53:24 -08001302 // Test negatives/too low.
Kweku Adams4836f9d2018-11-12 17:04:17 -08001303 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS;
1304 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS;
1305 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS;
1306 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS;
1307 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS;
1308 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001309 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS;
Kweku Adams288e73b2019-01-17 13:53:24 -08001310 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_ACTIVE = -1;
1311 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_WORKING = 1;
1312 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_FREQUENT = 1;
1313 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_RARE = 1;
1314 mConstants.QUOTA_CONTROLLER_MAX_JOB_COUNT_PER_ALLOWED_TIME = 0;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001315
1316 mQuotaController.onConstantsUpdatedLocked();
1317
1318 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1319 assertEquals(0, mQuotaController.getInQuotaBufferMs());
1320 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1321 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1322 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1323 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001324 assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams288e73b2019-01-17 13:53:24 -08001325 assertEquals(10, mQuotaController.getMaxJobCountPerAllowedTime());
1326 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]);
1327 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]);
1328 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]);
1329 assertEquals(100, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001330
1331 // Test larger than a day. Controller should cap at one day.
1332 mConstants.QUOTA_CONTROLLER_ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS;
1333 mConstants.QUOTA_CONTROLLER_IN_QUOTA_BUFFER_MS = 25 * HOUR_IN_MILLIS;
1334 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_ACTIVE_MS = 25 * HOUR_IN_MILLIS;
1335 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS;
1336 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS;
1337 mConstants.QUOTA_CONTROLLER_WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams045fb5722018-12-11 14:29:10 -08001338 mConstants.QUOTA_CONTROLLER_MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS;
Kweku Adams4836f9d2018-11-12 17:04:17 -08001339
1340 mQuotaController.onConstantsUpdatedLocked();
1341
1342 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs());
1343 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs());
1344 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]);
1345 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]);
1346 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]);
1347 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]);
Kweku Adams045fb5722018-12-11 14:29:10 -08001348 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs());
Kweku Adams4836f9d2018-11-12 17:04:17 -08001349 }
1350
1351 /** Tests that TimingSessions aren't saved when the device is charging. */
1352 @Test
1353 public void testTimerTracking_Charging() {
1354 setCharging();
1355
1356 JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1);
1357 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1358
1359 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1360
1361 mQuotaController.prepareForExecutionLocked(jobStatus);
1362 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1363 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1364 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1365 }
1366
1367 /** Tests that TimingSessions are saved properly when the device is discharging. */
1368 @Test
1369 public void testTimerTracking_Discharging() {
1370 setDischarging();
Kweku Adams288e73b2019-01-17 13:53:24 -08001371 setProcessState(ActivityManager.PROCESS_STATE_BACKUP);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001372
1373 JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1);
1374 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1375
1376 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1377
1378 List<TimingSession> expected = new ArrayList<>();
1379
1380 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1381 mQuotaController.prepareForExecutionLocked(jobStatus);
1382 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1383 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1384 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1385 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1386
1387 // Test overlapping jobs.
1388 JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2);
1389 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1390
1391 JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3);
1392 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1393
1394 advanceElapsedClock(SECOND_IN_MILLIS);
1395
1396 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1397 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1398 mQuotaController.prepareForExecutionLocked(jobStatus);
1399 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1400 mQuotaController.prepareForExecutionLocked(jobStatus2);
1401 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1402 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1403 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1404 mQuotaController.prepareForExecutionLocked(jobStatus3);
1405 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1406 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1407 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1408 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1409 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1410 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1411 }
1412
1413 /**
1414 * Tests that TimingSessions are saved properly when the device alternates between
1415 * charging and discharging.
1416 */
1417 @Test
1418 public void testTimerTracking_ChargingAndDischarging() {
Kweku Adams288e73b2019-01-17 13:53:24 -08001419 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1420
Kweku Adams4836f9d2018-11-12 17:04:17 -08001421 JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1);
1422 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1423 JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2);
1424 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1425 JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3);
1426 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1427 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1428 List<TimingSession> expected = new ArrayList<>();
1429
1430 // A job starting while charging. Only the portion that runs during the discharging period
1431 // should be counted.
1432 setCharging();
1433
1434 mQuotaController.prepareForExecutionLocked(jobStatus);
1435 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1436 setDischarging();
1437 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1438 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1439 mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
1440 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1441 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1442
1443 advanceElapsedClock(SECOND_IN_MILLIS);
1444
1445 // One job starts while discharging, spans a charging session, and ends after the charging
1446 // session. Only the portions during the discharging periods should be counted. This should
1447 // result in two TimingSessions. A second job starts while discharging and ends within the
1448 // charging session. Only the portion during the first discharging portion should be
1449 // counted. A third job starts and ends within the charging session. The third job
1450 // shouldn't be included in either job count.
1451 setDischarging();
1452 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1453 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1454 mQuotaController.prepareForExecutionLocked(jobStatus);
1455 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1456 mQuotaController.prepareForExecutionLocked(jobStatus2);
1457 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1458 setCharging();
1459 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
1460 mQuotaController.prepareForExecutionLocked(jobStatus3);
1461 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1462 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1463 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1464 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1465 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1466 setDischarging();
1467 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1468 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1469 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1470 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
1471 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1472
1473 // A job starting while discharging and ending while charging. Only the portion that runs
1474 // during the discharging period should be counted.
1475 setDischarging();
1476 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1477 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1478 mQuotaController.prepareForExecutionLocked(jobStatus2);
1479 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1480 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1481 setCharging();
1482 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1483 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1484 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1485 }
1486
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001487 /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */
1488 @Test
1489 public void testTimerTracking_AllBackground() {
1490 setDischarging();
Kweku Adams288e73b2019-01-17 13:53:24 -08001491 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001492
1493 JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1);
1494 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1495
1496 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1497
1498 List<TimingSession> expected = new ArrayList<>();
1499
1500 // Test single job.
1501 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1502 mQuotaController.prepareForExecutionLocked(jobStatus);
1503 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1504 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1505 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
1506 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1507
1508 // Test overlapping jobs.
1509 JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2);
1510 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null);
1511
1512 JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3);
1513 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null);
1514
1515 advanceElapsedClock(SECOND_IN_MILLIS);
1516
1517 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1518 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1519 mQuotaController.prepareForExecutionLocked(jobStatus);
1520 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1521 mQuotaController.prepareForExecutionLocked(jobStatus2);
1522 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1523 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1524 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1525 mQuotaController.prepareForExecutionLocked(jobStatus3);
1526 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1527 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
1528 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1529 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
1530 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
1531 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1532 }
1533
1534 /** Tests that Timers don't count foreground jobs. */
1535 @Test
1536 public void testTimerTracking_AllForeground() {
1537 setDischarging();
1538
1539 JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001540 setProcessState(ActivityManager.PROCESS_STATE_TOP);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001541 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1542
1543 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1544
1545 mQuotaController.prepareForExecutionLocked(jobStatus);
1546 advanceElapsedClock(5 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001547 // Change to a state that should still be considered foreground.
1548 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1549 advanceElapsedClock(5 * SECOND_IN_MILLIS);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001550 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
1551 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1552 }
1553
1554 /**
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001555 * Tests that Timers properly track sessions when switching between foreground and background
1556 * states.
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001557 */
1558 @Test
1559 public void testTimerTracking_ForegroundAndBackground() {
1560 setDischarging();
1561
1562 JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1);
1563 JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2);
1564 JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001565 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1566 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1567 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
1568 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1569 List<TimingSession> expected = new ArrayList<>();
1570
1571 // UID starts out inactive.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001572 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001573 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1574 mQuotaController.prepareForExecutionLocked(jobBg1);
1575 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1576 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1577 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1578 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1579
1580 advanceElapsedClock(SECOND_IN_MILLIS);
1581
1582 // Bg job starts while inactive, spans an entire active session, and ends after the
1583 // active session.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001584 // App switching to foreground state then fg job starts.
1585 // App remains in foreground state after coming to foreground, so there should only be one
1586 // session.
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001587 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1588 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1589 mQuotaController.prepareForExecutionLocked(jobBg2);
1590 advanceElapsedClock(10 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001591 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1592 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001593 mQuotaController.prepareForExecutionLocked(jobFg3);
1594 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1595 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1596 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1597 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001598 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1599
1600 advanceElapsedClock(SECOND_IN_MILLIS);
1601
1602 // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes
1603 // "inactive" and then bg job 2 starts. Then fg job ends.
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001604 // This should result in two TimingSessions:
1605 // * The first should have a count of 1
1606 // * The second should have a count of 2 since it will include both jobs
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001607 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1608 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1609 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1610 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001611 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001612 mQuotaController.prepareForExecutionLocked(jobBg1);
1613 advanceElapsedClock(10 * SECOND_IN_MILLIS);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001614 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1615 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001616 mQuotaController.prepareForExecutionLocked(jobFg3);
1617 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1618 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001619 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
1620 start = JobSchedulerService.sElapsedRealtimeClock.millis();
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001621 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001622 mQuotaController.prepareForExecutionLocked(jobBg2);
1623 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1624 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
1625 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1626 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001627 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
Kweku Adamscc5afbc2018-12-11 15:24:25 -08001628 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1629 }
1630
Kweku Adams4836f9d2018-11-12 17:04:17 -08001631 /**
Kweku Adams288e73b2019-01-17 13:53:24 -08001632 * Tests that Timers don't track job counts while in the foreground.
1633 */
1634 @Test
1635 public void testTimerTracking_JobCount_Foreground() {
1636 setDischarging();
1637
1638 final int standbyBucket = ACTIVE_INDEX;
1639 JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1);
1640 JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2);
1641
1642 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
1643 mQuotaController.maybeStartTrackingJobLocked(jobFg2, null);
1644 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1645 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1646 SOURCE_PACKAGE, standbyBucket);
1647 assertEquals(0, stats.jobCountInAllowedTime);
1648
1649 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1650 mQuotaController.prepareForExecutionLocked(jobFg1);
1651 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1652 mQuotaController.prepareForExecutionLocked(jobFg2);
1653 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1654 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
1655 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1656 mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
1657 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1658
1659 assertEquals(0, stats.jobCountInAllowedTime);
1660 }
1661
1662 /**
1663 * Tests that Timers properly track job counts while in the background.
1664 */
1665 @Test
1666 public void testTimerTracking_JobCount_Background() {
1667 final int standbyBucket = WORKING_INDEX;
1668 JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1);
1669 JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2);
1670 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1671 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1672
1673 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID,
1674 SOURCE_PACKAGE, standbyBucket);
1675 assertEquals(0, stats.jobCountInAllowedTime);
1676
1677 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
1678 mQuotaController.prepareForExecutionLocked(jobBg1);
1679 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1680 mQuotaController.prepareForExecutionLocked(jobBg2);
1681 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1682 mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
1683 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1684 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1685
1686 assertEquals(2, stats.jobCountInAllowedTime);
1687 }
1688
1689 /**
Kweku Adamsd6625ff2019-01-10 12:06:21 -08001690 * Tests that Timers properly track overlapping top and background jobs.
1691 */
1692 @Test
1693 public void testTimerTracking_TopAndNonTop() {
1694 setDischarging();
1695
1696 JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1);
1697 JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2);
1698 JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3);
1699 JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4);
1700 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1701 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1702 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null);
1703 mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
1704 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1705 List<TimingSession> expected = new ArrayList<>();
1706
1707 // UID starts out inactive.
1708 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
1709 long start = JobSchedulerService.sElapsedRealtimeClock.millis();
1710 mQuotaController.prepareForExecutionLocked(jobBg1);
1711 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1712 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1713 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1714 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1715
1716 advanceElapsedClock(SECOND_IN_MILLIS);
1717
1718 // Bg job starts while inactive, spans an entire active session, and ends after the
1719 // active session.
1720 // App switching to top state then fg job starts.
1721 // App remains in top state after coming to top, so there should only be one
1722 // session.
1723 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1724 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1725 mQuotaController.prepareForExecutionLocked(jobBg2);
1726 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1727 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1728 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1729 mQuotaController.prepareForExecutionLocked(jobTop);
1730 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1731 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
1732 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1733 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1734 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1735
1736 advanceElapsedClock(SECOND_IN_MILLIS);
1737
1738 // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to
1739 // foreground_service and a new job starts. Shortly after, uid goes
1740 // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs.
1741 // This should result in two TimingSessions:
1742 // * The first should have a count of 1
1743 // * The second should have a count of 2, which accounts for the bg2 and fg, but not top
1744 // jobs.
1745 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1746 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null);
1747 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null);
1748 mQuotaController.maybeStartTrackingJobLocked(jobTop, null);
1749 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY);
1750 mQuotaController.prepareForExecutionLocked(jobBg1);
1751 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1752 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
1753 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1754 mQuotaController.prepareForExecutionLocked(jobTop);
1755 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1756 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
1757 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1758 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1759 mQuotaController.prepareForExecutionLocked(jobFg1);
1760 advanceElapsedClock(5 * SECOND_IN_MILLIS);
1761 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1762 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
1763 start = JobSchedulerService.sElapsedRealtimeClock.millis();
1764 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
1765 mQuotaController.prepareForExecutionLocked(jobBg2);
1766 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1767 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
1768 advanceElapsedClock(10 * SECOND_IN_MILLIS);
1769 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
1770 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
1771 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
1772 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
1773 }
1774
1775 /**
1776 * Tests that TOP jobs aren't stopped when an app runs out of quota.
1777 */
1778 @Test
1779 public void testTracking_OutOfQuota_ForegroundAndBackground() {
1780 setDischarging();
1781
1782 JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1);
1783 JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2);
1784 trackJobs(jobBg, jobTop);
1785 setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window
1786 // Now the package only has 20 seconds to run.
1787 final long remainingTimeMs = 20 * SECOND_IN_MILLIS;
1788 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1789 createTimingSession(
1790 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1791 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
1792
1793 InOrder inOrder = inOrder(mJobSchedulerService);
1794
1795 // UID starts out inactive.
1796 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
1797 // Start the job.
1798 mQuotaController.prepareForExecutionLocked(jobBg);
1799 advanceElapsedClock(remainingTimeMs / 2);
1800 // New job starts after UID is in the foreground. Since the app is now in the foreground, it
1801 // should continue to have remainingTimeMs / 2 time remaining.
1802 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1803 mQuotaController.prepareForExecutionLocked(jobTop);
1804 advanceElapsedClock(remainingTimeMs);
1805
1806 // Wait for some extra time to allow for job processing.
1807 inOrder.verify(mJobSchedulerService,
1808 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
1809 .onControllerStateChanged();
1810 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg));
1811 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop));
1812 // Go to a background state.
1813 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING);
1814 advanceElapsedClock(remainingTimeMs / 2 + 1);
1815 inOrder.verify(mJobSchedulerService,
1816 timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1))
1817 .onControllerStateChanged();
1818 // Top job should still be allowed to run.
1819 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1820 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1821
1822 // New jobs to run.
1823 JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3);
1824 JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4);
1825 JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5);
1826 setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg);
1827
1828 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1829 setProcessState(ActivityManager.PROCESS_STATE_TOP);
1830 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
1831 .onControllerStateChanged();
1832 trackJobs(jobFg, jobTop);
1833 mQuotaController.prepareForExecutionLocked(jobTop);
1834 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1835 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1836 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1837
1838 // App still in foreground so everything should be in quota.
1839 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1840 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
1841 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1842 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1843 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1844
1845 advanceElapsedClock(20 * SECOND_IN_MILLIS);
1846 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
1847 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1))
1848 .onControllerStateChanged();
1849 // App is now in background and out of quota. Fg should now change to out of quota since it
1850 // wasn't started. Top should remain in quota since it started when the app was in TOP.
1851 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1852 assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1853 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1854 trackJobs(jobBg2);
1855 assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1856 }
1857
1858 /**
Kweku Adams4836f9d2018-11-12 17:04:17 -08001859 * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches
1860 * its quota.
1861 */
1862 @Test
1863 public void testTracking_OutOfQuota() {
1864 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
1865 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1866 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
Kweku Adams288e73b2019-01-17 13:53:24 -08001867 setProcessState(ActivityManager.PROCESS_STATE_HOME);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001868 // Now the package only has two seconds to run.
1869 final long remainingTimeMs = 2 * SECOND_IN_MILLIS;
1870 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1871 createTimingSession(
1872 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS,
1873 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
1874
1875 // Start the job.
1876 mQuotaController.prepareForExecutionLocked(jobStatus);
1877 advanceElapsedClock(remainingTimeMs);
1878
1879 // Wait for some extra time to allow for job processing.
1880 verify(mJobSchedulerService,
1881 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1))
1882 .onControllerStateChanged();
1883 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1884 }
1885
1886 /**
1887 * Tests that a job is properly handled when it's at the edge of its quota and the old quota is
1888 * being phased out.
1889 */
1890 @Test
1891 public void testTracking_RollingQuota() {
1892 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1);
1893 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null);
1894 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window
Kweku Adams288e73b2019-01-17 13:53:24 -08001895 setProcessState(ActivityManager.PROCESS_STATE_SERVICE);
Kweku Adams4836f9d2018-11-12 17:04:17 -08001896 Handler handler = mQuotaController.getHandler();
1897 spyOn(handler);
1898
1899 long now = JobSchedulerService.sElapsedRealtimeClock.millis();
1900 final long remainingTimeMs = SECOND_IN_MILLIS;
1901 // The package only has one second to run, but this session is at the edge of the rolling
1902 // window, so as the package "reaches its quota" it will have more to keep running.
1903 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
1904 createTimingSession(now - 2 * HOUR_IN_MILLIS,
1905 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1));
1906
1907 assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus));
1908 // Start the job.
1909 mQuotaController.prepareForExecutionLocked(jobStatus);
1910 advanceElapsedClock(remainingTimeMs);
1911
1912 // Wait for some extra time to allow for job processing.
1913 verify(mJobSchedulerService,
1914 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0))
1915 .onControllerStateChanged();
1916 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA));
1917 // The job used up the remaining quota, but in that time, the same amount of time in the
1918 // old TimingSession also fell out of the quota window, so it should still have the same
1919 // amount of remaining time left its quota.
1920 assertEquals(remainingTimeMs,
1921 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE));
1922 verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(remainingTimeMs));
1923 }
1924}