1 /* 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 17 package com.android.server.job.controllers; 18 19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; 23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; 26 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; 27 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; 28 import static com.android.server.job.JobSchedulerService.NEVER_INDEX; 29 import static com.android.server.job.JobSchedulerService.RARE_INDEX; 30 import static com.android.server.job.JobSchedulerService.WORKING_INDEX; 31 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 32 33 import static org.junit.Assert.assertEquals; 34 import static org.junit.Assert.assertFalse; 35 import static org.junit.Assert.assertNotEquals; 36 import static org.junit.Assert.assertNull; 37 import static org.junit.Assert.assertTrue; 38 import static org.junit.Assert.fail; 39 import static org.mockito.ArgumentMatchers.any; 40 import static org.mockito.ArgumentMatchers.anyInt; 41 import static org.mockito.ArgumentMatchers.anyLong; 42 import static org.mockito.Mockito.atLeast; 43 import static org.mockito.Mockito.eq; 44 import static org.mockito.Mockito.never; 45 import static org.mockito.Mockito.timeout; 46 import static org.mockito.Mockito.times; 47 import static org.mockito.Mockito.verify; 48 49 import android.app.ActivityManager; 50 import android.app.ActivityManagerInternal; 51 import android.app.AlarmManager; 52 import android.app.AppGlobals; 53 import android.app.IActivityManager; 54 import android.app.IUidObserver; 55 import android.app.job.JobInfo; 56 import android.app.usage.UsageStatsManager; 57 import android.app.usage.UsageStatsManagerInternal; 58 import android.content.BroadcastReceiver; 59 import android.content.ComponentName; 60 import android.content.Context; 61 import android.content.Intent; 62 import android.content.pm.IPackageManager; 63 import android.content.pm.PackageManagerInternal; 64 import android.os.BatteryManager; 65 import android.os.BatteryManagerInternal; 66 import android.os.Handler; 67 import android.os.Looper; 68 import android.os.RemoteException; 69 import android.os.SystemClock; 70 import android.util.SparseBooleanArray; 71 72 import androidx.test.runner.AndroidJUnit4; 73 74 import com.android.server.LocalServices; 75 import com.android.server.job.JobSchedulerService; 76 import com.android.server.job.JobSchedulerService.Constants; 77 import com.android.server.job.JobServiceContext; 78 import com.android.server.job.JobStore; 79 import com.android.server.job.controllers.QuotaController.ExecutionStats; 80 import com.android.server.job.controllers.QuotaController.TimingSession; 81 import com.android.server.usage.AppStandbyInternal; 82 83 import org.junit.After; 84 import org.junit.Before; 85 import org.junit.Test; 86 import org.junit.runner.RunWith; 87 import org.mockito.ArgumentCaptor; 88 import org.mockito.InOrder; 89 import org.mockito.Mock; 90 import org.mockito.MockitoSession; 91 import org.mockito.quality.Strictness; 92 93 import java.time.Clock; 94 import java.time.Duration; 95 import java.time.ZoneOffset; 96 import java.util.ArrayList; 97 import java.util.List; 98 99 @RunWith(AndroidJUnit4.class) 100 public class QuotaControllerTest { 101 private static final long SECOND_IN_MILLIS = 1000L; 102 private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; 103 private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; 104 private static final String TAG_CLEANUP = "*job.cleanup*"; 105 private static final String TAG_QUOTA_CHECK = "*job.quota_check*"; 106 private static final int CALLING_UID = 1000; 107 private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; 108 private static final int SOURCE_USER_ID = 0; 109 110 private BroadcastReceiver mChargingReceiver; 111 private QuotaController mQuotaController; 112 private QuotaController.QcConstants mQcConstants; 113 private int mSourceUid; 114 private IUidObserver mUidObserver; 115 116 private MockitoSession mMockingSession; 117 @Mock 118 private ActivityManagerInternal mActivityMangerInternal; 119 @Mock 120 private AlarmManager mAlarmManager; 121 @Mock 122 private Context mContext; 123 @Mock 124 private JobSchedulerService mJobSchedulerService; 125 @Mock 126 private UsageStatsManagerInternal mUsageStatsManager; 127 128 private JobStore mJobStore; 129 130 @Before setUp()131 public void setUp() { 132 mMockingSession = mockitoSession() 133 .initMocks(this) 134 .strictness(Strictness.LENIENT) 135 .mockStatic(LocalServices.class) 136 .startMocking(); 137 138 // Called in StateController constructor. 139 when(mJobSchedulerService.getTestableContext()).thenReturn(mContext); 140 when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService); 141 when(mJobSchedulerService.getConstants()).thenReturn(mock(Constants.class)); 142 // Called in QuotaController constructor. 143 IActivityManager activityManager = ActivityManager.getService(); 144 spyOn(activityManager); 145 try { 146 doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any()); 147 } catch (RemoteException e) { 148 fail("registerUidObserver threw exception: " + e.getMessage()); 149 } 150 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); 151 when(mContext.getSystemService(Context.ALARM_SERVICE)).thenReturn(mAlarmManager); 152 doReturn(mActivityMangerInternal) 153 .when(() -> LocalServices.getService(ActivityManagerInternal.class)); 154 doReturn(mock(AppStandbyInternal.class)) 155 .when(() -> LocalServices.getService(AppStandbyInternal.class)); 156 doReturn(mock(BatteryManagerInternal.class)) 157 .when(() -> LocalServices.getService(BatteryManagerInternal.class)); 158 doReturn(mUsageStatsManager) 159 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class)); 160 // Used in JobStatus. 161 doReturn(mock(PackageManagerInternal.class)) 162 .when(() -> LocalServices.getService(PackageManagerInternal.class)); 163 // Used in QuotaController.Handler. 164 mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir()); 165 when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore); 166 167 // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions 168 // in the past, and QuotaController sometimes floors values at 0, so if the test time 169 // causes sessions with negative timestamps, they will fail. 170 JobSchedulerService.sSystemClock = 171 getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC), 172 24 * HOUR_IN_MILLIS); 173 JobSchedulerService.sUptimeMillisClock = getAdvancedClock( 174 Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC), 175 24 * HOUR_IN_MILLIS); 176 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock( 177 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC), 178 24 * HOUR_IN_MILLIS); 179 180 // Initialize real objects. 181 // Capture the listeners. 182 ArgumentCaptor<BroadcastReceiver> receiverCaptor = 183 ArgumentCaptor.forClass(BroadcastReceiver.class); 184 ArgumentCaptor<IUidObserver> uidObserverCaptor = 185 ArgumentCaptor.forClass(IUidObserver.class); 186 mQuotaController = new QuotaController(mJobSchedulerService); 187 188 verify(mContext).registerReceiver(receiverCaptor.capture(), any()); 189 mChargingReceiver = receiverCaptor.getValue(); 190 try { 191 verify(activityManager).registerUidObserver( 192 uidObserverCaptor.capture(), 193 eq(ActivityManager.UID_OBSERVER_PROCSTATE), 194 eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE), 195 any()); 196 mUidObserver = uidObserverCaptor.getValue(); 197 mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0); 198 } catch (RemoteException e) { 199 fail(e.getMessage()); 200 } 201 mQcConstants = mQuotaController.getQcConstants(); 202 } 203 204 @After tearDown()205 public void tearDown() { 206 if (mMockingSession != null) { 207 mMockingSession.finishMocking(); 208 } 209 } 210 getAdvancedClock(Clock clock, long incrementMs)211 private Clock getAdvancedClock(Clock clock, long incrementMs) { 212 return Clock.offset(clock, Duration.ofMillis(incrementMs)); 213 } 214 advanceElapsedClock(long incrementMs)215 private void advanceElapsedClock(long incrementMs) { 216 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock( 217 JobSchedulerService.sElapsedRealtimeClock, incrementMs); 218 } 219 setCharging()220 private void setCharging() { 221 Intent intent = new Intent(BatteryManager.ACTION_CHARGING); 222 mChargingReceiver.onReceive(mContext, intent); 223 } 224 setDischarging()225 private void setDischarging() { 226 Intent intent = new Intent(BatteryManager.ACTION_DISCHARGING); 227 mChargingReceiver.onReceive(mContext, intent); 228 } 229 setProcessState(int procState)230 private void setProcessState(int procState) { 231 setProcessState(procState, mSourceUid); 232 } 233 setProcessState(int procState, int uid)234 private void setProcessState(int procState, int uid) { 235 try { 236 doReturn(procState).when(mActivityMangerInternal).getUidProcessState(uid); 237 SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids(); 238 spyOn(foregroundUids); 239 mUidObserver.onUidStateChanged(uid, procState, 0, 240 ActivityManager.PROCESS_CAPABILITY_NONE); 241 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { 242 verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)) 243 .put(eq(uid), eq(true)); 244 assertTrue(foregroundUids.get(uid)); 245 } else { 246 verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)).delete(eq(uid)); 247 assertFalse(foregroundUids.get(uid)); 248 } 249 } catch (RemoteException e) { 250 fail("registerUidObserver threw exception: " + e.getMessage()); 251 } 252 } 253 bucketIndexToUsageStatsBucket(int bucketIndex)254 private int bucketIndexToUsageStatsBucket(int bucketIndex) { 255 switch (bucketIndex) { 256 case ACTIVE_INDEX: 257 return UsageStatsManager.STANDBY_BUCKET_ACTIVE; 258 case WORKING_INDEX: 259 return UsageStatsManager.STANDBY_BUCKET_WORKING_SET; 260 case FREQUENT_INDEX: 261 return UsageStatsManager.STANDBY_BUCKET_FREQUENT; 262 case RARE_INDEX: 263 return UsageStatsManager.STANDBY_BUCKET_RARE; 264 default: 265 return UsageStatsManager.STANDBY_BUCKET_NEVER; 266 } 267 } 268 setStandbyBucket(int bucketIndex)269 private void setStandbyBucket(int bucketIndex) { 270 when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID), 271 anyLong())).thenReturn(bucketIndexToUsageStatsBucket(bucketIndex)); 272 } 273 setStandbyBucket(int bucketIndex, JobStatus... jobs)274 private void setStandbyBucket(int bucketIndex, JobStatus... jobs) { 275 setStandbyBucket(bucketIndex); 276 for (JobStatus job : jobs) { 277 job.setStandbyBucket(bucketIndex); 278 when(mUsageStatsManager.getAppStandbyBucket( 279 eq(job.getSourcePackageName()), eq(job.getSourceUserId()), anyLong())) 280 .thenReturn(bucketIndexToUsageStatsBucket(bucketIndex)); 281 } 282 } 283 trackJobs(JobStatus... jobs)284 private void trackJobs(JobStatus... jobs) { 285 for (JobStatus job : jobs) { 286 mJobStore.add(job); 287 mQuotaController.maybeStartTrackingJobLocked(job, null); 288 } 289 } 290 createJobStatus(String testTag, int jobId)291 private JobStatus createJobStatus(String testTag, int jobId) { 292 JobInfo jobInfo = new JobInfo.Builder(jobId, 293 new ComponentName(mContext, "TestQuotaJobService")) 294 .build(); 295 return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo); 296 } 297 createJobStatus(String testTag, String packageName, int callingUid, JobInfo jobInfo)298 private JobStatus createJobStatus(String testTag, String packageName, int callingUid, 299 JobInfo jobInfo) { 300 JobStatus js = JobStatus.createFromJobInfo( 301 jobInfo, callingUid, packageName, SOURCE_USER_ID, testTag); 302 // Make sure tests aren't passing just because the default bucket is likely ACTIVE. 303 js.setStandbyBucket(FREQUENT_INDEX); 304 // Make sure Doze and background-not-restricted don't affect tests. 305 js.setDeviceNotDozingConstraintSatisfied(/* state */ true, /* whitelisted */false); 306 js.setBackgroundNotRestrictedConstraintSatisfied(true); 307 return js; 308 } 309 createTimingSession(long start, long duration, int count)310 private TimingSession createTimingSession(long start, long duration, int count) { 311 return new TimingSession(start, start + duration, count); 312 } 313 314 @Test testSaveTimingSession()315 public void testSaveTimingSession() { 316 assertNull(mQuotaController.getTimingSessions(0, "com.android.test")); 317 318 List<TimingSession> expected = new ArrayList<>(); 319 TimingSession one = new TimingSession(1, 10, 1); 320 TimingSession two = new TimingSession(11, 20, 2); 321 TimingSession thr = new TimingSession(21, 30, 3); 322 323 mQuotaController.saveTimingSession(0, "com.android.test", one); 324 expected.add(one); 325 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test")); 326 327 mQuotaController.saveTimingSession(0, "com.android.test", two); 328 expected.add(two); 329 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test")); 330 331 mQuotaController.saveTimingSession(0, "com.android.test", thr); 332 expected.add(thr); 333 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test")); 334 } 335 336 @Test testDeleteObsoleteSessionsLocked()337 public void testDeleteObsoleteSessionsLocked() { 338 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 339 TimingSession one = createTimingSession( 340 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); 341 TimingSession two = createTimingSession( 342 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); 343 TimingSession thr = createTimingSession( 344 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); 345 // Overlaps 24 hour boundary. 346 TimingSession fou = createTimingSession( 347 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1); 348 // Way past the 24 hour boundary. 349 TimingSession fiv = createTimingSession( 350 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4); 351 List<TimingSession> expected = new ArrayList<>(); 352 // Added in correct (chronological) order. 353 expected.add(fou); 354 expected.add(thr); 355 expected.add(two); 356 expected.add(one); 357 mQuotaController.saveTimingSession(0, "com.android.test", fiv); 358 mQuotaController.saveTimingSession(0, "com.android.test", fou); 359 mQuotaController.saveTimingSession(0, "com.android.test", thr); 360 mQuotaController.saveTimingSession(0, "com.android.test", two); 361 mQuotaController.saveTimingSession(0, "com.android.test", one); 362 363 mQuotaController.deleteObsoleteSessionsLocked(); 364 365 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test")); 366 } 367 368 @Test testOnAppRemovedLocked()369 public void testOnAppRemovedLocked() { 370 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 371 mQuotaController.saveTimingSession(0, "com.android.test.remove", 372 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); 373 mQuotaController.saveTimingSession(0, "com.android.test.remove", 374 createTimingSession( 375 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5)); 376 mQuotaController.saveTimingSession(0, "com.android.test.remove", 377 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1)); 378 // Test that another app isn't affected. 379 TimingSession one = createTimingSession( 380 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); 381 TimingSession two = createTimingSession( 382 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); 383 List<TimingSession> expected = new ArrayList<>(); 384 // Added in correct (chronological) order. 385 expected.add(two); 386 expected.add(one); 387 mQuotaController.saveTimingSession(0, "com.android.test.stay", two); 388 mQuotaController.saveTimingSession(0, "com.android.test.stay", one); 389 390 ExecutionStats expectedStats = new ExecutionStats(); 391 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; 392 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 393 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 394 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; 395 396 final int uid = 10001; 397 mQuotaController.onAppRemovedLocked("com.android.test.remove", uid); 398 assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove")); 399 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay")); 400 assertEquals(expectedStats, 401 mQuotaController.getExecutionStatsLocked(0, "com.android.test.remove", RARE_INDEX)); 402 assertNotEquals(expectedStats, 403 mQuotaController.getExecutionStatsLocked(0, "com.android.test.stay", RARE_INDEX)); 404 405 assertFalse(mQuotaController.getForegroundUids().get(uid)); 406 } 407 408 @Test testOnUserRemovedLocked()409 public void testOnUserRemovedLocked() { 410 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 411 mQuotaController.saveTimingSession(0, "com.android.test", 412 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); 413 mQuotaController.saveTimingSession(0, "com.android.test", 414 createTimingSession( 415 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5)); 416 mQuotaController.saveTimingSession(0, "com.android.test", 417 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1)); 418 // Test that another user isn't affected. 419 TimingSession one = createTimingSession( 420 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); 421 TimingSession two = createTimingSession( 422 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); 423 List<TimingSession> expected = new ArrayList<>(); 424 // Added in correct (chronological) order. 425 expected.add(two); 426 expected.add(one); 427 mQuotaController.saveTimingSession(10, "com.android.test", two); 428 mQuotaController.saveTimingSession(10, "com.android.test", one); 429 430 ExecutionStats expectedStats = new ExecutionStats(); 431 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; 432 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 433 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 434 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; 435 436 mQuotaController.onUserRemovedLocked(0); 437 assertNull(mQuotaController.getTimingSessions(0, "com.android.test")); 438 assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test")); 439 assertEquals(expectedStats, 440 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX)); 441 assertNotEquals(expectedStats, 442 mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX)); 443 } 444 445 @Test testUpdateExecutionStatsLocked_NoTimer()446 public void testUpdateExecutionStatsLocked_NoTimer() { 447 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 448 // Added in chronological order. 449 mQuotaController.saveTimingSession(0, "com.android.test", 450 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); 451 mQuotaController.saveTimingSession(0, "com.android.test", 452 createTimingSession( 453 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5)); 454 mQuotaController.saveTimingSession(0, "com.android.test", 455 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1)); 456 mQuotaController.saveTimingSession(0, "com.android.test", 457 createTimingSession( 458 now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1)); 459 mQuotaController.saveTimingSession(0, "com.android.test", 460 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3)); 461 462 // Test an app that hasn't had any activity. 463 ExecutionStats expectedStats = new ExecutionStats(); 464 ExecutionStats inputStats = new ExecutionStats(); 465 466 inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS; 467 inputStats.jobCountLimit = expectedStats.jobCountLimit = 100; 468 inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100; 469 // Invalid time is now +24 hours since there are no sessions at all for the app. 470 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; 471 mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats); 472 assertEquals(expectedStats, inputStats); 473 474 inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS; 475 // Invalid time is now +18 hours since there are no sessions in the window but the earliest 476 // session is 6 hours ago. 477 expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS; 478 expectedStats.executionTimeInWindowMs = 0; 479 expectedStats.bgJobCountInWindow = 0; 480 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 481 expectedStats.bgJobCountInMaxPeriod = 15; 482 expectedStats.sessionCountInWindow = 0; 483 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 484 assertEquals(expectedStats, inputStats); 485 486 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS; 487 // Invalid time is now since the session straddles the window cutoff time. 488 expectedStats.expirationTimeElapsed = now; 489 expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS; 490 expectedStats.bgJobCountInWindow = 3; 491 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 492 expectedStats.bgJobCountInMaxPeriod = 15; 493 expectedStats.sessionCountInWindow = 1; 494 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 495 assertEquals(expectedStats, inputStats); 496 497 inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS; 498 // Invalid time is now since the start of the session is at the very edge of the window 499 // cutoff time. 500 expectedStats.expirationTimeElapsed = now; 501 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; 502 expectedStats.bgJobCountInWindow = 3; 503 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 504 expectedStats.bgJobCountInMaxPeriod = 15; 505 expectedStats.sessionCountInWindow = 1; 506 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 507 assertEquals(expectedStats, inputStats); 508 509 inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS; 510 // Invalid time is now +44 minutes since the earliest session in the window is now-5 511 // minutes. 512 expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS; 513 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; 514 expectedStats.bgJobCountInWindow = 3; 515 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 516 expectedStats.bgJobCountInMaxPeriod = 15; 517 expectedStats.sessionCountInWindow = 1; 518 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 519 assertEquals(expectedStats, inputStats); 520 521 inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS; 522 // Invalid time is now since the session is at the very edge of the window cutoff time. 523 expectedStats.expirationTimeElapsed = now; 524 expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS; 525 expectedStats.bgJobCountInWindow = 4; 526 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 527 expectedStats.bgJobCountInMaxPeriod = 15; 528 expectedStats.sessionCountInWindow = 2; 529 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 530 assertEquals(expectedStats, inputStats); 531 532 inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS; 533 inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 2; 534 // Invalid time is now since the start of the session is at the very edge of the window 535 // cutoff time. 536 expectedStats.expirationTimeElapsed = now; 537 expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS; 538 expectedStats.bgJobCountInWindow = 5; 539 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 540 expectedStats.bgJobCountInMaxPeriod = 15; 541 expectedStats.sessionCountInWindow = 3; 542 expectedStats.inQuotaTimeElapsed = now + 11 * MINUTE_IN_MILLIS; 543 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 544 assertEquals(expectedStats, inputStats); 545 546 inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; 547 inputStats.jobCountLimit = expectedStats.jobCountLimit = 6; 548 inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100; 549 // Invalid time is now since the session straddles the window cutoff time. 550 expectedStats.expirationTimeElapsed = now; 551 expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS; 552 expectedStats.bgJobCountInWindow = 10; 553 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 554 expectedStats.bgJobCountInMaxPeriod = 15; 555 expectedStats.sessionCountInWindow = 4; 556 expectedStats.inQuotaTimeElapsed = now + 5 * MINUTE_IN_MILLIS; 557 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 558 assertEquals(expectedStats, inputStats); 559 560 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS; 561 // Invalid time is now +59 minutes since the earliest session in the window is now-121 562 // minutes. 563 expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS; 564 expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS; 565 expectedStats.bgJobCountInWindow = 10; 566 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 567 expectedStats.bgJobCountInMaxPeriod = 15; 568 expectedStats.sessionCountInWindow = 4; 569 // App goes under job execution time limit in ~61 minutes, but will be under job count limit 570 // in 65 minutes. 571 expectedStats.inQuotaTimeElapsed = now + 65 * MINUTE_IN_MILLIS; 572 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 573 assertEquals(expectedStats, inputStats); 574 575 inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS; 576 // Invalid time is now since the start of the session is at the very edge of the window 577 // cutoff time. 578 expectedStats.expirationTimeElapsed = now; 579 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; 580 expectedStats.bgJobCountInWindow = 15; 581 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 582 expectedStats.bgJobCountInMaxPeriod = 15; 583 expectedStats.sessionCountInWindow = 5; 584 expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS + 5 * MINUTE_IN_MILLIS; 585 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 586 assertEquals(expectedStats, inputStats); 587 588 // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period. 589 mQuotaController.getTimingSessions(0, "com.android.test") 590 .add(0, 591 createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3)); 592 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; 593 inputStats.jobCountLimit = expectedStats.jobCountLimit = 100; 594 // Invalid time is now +1 hour since the earliest session in the max period is 1 hour 595 // before the end of the max period cutoff time. 596 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 597 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; 598 expectedStats.bgJobCountInWindow = 15; 599 expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS; 600 expectedStats.bgJobCountInMaxPeriod = 18; 601 expectedStats.sessionCountInWindow = 5; 602 expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS 603 + mQcConstants.IN_QUOTA_BUFFER_MS; 604 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 605 assertEquals(expectedStats, inputStats); 606 607 mQuotaController.getTimingSessions(0, "com.android.test") 608 .add(0, 609 createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 610 2 * MINUTE_IN_MILLIS, 2)); 611 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; 612 // Invalid time is now since the earliest session straddles the max period cutoff time. 613 expectedStats.expirationTimeElapsed = now; 614 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; 615 expectedStats.bgJobCountInWindow = 15; 616 expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS; 617 expectedStats.bgJobCountInMaxPeriod = 20; 618 expectedStats.sessionCountInWindow = 5; 619 expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS 620 + mQcConstants.IN_QUOTA_BUFFER_MS; 621 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 622 assertEquals(expectedStats, inputStats); 623 } 624 625 @Test testUpdateExecutionStatsLocked_WithTimer()626 public void testUpdateExecutionStatsLocked_WithTimer() { 627 final long now = sElapsedRealtimeClock.millis(); 628 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 629 630 ExecutionStats expectedStats = new ExecutionStats(); 631 ExecutionStats inputStats = new ExecutionStats(); 632 inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 633 inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 634 inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 635 mQcConstants.MAX_SESSION_COUNT_RARE; 636 // Active timer isn't counted as session yet. 637 expectedStats.sessionCountInWindow = 0; 638 // Timer only, under quota. 639 for (int i = 1; i < mQcConstants.MAX_JOB_COUNT_RARE; ++i) { 640 JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", i); 641 setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window 642 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 643 mQuotaController.prepareForExecutionLocked(jobStatus); 644 advanceElapsedClock(7000); 645 646 expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis(); 647 expectedStats.executionTimeInWindowMs = expectedStats.executionTimeInMaxPeriodMs = 648 7000 * i; 649 expectedStats.bgJobCountInWindow = expectedStats.bgJobCountInMaxPeriod = i; 650 mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); 651 assertEquals(expectedStats, inputStats); 652 assertTrue(mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 653 RARE_INDEX)); 654 assertTrue("Job not ready: " + jobStatus, jobStatus.isReady()); 655 } 656 657 // Add old session. Make sure values are combined correctly. 658 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 659 createTimingSession(sElapsedRealtimeClock.millis() - (6 * HOUR_IN_MILLIS), 660 10 * MINUTE_IN_MILLIS, 5)); 661 expectedStats.sessionCountInWindow = 1; 662 663 expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS; 664 expectedStats.executionTimeInWindowMs += 10 * MINUTE_IN_MILLIS; 665 expectedStats.executionTimeInMaxPeriodMs += 10 * MINUTE_IN_MILLIS; 666 expectedStats.bgJobCountInWindow += 5; 667 expectedStats.bgJobCountInMaxPeriod += 5; 668 // Active timer is under quota, so out of quota due to old session. 669 expectedStats.inQuotaTimeElapsed = 670 sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS; 671 mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); 672 assertEquals(expectedStats, inputStats); 673 assertFalse( 674 mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); 675 676 // Quota should be exceeded due to activity in active timer. 677 JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", 0); 678 setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window 679 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 680 mQuotaController.prepareForExecutionLocked(jobStatus); 681 advanceElapsedClock(10000); 682 683 expectedStats.executionTimeInWindowMs += 10000; 684 expectedStats.executionTimeInMaxPeriodMs += 10000; 685 expectedStats.bgJobCountInWindow++; 686 expectedStats.bgJobCountInMaxPeriod++; 687 // Out of quota due to activity in active timer, so in quota time should be when enough 688 // time has passed since active timer. 689 expectedStats.inQuotaTimeElapsed = 690 sElapsedRealtimeClock.millis() + expectedStats.windowSizeMs; 691 mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); 692 assertEquals(expectedStats, inputStats); 693 assertFalse( 694 mQuotaController.isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); 695 assertFalse("Job unexpectedly ready: " + jobStatus, jobStatus.isReady()); 696 } 697 698 /** 699 * Tests that getExecutionStatsLocked returns the correct stats. 700 */ 701 @Test testGetExecutionStatsLocked_Values()702 public void testGetExecutionStatsLocked_Values() { 703 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 704 mQuotaController.saveTimingSession(0, "com.android.test", 705 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); 706 mQuotaController.saveTimingSession(0, "com.android.test", 707 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); 708 mQuotaController.saveTimingSession(0, "com.android.test", 709 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); 710 mQuotaController.saveTimingSession(0, "com.android.test", 711 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 712 713 ExecutionStats expectedStats = new ExecutionStats(); 714 715 // Active 716 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; 717 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; 718 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; 719 expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS; 720 expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS; 721 expectedStats.bgJobCountInWindow = 5; 722 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; 723 expectedStats.bgJobCountInMaxPeriod = 20; 724 expectedStats.sessionCountInWindow = 1; 725 assertEquals(expectedStats, 726 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX)); 727 728 // Working 729 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; 730 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING; 731 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING; 732 expectedStats.expirationTimeElapsed = now; 733 expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS; 734 expectedStats.bgJobCountInWindow = 10; 735 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; 736 expectedStats.bgJobCountInMaxPeriod = 20; 737 expectedStats.sessionCountInWindow = 2; 738 expectedStats.inQuotaTimeElapsed = now + 3 * MINUTE_IN_MILLIS 739 + mQcConstants.IN_QUOTA_BUFFER_MS; 740 assertEquals(expectedStats, 741 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX)); 742 743 // Frequent 744 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; 745 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT; 746 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT; 747 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 748 expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS; 749 expectedStats.bgJobCountInWindow = 15; 750 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; 751 expectedStats.bgJobCountInMaxPeriod = 20; 752 expectedStats.sessionCountInWindow = 3; 753 expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS 754 + mQcConstants.IN_QUOTA_BUFFER_MS; 755 assertEquals(expectedStats, 756 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX)); 757 758 // Rare 759 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 760 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 761 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; 762 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 763 expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS; 764 expectedStats.bgJobCountInWindow = 20; 765 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; 766 expectedStats.bgJobCountInMaxPeriod = 20; 767 expectedStats.sessionCountInWindow = 4; 768 expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS 769 + mQcConstants.IN_QUOTA_BUFFER_MS; 770 assertEquals(expectedStats, 771 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX)); 772 } 773 774 /** 775 * Tests that getExecutionStatsLocked returns the correct stats soon after device startup. 776 */ 777 @Test testGetExecutionStatsLocked_Values_BeginningOfTime()778 public void testGetExecutionStatsLocked_Values_BeginningOfTime() { 779 // Set time to 3 minutes after boot. 780 advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis()); 781 advanceElapsedClock(3 * MINUTE_IN_MILLIS); 782 783 mQuotaController.saveTimingSession(0, "com.android.test", 784 createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2)); 785 786 ExecutionStats expectedStats = new ExecutionStats(); 787 788 // Active 789 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; 790 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; 791 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; 792 expectedStats.expirationTimeElapsed = 11 * MINUTE_IN_MILLIS; 793 expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS; 794 expectedStats.bgJobCountInWindow = 2; 795 expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS; 796 expectedStats.bgJobCountInMaxPeriod = 2; 797 expectedStats.sessionCountInWindow = 1; 798 assertEquals(expectedStats, 799 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX)); 800 801 // Working 802 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; 803 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING; 804 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING; 805 expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; 806 assertEquals(expectedStats, 807 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX)); 808 809 // Frequent 810 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; 811 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT; 812 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT; 813 expectedStats.expirationTimeElapsed = 8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; 814 assertEquals(expectedStats, 815 mQuotaController.getExecutionStatsLocked(0, "com.android.test", FREQUENT_INDEX)); 816 817 // Rare 818 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 819 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 820 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; 821 expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; 822 assertEquals(expectedStats, 823 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX)); 824 } 825 826 /** 827 * Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing. 828 */ 829 @Test testGetExecutionStatsLocked_CoalescingSessions()830 public void testGetExecutionStatsLocked_CoalescingSessions() { 831 for (int i = 0; i < 10; ++i) { 832 mQuotaController.saveTimingSession(0, "com.android.test", 833 createTimingSession( 834 JobSchedulerService.sElapsedRealtimeClock.millis(), 835 5 * MINUTE_IN_MILLIS, 5)); 836 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 837 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 838 for (int j = 0; j < 5; ++j) { 839 mQuotaController.saveTimingSession(0, "com.android.test", 840 createTimingSession( 841 JobSchedulerService.sElapsedRealtimeClock.millis(), 842 MINUTE_IN_MILLIS, 2)); 843 advanceElapsedClock(MINUTE_IN_MILLIS); 844 advanceElapsedClock(54 * SECOND_IN_MILLIS); 845 mQuotaController.saveTimingSession(0, "com.android.test", 846 createTimingSession( 847 JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1)); 848 advanceElapsedClock(500); 849 advanceElapsedClock(400); 850 mQuotaController.saveTimingSession(0, "com.android.test", 851 createTimingSession( 852 JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1)); 853 advanceElapsedClock(100); 854 advanceElapsedClock(5 * SECOND_IN_MILLIS); 855 } 856 advanceElapsedClock(40 * MINUTE_IN_MILLIS); 857 } 858 859 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 0; 860 mQcConstants.updateConstants(); 861 862 mQuotaController.invalidateAllExecutionStatsLocked(); 863 assertEquals(0, mQuotaController.getExecutionStatsLocked( 864 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 865 assertEquals(32, mQuotaController.getExecutionStatsLocked( 866 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 867 assertEquals(128, mQuotaController.getExecutionStatsLocked( 868 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 869 assertEquals(160, mQuotaController.getExecutionStatsLocked( 870 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 871 872 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 500; 873 mQcConstants.updateConstants(); 874 875 mQuotaController.invalidateAllExecutionStatsLocked(); 876 assertEquals(0, mQuotaController.getExecutionStatsLocked( 877 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 878 assertEquals(22, mQuotaController.getExecutionStatsLocked( 879 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 880 assertEquals(88, mQuotaController.getExecutionStatsLocked( 881 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 882 assertEquals(110, mQuotaController.getExecutionStatsLocked( 883 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 884 885 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 1000; 886 mQcConstants.updateConstants(); 887 888 mQuotaController.invalidateAllExecutionStatsLocked(); 889 assertEquals(0, mQuotaController.getExecutionStatsLocked( 890 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 891 assertEquals(22, mQuotaController.getExecutionStatsLocked( 892 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 893 assertEquals(88, mQuotaController.getExecutionStatsLocked( 894 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 895 assertEquals(110, mQuotaController.getExecutionStatsLocked( 896 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 897 898 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 5 * SECOND_IN_MILLIS; 899 mQcConstants.updateConstants(); 900 901 mQuotaController.invalidateAllExecutionStatsLocked(); 902 assertEquals(0, mQuotaController.getExecutionStatsLocked( 903 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 904 assertEquals(14, mQuotaController.getExecutionStatsLocked( 905 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 906 assertEquals(56, mQuotaController.getExecutionStatsLocked( 907 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 908 assertEquals(70, mQuotaController.getExecutionStatsLocked( 909 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 910 911 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = MINUTE_IN_MILLIS; 912 mQcConstants.updateConstants(); 913 914 mQuotaController.invalidateAllExecutionStatsLocked(); 915 assertEquals(0, mQuotaController.getExecutionStatsLocked( 916 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 917 assertEquals(4, mQuotaController.getExecutionStatsLocked( 918 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 919 assertEquals(16, mQuotaController.getExecutionStatsLocked( 920 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 921 assertEquals(20, mQuotaController.getExecutionStatsLocked( 922 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 923 924 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 5 * MINUTE_IN_MILLIS; 925 mQcConstants.updateConstants(); 926 927 mQuotaController.invalidateAllExecutionStatsLocked(); 928 assertEquals(0, mQuotaController.getExecutionStatsLocked( 929 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 930 assertEquals(2, mQuotaController.getExecutionStatsLocked( 931 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 932 assertEquals(8, mQuotaController.getExecutionStatsLocked( 933 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 934 assertEquals(10, mQuotaController.getExecutionStatsLocked( 935 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 936 937 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 15 * MINUTE_IN_MILLIS; 938 mQcConstants.updateConstants(); 939 940 mQuotaController.invalidateAllExecutionStatsLocked(); 941 assertEquals(0, mQuotaController.getExecutionStatsLocked( 942 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 943 assertEquals(2, mQuotaController.getExecutionStatsLocked( 944 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 945 assertEquals(8, mQuotaController.getExecutionStatsLocked( 946 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 947 assertEquals(10, mQuotaController.getExecutionStatsLocked( 948 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 949 950 // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference 951 // between an hour and 15 minutes. 952 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = HOUR_IN_MILLIS; 953 mQcConstants.updateConstants(); 954 955 mQuotaController.invalidateAllExecutionStatsLocked(); 956 assertEquals(0, mQuotaController.getExecutionStatsLocked( 957 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 958 assertEquals(2, mQuotaController.getExecutionStatsLocked( 959 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 960 assertEquals(8, mQuotaController.getExecutionStatsLocked( 961 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 962 assertEquals(10, mQuotaController.getExecutionStatsLocked( 963 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 964 } 965 966 /** 967 * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object. 968 */ 969 @Test testGetExecutionStatsLocked_Caching()970 public void testGetExecutionStatsLocked_Caching() { 971 spyOn(mQuotaController); 972 doNothing().when(mQuotaController).invalidateAllExecutionStatsLocked(); 973 974 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 975 mQuotaController.saveTimingSession(0, "com.android.test", 976 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); 977 mQuotaController.saveTimingSession(0, "com.android.test", 978 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); 979 mQuotaController.saveTimingSession(0, "com.android.test", 980 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); 981 mQuotaController.saveTimingSession(0, "com.android.test", 982 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 983 final ExecutionStats originalStatsActive = mQuotaController.getExecutionStatsLocked(0, 984 "com.android.test", ACTIVE_INDEX); 985 final ExecutionStats originalStatsWorking = mQuotaController.getExecutionStatsLocked(0, 986 "com.android.test", WORKING_INDEX); 987 final ExecutionStats originalStatsFrequent = mQuotaController.getExecutionStatsLocked(0, 988 "com.android.test", FREQUENT_INDEX); 989 final ExecutionStats originalStatsRare = mQuotaController.getExecutionStatsLocked(0, 990 "com.android.test", RARE_INDEX); 991 992 // Advance clock so that the working stats shouldn't be the same. 993 advanceElapsedClock(MINUTE_IN_MILLIS); 994 // Change frequent bucket size so that the stats need to be recalculated. 995 mQcConstants.WINDOW_SIZE_FREQUENT_MS = 6 * HOUR_IN_MILLIS; 996 mQcConstants.updateConstants(); 997 998 ExecutionStats expectedStats = new ExecutionStats(); 999 expectedStats.windowSizeMs = originalStatsActive.windowSizeMs; 1000 expectedStats.jobCountLimit = originalStatsActive.jobCountLimit; 1001 expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit; 1002 expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed; 1003 expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs; 1004 expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow; 1005 expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs; 1006 expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod; 1007 expectedStats.sessionCountInWindow = originalStatsActive.sessionCountInWindow; 1008 expectedStats.inQuotaTimeElapsed = originalStatsActive.inQuotaTimeElapsed; 1009 final ExecutionStats newStatsActive = mQuotaController.getExecutionStatsLocked(0, 1010 "com.android.test", ACTIVE_INDEX); 1011 // Stats for the same bucket should use the same object. 1012 assertTrue(originalStatsActive == newStatsActive); 1013 assertEquals(expectedStats, newStatsActive); 1014 1015 expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs; 1016 expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit; 1017 expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit; 1018 expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed; 1019 expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs; 1020 expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow; 1021 expectedStats.sessionCountInWindow = originalStatsWorking.sessionCountInWindow; 1022 expectedStats.inQuotaTimeElapsed = originalStatsWorking.inQuotaTimeElapsed; 1023 final ExecutionStats newStatsWorking = mQuotaController.getExecutionStatsLocked(0, 1024 "com.android.test", WORKING_INDEX); 1025 assertTrue(originalStatsWorking == newStatsWorking); 1026 assertNotEquals(expectedStats, newStatsWorking); 1027 1028 expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs; 1029 expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit; 1030 expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit; 1031 expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed; 1032 expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs; 1033 expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow; 1034 expectedStats.sessionCountInWindow = originalStatsFrequent.sessionCountInWindow; 1035 expectedStats.inQuotaTimeElapsed = originalStatsFrequent.inQuotaTimeElapsed; 1036 final ExecutionStats newStatsFrequent = mQuotaController.getExecutionStatsLocked(0, 1037 "com.android.test", FREQUENT_INDEX); 1038 assertTrue(originalStatsFrequent == newStatsFrequent); 1039 assertNotEquals(expectedStats, newStatsFrequent); 1040 1041 expectedStats.windowSizeMs = originalStatsRare.windowSizeMs; 1042 expectedStats.jobCountLimit = originalStatsRare.jobCountLimit; 1043 expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit; 1044 expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed; 1045 expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs; 1046 expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow; 1047 expectedStats.sessionCountInWindow = originalStatsRare.sessionCountInWindow; 1048 expectedStats.inQuotaTimeElapsed = originalStatsRare.inQuotaTimeElapsed; 1049 final ExecutionStats newStatsRare = mQuotaController.getExecutionStatsLocked(0, 1050 "com.android.test", RARE_INDEX); 1051 assertTrue(originalStatsRare == newStatsRare); 1052 assertEquals(expectedStats, newStatsRare); 1053 } 1054 1055 @Test testGetMaxJobExecutionTimeLocked()1056 public void testGetMaxJobExecutionTimeLocked() { 1057 mQuotaController.saveTimingSession(0, SOURCE_PACKAGE, 1058 createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS), 1059 3 * MINUTE_IN_MILLIS, 5)); 1060 JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0); 1061 job.setStandbyBucket(RARE_INDEX); 1062 1063 setCharging(); 1064 assertEquals(JobServiceContext.EXECUTING_TIMESLICE_MILLIS, 1065 mQuotaController.getMaxJobExecutionTimeMsLocked((job))); 1066 1067 setDischarging(); 1068 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 1069 assertEquals(JobServiceContext.EXECUTING_TIMESLICE_MILLIS, 1070 mQuotaController.getMaxJobExecutionTimeMsLocked((job))); 1071 1072 // Top-started job 1073 setProcessState(ActivityManager.PROCESS_STATE_TOP); 1074 mQuotaController.maybeStartTrackingJobLocked(job, null); 1075 mQuotaController.prepareForExecutionLocked(job); 1076 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 1077 assertEquals(JobServiceContext.EXECUTING_TIMESLICE_MILLIS, 1078 mQuotaController.getMaxJobExecutionTimeMsLocked((job))); 1079 mQuotaController.maybeStopTrackingJobLocked(job, null, false); 1080 1081 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 1082 assertEquals(7 * MINUTE_IN_MILLIS, 1083 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 1084 } 1085 1086 /** 1087 * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket 1088 * window. 1089 */ 1090 @Test testGetTimeUntilQuotaConsumedLocked_BucketWindow()1091 public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() { 1092 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1093 // Close to RARE boundary. 1094 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1095 createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), 1096 30 * SECOND_IN_MILLIS, 5)); 1097 // Far away from FREQUENT boundary. 1098 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1099 createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 1100 // Overlap WORKING_SET boundary. 1101 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1102 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 1103 3 * MINUTE_IN_MILLIS, 5)); 1104 // Close to ACTIVE boundary. 1105 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1106 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 1107 1108 setStandbyBucket(RARE_INDEX); 1109 assertEquals(30 * SECOND_IN_MILLIS, 1110 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1111 assertEquals(MINUTE_IN_MILLIS, 1112 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1113 1114 setStandbyBucket(FREQUENT_INDEX); 1115 assertEquals(MINUTE_IN_MILLIS, 1116 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1117 assertEquals(MINUTE_IN_MILLIS, 1118 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1119 1120 setStandbyBucket(WORKING_INDEX); 1121 assertEquals(5 * MINUTE_IN_MILLIS, 1122 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1123 assertEquals(7 * MINUTE_IN_MILLIS, 1124 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1125 1126 // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the 1127 // max execution time. 1128 setStandbyBucket(ACTIVE_INDEX); 1129 assertEquals(7 * MINUTE_IN_MILLIS, 1130 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1131 assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS, 1132 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1133 } 1134 1135 /** 1136 * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit. 1137 */ 1138 @Test testGetTimeUntilQuotaConsumedLocked_MaxExecution()1139 public void testGetTimeUntilQuotaConsumedLocked_MaxExecution() { 1140 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1141 // Overlap boundary. 1142 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1143 createTimingSession( 1144 now - (24 * HOUR_IN_MILLIS + 8 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5)); 1145 1146 setStandbyBucket(WORKING_INDEX); 1147 assertEquals(8 * MINUTE_IN_MILLIS, 1148 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1149 // Max time will phase out, so should use bucket limit. 1150 assertEquals(10 * MINUTE_IN_MILLIS, 1151 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1152 1153 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1154 // Close to boundary. 1155 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1156 createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS), 1157 4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5)); 1158 1159 setStandbyBucket(WORKING_INDEX); 1160 assertEquals(5 * MINUTE_IN_MILLIS, 1161 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1162 assertEquals(10 * MINUTE_IN_MILLIS, 1163 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1164 1165 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1166 // Far from boundary. 1167 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1168 createTimingSession( 1169 now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5)); 1170 1171 setStandbyBucket(WORKING_INDEX); 1172 assertEquals(3 * MINUTE_IN_MILLIS, 1173 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1174 assertEquals(3 * MINUTE_IN_MILLIS, 1175 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1176 } 1177 1178 /** 1179 * Test getTimeUntilQuotaConsumedLocked when the max execution time and bucket window time 1180 * remaining are equal. 1181 */ 1182 @Test testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining()1183 public void testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining() { 1184 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1185 setStandbyBucket(FREQUENT_INDEX); 1186 1187 // Overlap boundary. 1188 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1189 createTimingSession( 1190 now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS), 1191 4 * HOUR_IN_MILLIS, 1192 5)); 1193 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1194 createTimingSession( 1195 now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 1196 1197 // Both max and bucket time have 8 minutes left. 1198 assertEquals(8 * MINUTE_IN_MILLIS, 1199 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1200 // Max time essentially free. Bucket time has 2 min phase out plus original 8 minute 1201 // window time. 1202 assertEquals(10 * MINUTE_IN_MILLIS, 1203 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1204 1205 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1206 // Overlap boundary. 1207 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1208 createTimingSession( 1209 now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5)); 1210 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1211 createTimingSession( 1212 now - (20 * HOUR_IN_MILLIS), 1213 3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS, 1214 5)); 1215 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1216 createTimingSession( 1217 now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 1218 1219 // Both max and bucket time have 8 minutes left. 1220 assertEquals(8 * MINUTE_IN_MILLIS, 1221 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1222 // Max time only has one minute phase out. Bucket time has 2 minute phase out. 1223 assertEquals(9 * MINUTE_IN_MILLIS, 1224 mQuotaController.getTimeUntilQuotaConsumedLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 1225 } 1226 1227 @Test testIsWithinQuotaLocked_NeverApp()1228 public void testIsWithinQuotaLocked_NeverApp() { 1229 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX)); 1230 } 1231 1232 @Test testIsWithinQuotaLocked_Charging()1233 public void testIsWithinQuotaLocked_Charging() { 1234 setCharging(); 1235 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX)); 1236 } 1237 1238 @Test testIsWithinQuotaLocked_UnderDuration_UnderJobCount()1239 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() { 1240 setDischarging(); 1241 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1242 mQuotaController.saveTimingSession(0, "com.android.test", 1243 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 1244 mQuotaController.saveTimingSession(0, "com.android.test", 1245 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 1246 mQuotaController.incrementJobCount(0, "com.android.test", 5); 1247 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); 1248 } 1249 1250 @Test testIsWithinQuotaLocked_UnderDuration_OverJobCount()1251 public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() { 1252 setDischarging(); 1253 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1254 final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; 1255 mQuotaController.saveTimingSession(0, "com.android.test.spam", 1256 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25)); 1257 mQuotaController.saveTimingSession(0, "com.android.test.spam", 1258 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount)); 1259 mQuotaController.incrementJobCount(0, "com.android.test.spam", jobCount); 1260 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.spam", 1261 WORKING_INDEX)); 1262 1263 mQuotaController.saveTimingSession(0, "com.android.test.frequent", 1264 createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000)); 1265 mQuotaController.saveTimingSession(0, "com.android.test.frequent", 1266 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500)); 1267 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test.frequent", 1268 FREQUENT_INDEX)); 1269 } 1270 1271 @Test testIsWithinQuotaLocked_OverDuration_UnderJobCount()1272 public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() { 1273 setDischarging(); 1274 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1275 mQuotaController.saveTimingSession(0, "com.android.test", 1276 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 1277 mQuotaController.saveTimingSession(0, "com.android.test", 1278 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5)); 1279 mQuotaController.saveTimingSession(0, "com.android.test", 1280 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5)); 1281 mQuotaController.incrementJobCount(0, "com.android.test", 5); 1282 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); 1283 } 1284 1285 @Test testIsWithinQuotaLocked_OverDuration_OverJobCount()1286 public void testIsWithinQuotaLocked_OverDuration_OverJobCount() { 1287 setDischarging(); 1288 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1289 final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; 1290 mQuotaController.saveTimingSession(0, "com.android.test", 1291 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25)); 1292 mQuotaController.saveTimingSession(0, "com.android.test", 1293 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount)); 1294 mQuotaController.incrementJobCount(0, "com.android.test", jobCount); 1295 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); 1296 } 1297 1298 @Test testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS()1299 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS() { 1300 setDischarging(); 1301 1302 JobStatus jobStatus = createJobStatus( 1303 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS", 1); 1304 setStandbyBucket(ACTIVE_INDEX, jobStatus); 1305 setProcessState(ActivityManager.PROCESS_STATE_BACKUP); 1306 1307 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 1308 mQuotaController.prepareForExecutionLocked(jobStatus); 1309 for (int i = 0; i < 20; ++i) { 1310 advanceElapsedClock(SECOND_IN_MILLIS); 1311 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 1312 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 1313 } 1314 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); 1315 1316 advanceElapsedClock(15 * SECOND_IN_MILLIS); 1317 1318 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 1319 mQuotaController.prepareForExecutionLocked(jobStatus); 1320 for (int i = 0; i < 20; ++i) { 1321 advanceElapsedClock(SECOND_IN_MILLIS); 1322 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 1323 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 1324 } 1325 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); 1326 1327 advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS); 1328 1329 assertEquals(2, mQuotaController.getExecutionStatsLocked( 1330 SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInRateLimitingWindow); 1331 assertTrue(mQuotaController.isWithinQuotaLocked(jobStatus)); 1332 assertTrue(jobStatus.isReady()); 1333 } 1334 1335 @Test testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps()1336 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps() 1337 throws Exception { 1338 setDischarging(); 1339 1340 final String unaffectedPkgName = "com.android.unaffected"; 1341 final int unaffectedUid = 10987; 1342 JobInfo unaffectedJobInfo = new JobInfo.Builder(1, 1343 new ComponentName(unaffectedPkgName, "foo")) 1344 .build(); 1345 JobStatus unaffected = createJobStatus( 1346 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps", 1347 unaffectedPkgName, unaffectedUid, unaffectedJobInfo); 1348 setStandbyBucket(FREQUENT_INDEX, unaffected); 1349 setProcessState(ActivityManager.PROCESS_STATE_SERVICE, unaffectedUid); 1350 1351 final String fgChangerPkgName = "com.android.foreground.changer"; 1352 final int fgChangerUid = 10234; 1353 JobInfo fgChangerJobInfo = new JobInfo.Builder(2, 1354 new ComponentName(fgChangerPkgName, "foo")) 1355 .build(); 1356 JobStatus fgStateChanger = createJobStatus( 1357 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps", 1358 fgChangerPkgName, fgChangerUid, fgChangerJobInfo); 1359 setStandbyBucket(ACTIVE_INDEX, fgStateChanger); 1360 setProcessState(ActivityManager.PROCESS_STATE_BACKUP, fgChangerUid); 1361 1362 IPackageManager packageManager = AppGlobals.getPackageManager(); 1363 spyOn(packageManager); 1364 doReturn(new String[]{unaffectedPkgName}) 1365 .when(packageManager).getPackagesForUid(unaffectedUid); 1366 doReturn(new String[]{fgChangerPkgName}) 1367 .when(packageManager).getPackagesForUid(fgChangerUid); 1368 1369 mQuotaController.maybeStartTrackingJobLocked(unaffected, null); 1370 mQuotaController.prepareForExecutionLocked(unaffected); 1371 1372 mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null); 1373 mQuotaController.prepareForExecutionLocked(fgStateChanger); 1374 for (int i = 0; i < 20; ++i) { 1375 advanceElapsedClock(SECOND_IN_MILLIS); 1376 setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid); 1377 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid); 1378 } 1379 mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false); 1380 1381 advanceElapsedClock(15 * SECOND_IN_MILLIS); 1382 1383 mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null); 1384 mQuotaController.prepareForExecutionLocked(fgStateChanger); 1385 for (int i = 0; i < 20; ++i) { 1386 advanceElapsedClock(SECOND_IN_MILLIS); 1387 setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid); 1388 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid); 1389 } 1390 mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false); 1391 1392 mQuotaController.maybeStopTrackingJobLocked(unaffected, null, false); 1393 1394 assertTrue(mQuotaController.isWithinQuotaLocked(unaffected)); 1395 assertTrue(unaffected.isReady()); 1396 assertFalse(mQuotaController.isWithinQuotaLocked(fgStateChanger)); 1397 assertFalse(fgStateChanger.isReady()); 1398 assertEquals(1, 1399 mQuotaController.getTimingSessions(SOURCE_USER_ID, unaffectedPkgName).size()); 1400 assertEquals(42, 1401 mQuotaController.getTimingSessions(SOURCE_USER_ID, fgChangerPkgName).size()); 1402 for (int i = ACTIVE_INDEX; i < RARE_INDEX; ++i) { 1403 assertEquals(42, mQuotaController.getExecutionStatsLocked( 1404 SOURCE_USER_ID, fgChangerPkgName, i).jobCountInRateLimitingWindow); 1405 assertEquals(1, mQuotaController.getExecutionStatsLocked( 1406 SOURCE_USER_ID, unaffectedPkgName, i).jobCountInRateLimitingWindow); 1407 } 1408 } 1409 1410 @Test testIsWithinQuotaLocked_TimingSession()1411 public void testIsWithinQuotaLocked_TimingSession() { 1412 setDischarging(); 1413 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1414 mQcConstants.MAX_SESSION_COUNT_RARE = 3; 1415 mQcConstants.MAX_SESSION_COUNT_FREQUENT = 4; 1416 mQcConstants.MAX_SESSION_COUNT_WORKING = 5; 1417 mQcConstants.MAX_SESSION_COUNT_ACTIVE = 6; 1418 mQcConstants.updateConstants(); 1419 1420 for (int i = 0; i < 7; ++i) { 1421 mQuotaController.saveTimingSession(0, "com.android.test", 1422 createTimingSession(now - ((10 - i) * MINUTE_IN_MILLIS), 30 * SECOND_IN_MILLIS, 1423 2)); 1424 mQuotaController.incrementJobCount(0, "com.android.test", 2); 1425 1426 assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions", 1427 i < 2, 1428 mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX)); 1429 assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions", 1430 i < 3, 1431 mQuotaController.isWithinQuotaLocked(0, "com.android.test", FREQUENT_INDEX)); 1432 assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions", 1433 i < 4, 1434 mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); 1435 assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions", 1436 i < 5, 1437 mQuotaController.isWithinQuotaLocked(0, "com.android.test", ACTIVE_INDEX)); 1438 } 1439 } 1440 1441 @Test testMaybeScheduleCleanupAlarmLocked()1442 public void testMaybeScheduleCleanupAlarmLocked() { 1443 // No sessions saved yet. 1444 mQuotaController.maybeScheduleCleanupAlarmLocked(); 1445 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any()); 1446 1447 // Test with only one timing session saved. 1448 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1449 final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); 1450 mQuotaController.saveTimingSession(0, "com.android.test", 1451 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1)); 1452 mQuotaController.maybeScheduleCleanupAlarmLocked(); 1453 verify(mAlarmManager, times(1)) 1454 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); 1455 1456 // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again. 1457 mQuotaController.saveTimingSession(0, "com.android.test", 1458 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1)); 1459 mQuotaController.saveTimingSession(0, "com.android.test", 1460 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1)); 1461 mQuotaController.maybeScheduleCleanupAlarmLocked(); 1462 verify(mAlarmManager, times(1)) 1463 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); 1464 } 1465 1466 @Test testMaybeScheduleStartAlarmLocked_Active()1467 public void testMaybeScheduleStartAlarmLocked_Active() { 1468 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 1469 // because it schedules an alarm too. Prevent it from doing so. 1470 spyOn(mQuotaController); 1471 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 1472 1473 // Active window size is 10 minutes. 1474 final int standbyBucket = ACTIVE_INDEX; 1475 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); 1476 1477 // No sessions saved yet. 1478 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1479 standbyBucket); 1480 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1481 1482 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1483 // Test with timing sessions out of window but still under max execution limit. 1484 final long expectedAlarmTime = 1485 (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; 1486 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1487 createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1)); 1488 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1489 createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1)); 1490 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1491 createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1)); 1492 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1493 standbyBucket); 1494 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1495 1496 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1497 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1)); 1498 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1499 standbyBucket); 1500 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1501 1502 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1); 1503 setStandbyBucket(standbyBucket, jobStatus); 1504 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 1505 mQuotaController.prepareForExecutionLocked(jobStatus); 1506 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 1507 // Timer has only been going for 5 minutes in the past 10 minutes, which is under the window 1508 // size limit, but the total execution time for the past 24 hours is 6 hours, so the job no 1509 // longer has quota. 1510 assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus)); 1511 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1512 standbyBucket); 1513 verify(mAlarmManager, times(1)).set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), 1514 any(), any()); 1515 } 1516 1517 @Test testMaybeScheduleStartAlarmLocked_WorkingSet()1518 public void testMaybeScheduleStartAlarmLocked_WorkingSet() { 1519 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 1520 // because it schedules an alarm too. Prevent it from doing so. 1521 spyOn(mQuotaController); 1522 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 1523 1524 // Working set window size is 2 hours. 1525 final int standbyBucket = WORKING_INDEX; 1526 1527 // No sessions saved yet. 1528 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1529 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1530 1531 // Test with timing sessions out of window. 1532 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1533 mQuotaController.saveTimingSession(0, "com.android.test", 1534 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1)); 1535 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1536 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1537 1538 // Test with timing sessions in window but still in quota. 1539 final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); 1540 // Counting backwards, the quota will come back one minute before the end. 1541 final long expectedAlarmTime = 1542 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; 1543 mQuotaController.saveTimingSession(0, "com.android.test", 1544 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1)); 1545 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1546 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1547 1548 // Add some more sessions, but still in quota. 1549 mQuotaController.saveTimingSession(0, "com.android.test", 1550 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1)); 1551 mQuotaController.saveTimingSession(0, "com.android.test", 1552 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1)); 1553 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1554 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1555 1556 // Test when out of quota. 1557 mQuotaController.saveTimingSession(0, "com.android.test", 1558 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1)); 1559 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1560 verify(mAlarmManager, times(1)) 1561 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1562 1563 // Alarm already scheduled, so make sure it's not scheduled again. 1564 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1565 verify(mAlarmManager, times(1)) 1566 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1567 } 1568 1569 @Test testMaybeScheduleStartAlarmLocked_Frequent()1570 public void testMaybeScheduleStartAlarmLocked_Frequent() { 1571 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 1572 // because it schedules an alarm too. Prevent it from doing so. 1573 spyOn(mQuotaController); 1574 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 1575 1576 // Frequent window size is 8 hours. 1577 final int standbyBucket = FREQUENT_INDEX; 1578 1579 // No sessions saved yet. 1580 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1581 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1582 1583 // Test with timing sessions out of window. 1584 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1585 mQuotaController.saveTimingSession(0, "com.android.test", 1586 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1)); 1587 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1588 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1589 1590 // Test with timing sessions in window but still in quota. 1591 final long start = now - (6 * HOUR_IN_MILLIS); 1592 final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; 1593 mQuotaController.saveTimingSession(0, "com.android.test", 1594 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1)); 1595 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1596 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1597 1598 // Add some more sessions, but still in quota. 1599 mQuotaController.saveTimingSession(0, "com.android.test", 1600 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1)); 1601 mQuotaController.saveTimingSession(0, "com.android.test", 1602 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1)); 1603 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1604 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1605 1606 // Test when out of quota. 1607 mQuotaController.saveTimingSession(0, "com.android.test", 1608 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1)); 1609 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1610 verify(mAlarmManager, times(1)) 1611 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1612 1613 // Alarm already scheduled, so make sure it's not scheduled again. 1614 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1615 verify(mAlarmManager, times(1)) 1616 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1617 } 1618 1619 @Test testMaybeScheduleStartAlarmLocked_Rare()1620 public void testMaybeScheduleStartAlarmLocked_Rare() { 1621 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 1622 // because it schedules an alarm too. Prevent it from doing so. 1623 spyOn(mQuotaController); 1624 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 1625 1626 // Rare window size is 24 hours. 1627 final int standbyBucket = RARE_INDEX; 1628 1629 // Prevent timing session throttling from affecting the test. 1630 mQcConstants.MAX_SESSION_COUNT_RARE = 50; 1631 mQcConstants.updateConstants(); 1632 1633 // No sessions saved yet. 1634 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1635 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1636 1637 // Test with timing sessions out of window. 1638 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1639 mQuotaController.saveTimingSession(0, "com.android.test", 1640 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1)); 1641 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1642 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1643 1644 // Test with timing sessions in window but still in quota. 1645 final long start = now - (6 * HOUR_IN_MILLIS); 1646 // Counting backwards, the first minute in the session is over the allowed time, so it 1647 // needs to be excluded. 1648 final long expectedAlarmTime = 1649 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS 1650 + mQcConstants.IN_QUOTA_BUFFER_MS; 1651 mQuotaController.saveTimingSession(0, "com.android.test", 1652 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1)); 1653 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1654 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1655 1656 // Add some more sessions, but still in quota. 1657 mQuotaController.saveTimingSession(0, "com.android.test", 1658 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1)); 1659 mQuotaController.saveTimingSession(0, "com.android.test", 1660 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1)); 1661 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1662 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1663 1664 // Test when out of quota. 1665 mQuotaController.saveTimingSession(0, "com.android.test", 1666 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1)); 1667 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1668 verify(mAlarmManager, times(1)) 1669 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1670 1671 // Alarm already scheduled, so make sure it's not scheduled again. 1672 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 1673 verify(mAlarmManager, times(1)) 1674 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1675 } 1676 1677 /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */ 1678 @Test testMaybeScheduleStartAlarmLocked_BucketChange()1679 public void testMaybeScheduleStartAlarmLocked_BucketChange() { 1680 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 1681 // because it schedules an alarm too. Prevent it from doing so. 1682 spyOn(mQuotaController); 1683 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 1684 1685 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1686 1687 // Affects rare bucket 1688 mQuotaController.saveTimingSession(0, "com.android.test", 1689 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3)); 1690 // Affects frequent and rare buckets 1691 mQuotaController.saveTimingSession(0, "com.android.test", 1692 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3)); 1693 // Affects working, frequent, and rare buckets 1694 final long outOfQuotaTime = now - HOUR_IN_MILLIS; 1695 mQuotaController.saveTimingSession(0, "com.android.test", 1696 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10)); 1697 // Affects all buckets 1698 mQuotaController.saveTimingSession(0, "com.android.test", 1699 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3)); 1700 1701 InOrder inOrder = inOrder(mAlarmManager); 1702 1703 // Start in ACTIVE bucket. 1704 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX); 1705 inOrder.verify(mAlarmManager, never()) 1706 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1707 inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class)); 1708 1709 // And down from there. 1710 final long expectedWorkingAlarmTime = 1711 outOfQuotaTime + (2 * HOUR_IN_MILLIS) 1712 + mQcConstants.IN_QUOTA_BUFFER_MS; 1713 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX); 1714 inOrder.verify(mAlarmManager, times(1)) 1715 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1716 1717 final long expectedFrequentAlarmTime = 1718 outOfQuotaTime + (8 * HOUR_IN_MILLIS) 1719 + mQcConstants.IN_QUOTA_BUFFER_MS; 1720 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX); 1721 inOrder.verify(mAlarmManager, times(1)) 1722 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1723 1724 final long expectedRareAlarmTime = 1725 outOfQuotaTime + (24 * HOUR_IN_MILLIS) 1726 + mQcConstants.IN_QUOTA_BUFFER_MS; 1727 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", RARE_INDEX); 1728 inOrder.verify(mAlarmManager, times(1)) 1729 .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1730 1731 // And back up again. 1732 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", FREQUENT_INDEX); 1733 inOrder.verify(mAlarmManager, times(1)) 1734 .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1735 1736 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", WORKING_INDEX); 1737 inOrder.verify(mAlarmManager, times(1)) 1738 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1739 1740 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", ACTIVE_INDEX); 1741 inOrder.verify(mAlarmManager, never()) 1742 .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1743 inOrder.verify(mAlarmManager, times(1)).cancel(any(AlarmManager.OnAlarmListener.class)); 1744 } 1745 1746 @Test testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow()1747 public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() { 1748 // Set rate limiting period different from allowed time to confirm code sets based on 1749 // the former. 1750 mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 10 * MINUTE_IN_MILLIS; 1751 mQcConstants.RATE_LIMITING_WINDOW_MS = 5 * MINUTE_IN_MILLIS; 1752 mQcConstants.updateConstants(); 1753 1754 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1755 final int standbyBucket = WORKING_INDEX; 1756 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, 1757 SOURCE_PACKAGE, standbyBucket); 1758 stats.jobCountInRateLimitingWindow = 1759 mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW + 2; 1760 1761 // Invalid time in the past, so the count shouldn't be used. 1762 stats.jobRateLimitExpirationTimeElapsed = now - 5 * MINUTE_IN_MILLIS / 2; 1763 mQuotaController.maybeScheduleStartAlarmLocked( 1764 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 1765 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 1766 1767 // Valid time in the future, so the count should be used. 1768 stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2; 1769 final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed; 1770 mQuotaController.maybeScheduleStartAlarmLocked( 1771 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 1772 verify(mAlarmManager, times(1)) 1773 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1774 } 1775 1776 /** 1777 * Tests that the start alarm is properly rescheduled if the earliest session that contributes 1778 * to the app being out of quota contributes less than the quota buffer time. 1779 */ 1780 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues()1781 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() { 1782 // Use the default values 1783 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 1784 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1785 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 1786 } 1787 1788 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize()1789 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() { 1790 // Make sure any new value is used correctly. 1791 mQcConstants.IN_QUOTA_BUFFER_MS *= 2; 1792 mQcConstants.updateConstants(); 1793 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 1794 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1795 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 1796 } 1797 1798 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime()1799 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() { 1800 // Make sure any new value is used correctly. 1801 mQcConstants.ALLOWED_TIME_PER_PERIOD_MS /= 2; 1802 mQcConstants.updateConstants(); 1803 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 1804 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1805 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 1806 } 1807 1808 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime()1809 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() { 1810 // Make sure any new value is used correctly. 1811 mQcConstants.MAX_EXECUTION_TIME_MS /= 2; 1812 mQcConstants.updateConstants(); 1813 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 1814 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1815 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 1816 } 1817 1818 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything()1819 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() { 1820 // Make sure any new value is used correctly. 1821 mQcConstants.IN_QUOTA_BUFFER_MS *= 2; 1822 mQcConstants.ALLOWED_TIME_PER_PERIOD_MS /= 2; 1823 mQcConstants.MAX_EXECUTION_TIME_MS /= 2; 1824 mQcConstants.updateConstants(); 1825 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 1826 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1827 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 1828 } 1829 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck()1830 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() { 1831 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 1832 // because it schedules an alarm too. Prevent it from doing so. 1833 spyOn(mQuotaController); 1834 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 1835 1836 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1837 // Working set window size is 2 hours. 1838 final int standbyBucket = WORKING_INDEX; 1839 final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2; 1840 final long remainingTimeMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_MS - contributionMs; 1841 1842 // Session straddles edge of bucket window. Only the contribution should be counted towards 1843 // the quota. 1844 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1845 createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS), 1846 3 * MINUTE_IN_MILLIS + contributionMs, 3)); 1847 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1848 createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2)); 1849 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which 1850 // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session. 1851 final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS 1852 + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs); 1853 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1854 standbyBucket); 1855 verify(mAlarmManager, times(1)) 1856 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1857 } 1858 1859 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck()1860 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() { 1861 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 1862 // because it schedules an alarm too. Prevent it from doing so. 1863 spyOn(mQuotaController); 1864 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 1865 1866 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1867 // Working set window size is 2 hours. 1868 final int standbyBucket = WORKING_INDEX; 1869 final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2; 1870 final long remainingTimeMs = mQcConstants.MAX_EXECUTION_TIME_MS - contributionMs; 1871 1872 // Session straddles edge of 24 hour window. Only the contribution should be counted towards 1873 // the quota. 1874 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1875 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS), 1876 3 * MINUTE_IN_MILLIS + contributionMs, 3)); 1877 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1878 createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300)); 1879 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which 1880 // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session. 1881 final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS 1882 + 24 * HOUR_IN_MILLIS 1883 + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs); 1884 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1885 standbyBucket); 1886 verify(mAlarmManager, times(1)) 1887 .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 1888 } 1889 1890 @Test testConstantsUpdating_ValidValues()1891 public void testConstantsUpdating_ValidValues() { 1892 mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 5 * MINUTE_IN_MILLIS; 1893 mQcConstants.IN_QUOTA_BUFFER_MS = 2 * MINUTE_IN_MILLIS; 1894 mQcConstants.WINDOW_SIZE_ACTIVE_MS = 15 * MINUTE_IN_MILLIS; 1895 mQcConstants.WINDOW_SIZE_WORKING_MS = 30 * MINUTE_IN_MILLIS; 1896 mQcConstants.WINDOW_SIZE_FREQUENT_MS = 45 * MINUTE_IN_MILLIS; 1897 mQcConstants.WINDOW_SIZE_RARE_MS = 60 * MINUTE_IN_MILLIS; 1898 mQcConstants.MAX_EXECUTION_TIME_MS = 3 * HOUR_IN_MILLIS; 1899 mQcConstants.MAX_JOB_COUNT_ACTIVE = 5000; 1900 mQcConstants.MAX_JOB_COUNT_WORKING = 4000; 1901 mQcConstants.MAX_JOB_COUNT_FREQUENT = 3000; 1902 mQcConstants.MAX_JOB_COUNT_RARE = 2000; 1903 mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * MINUTE_IN_MILLIS; 1904 mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 500; 1905 mQcConstants.MAX_SESSION_COUNT_ACTIVE = 500; 1906 mQcConstants.MAX_SESSION_COUNT_WORKING = 400; 1907 mQcConstants.MAX_SESSION_COUNT_FREQUENT = 300; 1908 mQcConstants.MAX_SESSION_COUNT_RARE = 200; 1909 mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 50; 1910 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 10 * SECOND_IN_MILLIS; 1911 1912 mQcConstants.updateConstants(); 1913 1914 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); 1915 assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); 1916 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); 1917 assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); 1918 assertEquals(45 * MINUTE_IN_MILLIS, 1919 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); 1920 assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); 1921 assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); 1922 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); 1923 assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow()); 1924 assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); 1925 assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); 1926 assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); 1927 assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); 1928 assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow()); 1929 assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]); 1930 assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]); 1931 assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]); 1932 assertEquals(200, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]); 1933 assertEquals(10 * SECOND_IN_MILLIS, 1934 mQuotaController.getTimingSessionCoalescingDurationMs()); 1935 } 1936 1937 @Test testConstantsUpdating_InvalidValues()1938 public void testConstantsUpdating_InvalidValues() { 1939 // Test negatives/too low. 1940 mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = -MINUTE_IN_MILLIS; 1941 mQcConstants.IN_QUOTA_BUFFER_MS = -MINUTE_IN_MILLIS; 1942 mQcConstants.WINDOW_SIZE_ACTIVE_MS = -MINUTE_IN_MILLIS; 1943 mQcConstants.WINDOW_SIZE_WORKING_MS = -MINUTE_IN_MILLIS; 1944 mQcConstants.WINDOW_SIZE_FREQUENT_MS = -MINUTE_IN_MILLIS; 1945 mQcConstants.WINDOW_SIZE_RARE_MS = -MINUTE_IN_MILLIS; 1946 mQcConstants.MAX_EXECUTION_TIME_MS = -MINUTE_IN_MILLIS; 1947 mQcConstants.MAX_JOB_COUNT_ACTIVE = -1; 1948 mQcConstants.MAX_JOB_COUNT_WORKING = 1; 1949 mQcConstants.MAX_JOB_COUNT_FREQUENT = 1; 1950 mQcConstants.MAX_JOB_COUNT_RARE = 1; 1951 mQcConstants.RATE_LIMITING_WINDOW_MS = 15 * SECOND_IN_MILLIS; 1952 mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = 0; 1953 mQcConstants.MAX_SESSION_COUNT_ACTIVE = -1; 1954 mQcConstants.MAX_SESSION_COUNT_WORKING = 0; 1955 mQcConstants.MAX_SESSION_COUNT_FREQUENT = -3; 1956 mQcConstants.MAX_SESSION_COUNT_RARE = 0; 1957 mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = 0; 1958 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = -1; 1959 1960 mQcConstants.updateConstants(); 1961 1962 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); 1963 assertEquals(0, mQuotaController.getInQuotaBufferMs()); 1964 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); 1965 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); 1966 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); 1967 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); 1968 assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); 1969 assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); 1970 assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow()); 1971 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); 1972 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); 1973 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); 1974 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); 1975 assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow()); 1976 assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]); 1977 assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]); 1978 assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]); 1979 assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]); 1980 assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs()); 1981 1982 // Invalid configurations. 1983 // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD 1984 mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 2 * MINUTE_IN_MILLIS; 1985 mQcConstants.IN_QUOTA_BUFFER_MS = 5 * MINUTE_IN_MILLIS; 1986 1987 mQcConstants.updateConstants(); 1988 1989 assertTrue(mQuotaController.getInQuotaBufferMs() 1990 <= mQuotaController.getAllowedTimePerPeriodMs()); 1991 1992 // Test larger than a day. Controller should cap at one day. 1993 mQcConstants.ALLOWED_TIME_PER_PERIOD_MS = 25 * HOUR_IN_MILLIS; 1994 mQcConstants.IN_QUOTA_BUFFER_MS = 25 * HOUR_IN_MILLIS; 1995 mQcConstants.WINDOW_SIZE_ACTIVE_MS = 25 * HOUR_IN_MILLIS; 1996 mQcConstants.WINDOW_SIZE_WORKING_MS = 25 * HOUR_IN_MILLIS; 1997 mQcConstants.WINDOW_SIZE_FREQUENT_MS = 25 * HOUR_IN_MILLIS; 1998 mQcConstants.WINDOW_SIZE_RARE_MS = 25 * HOUR_IN_MILLIS; 1999 mQcConstants.MAX_EXECUTION_TIME_MS = 25 * HOUR_IN_MILLIS; 2000 mQcConstants.RATE_LIMITING_WINDOW_MS = 25 * HOUR_IN_MILLIS; 2001 mQcConstants.TIMING_SESSION_COALESCING_DURATION_MS = 25 * HOUR_IN_MILLIS; 2002 2003 mQcConstants.updateConstants(); 2004 2005 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()); 2006 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); 2007 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); 2008 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); 2009 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); 2010 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); 2011 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); 2012 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); 2013 assertEquals(15 * MINUTE_IN_MILLIS, 2014 mQuotaController.getTimingSessionCoalescingDurationMs()); 2015 } 2016 2017 /** Tests that TimingSessions aren't saved when the device is charging. */ 2018 @Test testTimerTracking_Charging()2019 public void testTimerTracking_Charging() { 2020 setCharging(); 2021 2022 JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1); 2023 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2024 2025 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2026 2027 mQuotaController.prepareForExecutionLocked(jobStatus); 2028 advanceElapsedClock(5 * SECOND_IN_MILLIS); 2029 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); 2030 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2031 } 2032 2033 /** Tests that TimingSessions are saved properly when the device is discharging. */ 2034 @Test testTimerTracking_Discharging()2035 public void testTimerTracking_Discharging() { 2036 setDischarging(); 2037 setProcessState(ActivityManager.PROCESS_STATE_BACKUP); 2038 2039 JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1); 2040 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2041 2042 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2043 2044 List<TimingSession> expected = new ArrayList<>(); 2045 2046 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2047 mQuotaController.prepareForExecutionLocked(jobStatus); 2048 advanceElapsedClock(5 * SECOND_IN_MILLIS); 2049 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); 2050 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); 2051 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2052 2053 // Test overlapping jobs. 2054 JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2); 2055 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 2056 2057 JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3); 2058 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); 2059 2060 advanceElapsedClock(SECOND_IN_MILLIS); 2061 2062 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2063 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2064 mQuotaController.prepareForExecutionLocked(jobStatus); 2065 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2066 mQuotaController.prepareForExecutionLocked(jobStatus2); 2067 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2068 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); 2069 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2070 mQuotaController.prepareForExecutionLocked(jobStatus3); 2071 advanceElapsedClock(20 * SECOND_IN_MILLIS); 2072 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); 2073 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2074 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); 2075 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); 2076 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2077 } 2078 2079 /** 2080 * Tests that TimingSessions are saved properly when the device alternates between 2081 * charging and discharging. 2082 */ 2083 @Test testTimerTracking_ChargingAndDischarging()2084 public void testTimerTracking_ChargingAndDischarging() { 2085 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 2086 2087 JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1); 2088 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2089 JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2); 2090 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 2091 JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3); 2092 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); 2093 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2094 List<TimingSession> expected = new ArrayList<>(); 2095 2096 // A job starting while charging. Only the portion that runs during the discharging period 2097 // should be counted. 2098 setCharging(); 2099 2100 mQuotaController.prepareForExecutionLocked(jobStatus); 2101 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2102 setDischarging(); 2103 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2104 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2105 mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true); 2106 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 2107 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2108 2109 advanceElapsedClock(SECOND_IN_MILLIS); 2110 2111 // One job starts while discharging, spans a charging session, and ends after the charging 2112 // session. Only the portions during the discharging periods should be counted. This should 2113 // result in two TimingSessions. A second job starts while discharging and ends within the 2114 // charging session. Only the portion during the first discharging portion should be 2115 // counted. A third job starts and ends within the charging session. The third job 2116 // shouldn't be included in either job count. 2117 setDischarging(); 2118 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2119 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2120 mQuotaController.prepareForExecutionLocked(jobStatus); 2121 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2122 mQuotaController.prepareForExecutionLocked(jobStatus2); 2123 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2124 setCharging(); 2125 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); 2126 mQuotaController.prepareForExecutionLocked(jobStatus3); 2127 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2128 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); 2129 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2130 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); 2131 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2132 setDischarging(); 2133 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2134 advanceElapsedClock(20 * SECOND_IN_MILLIS); 2135 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); 2136 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); 2137 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2138 2139 // A job starting while discharging and ending while charging. Only the portion that runs 2140 // during the discharging period should be counted. 2141 setDischarging(); 2142 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2143 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 2144 mQuotaController.prepareForExecutionLocked(jobStatus2); 2145 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2146 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 2147 setCharging(); 2148 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2149 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); 2150 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2151 } 2152 2153 /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */ 2154 @Test testTimerTracking_AllBackground()2155 public void testTimerTracking_AllBackground() { 2156 setDischarging(); 2157 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 2158 2159 JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1); 2160 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2161 2162 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2163 2164 List<TimingSession> expected = new ArrayList<>(); 2165 2166 // Test single job. 2167 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2168 mQuotaController.prepareForExecutionLocked(jobStatus); 2169 advanceElapsedClock(5 * SECOND_IN_MILLIS); 2170 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); 2171 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); 2172 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2173 2174 // Test overlapping jobs. 2175 JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2); 2176 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 2177 2178 JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3); 2179 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); 2180 2181 advanceElapsedClock(SECOND_IN_MILLIS); 2182 2183 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2184 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2185 mQuotaController.prepareForExecutionLocked(jobStatus); 2186 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2187 mQuotaController.prepareForExecutionLocked(jobStatus2); 2188 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2189 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); 2190 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2191 mQuotaController.prepareForExecutionLocked(jobStatus3); 2192 advanceElapsedClock(20 * SECOND_IN_MILLIS); 2193 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false); 2194 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2195 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false); 2196 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); 2197 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2198 } 2199 2200 /** Tests that Timers don't count foreground jobs. */ 2201 @Test testTimerTracking_AllForeground()2202 public void testTimerTracking_AllForeground() { 2203 setDischarging(); 2204 2205 JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1); 2206 setProcessState(ActivityManager.PROCESS_STATE_TOP); 2207 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2208 2209 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2210 2211 mQuotaController.prepareForExecutionLocked(jobStatus); 2212 advanceElapsedClock(5 * SECOND_IN_MILLIS); 2213 // Change to a state that should still be considered foreground. 2214 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 2215 advanceElapsedClock(5 * SECOND_IN_MILLIS); 2216 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false); 2217 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2218 } 2219 2220 /** 2221 * Tests that Timers properly track sessions when switching between foreground and background 2222 * states. 2223 */ 2224 @Test testTimerTracking_ForegroundAndBackground()2225 public void testTimerTracking_ForegroundAndBackground() { 2226 setDischarging(); 2227 2228 JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1); 2229 JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2); 2230 JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3); 2231 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 2232 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 2233 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); 2234 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2235 List<TimingSession> expected = new ArrayList<>(); 2236 2237 // UID starts out inactive. 2238 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 2239 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2240 mQuotaController.prepareForExecutionLocked(jobBg1); 2241 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2242 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); 2243 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 2244 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2245 2246 advanceElapsedClock(SECOND_IN_MILLIS); 2247 2248 // Bg job starts while inactive, spans an entire active session, and ends after the 2249 // active session. 2250 // App switching to foreground state then fg job starts. 2251 // App remains in foreground state after coming to foreground, so there should only be one 2252 // session. 2253 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2254 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 2255 mQuotaController.prepareForExecutionLocked(jobBg2); 2256 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2257 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 2258 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 2259 mQuotaController.prepareForExecutionLocked(jobFg3); 2260 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2261 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); 2262 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2263 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); 2264 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2265 2266 advanceElapsedClock(SECOND_IN_MILLIS); 2267 2268 // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes 2269 // "inactive" and then bg job 2 starts. Then fg job ends. 2270 // This should result in two TimingSessions: 2271 // * The first should have a count of 1 2272 // * The second should have a count of 2 since it will include both jobs 2273 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2274 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 2275 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 2276 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); 2277 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY); 2278 mQuotaController.prepareForExecutionLocked(jobBg1); 2279 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2280 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 2281 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 2282 mQuotaController.prepareForExecutionLocked(jobFg3); 2283 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2284 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); 2285 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now 2286 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2287 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 2288 mQuotaController.prepareForExecutionLocked(jobBg2); 2289 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2290 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false); 2291 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2292 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); 2293 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); 2294 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2295 } 2296 2297 /** 2298 * Tests that Timers don't track job counts while in the foreground. 2299 */ 2300 @Test testTimerTracking_JobCount_Foreground()2301 public void testTimerTracking_JobCount_Foreground() { 2302 setDischarging(); 2303 2304 final int standbyBucket = ACTIVE_INDEX; 2305 JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1); 2306 JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2); 2307 2308 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null); 2309 mQuotaController.maybeStartTrackingJobLocked(jobFg2, null); 2310 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2311 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, 2312 SOURCE_PACKAGE, standbyBucket); 2313 assertEquals(0, stats.jobCountInRateLimitingWindow); 2314 2315 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 2316 mQuotaController.prepareForExecutionLocked(jobFg1); 2317 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2318 mQuotaController.prepareForExecutionLocked(jobFg2); 2319 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2320 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false); 2321 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2322 mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false); 2323 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2324 2325 assertEquals(0, stats.jobCountInRateLimitingWindow); 2326 } 2327 2328 /** 2329 * Tests that Timers properly track job counts while in the background. 2330 */ 2331 @Test testTimerTracking_JobCount_Background()2332 public void testTimerTracking_JobCount_Background() { 2333 final int standbyBucket = WORKING_INDEX; 2334 JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1); 2335 JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2); 2336 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 2337 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 2338 2339 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, 2340 SOURCE_PACKAGE, standbyBucket); 2341 assertEquals(0, stats.jobCountInRateLimitingWindow); 2342 2343 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 2344 mQuotaController.prepareForExecutionLocked(jobBg1); 2345 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2346 mQuotaController.prepareForExecutionLocked(jobBg2); 2347 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2348 mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false); 2349 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2350 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); 2351 2352 assertEquals(2, stats.jobCountInRateLimitingWindow); 2353 } 2354 2355 /** 2356 * Tests that Timers properly track overlapping top and background jobs. 2357 */ 2358 @Test testTimerTracking_TopAndNonTop()2359 public void testTimerTracking_TopAndNonTop() { 2360 setDischarging(); 2361 2362 JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1); 2363 JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2); 2364 JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3); 2365 JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4); 2366 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 2367 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 2368 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null); 2369 mQuotaController.maybeStartTrackingJobLocked(jobTop, null); 2370 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2371 List<TimingSession> expected = new ArrayList<>(); 2372 2373 // UID starts out inactive. 2374 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 2375 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2376 mQuotaController.prepareForExecutionLocked(jobBg1); 2377 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2378 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); 2379 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 2380 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2381 2382 advanceElapsedClock(SECOND_IN_MILLIS); 2383 2384 // Bg job starts while inactive, spans an entire active session, and ends after the 2385 // active session. 2386 // App switching to top state then fg job starts. 2387 // App remains in top state after coming to top, so there should only be one 2388 // session. 2389 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2390 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 2391 mQuotaController.prepareForExecutionLocked(jobBg2); 2392 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2393 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 2394 setProcessState(ActivityManager.PROCESS_STATE_TOP); 2395 mQuotaController.prepareForExecutionLocked(jobTop); 2396 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2397 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false); 2398 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2399 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); 2400 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2401 2402 advanceElapsedClock(SECOND_IN_MILLIS); 2403 2404 // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to 2405 // foreground_service and a new job starts. Shortly after, uid goes 2406 // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs. 2407 // This should result in two TimingSessions: 2408 // * The first should have a count of 1 2409 // * The second should have a count of 2, which accounts for the bg2 and fg, but not top 2410 // jobs. 2411 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2412 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 2413 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 2414 mQuotaController.maybeStartTrackingJobLocked(jobTop, null); 2415 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY); 2416 mQuotaController.prepareForExecutionLocked(jobBg1); 2417 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2418 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 2419 setProcessState(ActivityManager.PROCESS_STATE_TOP); 2420 mQuotaController.prepareForExecutionLocked(jobTop); 2421 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2422 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true); 2423 advanceElapsedClock(5 * SECOND_IN_MILLIS); 2424 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 2425 mQuotaController.prepareForExecutionLocked(jobFg1); 2426 advanceElapsedClock(5 * SECOND_IN_MILLIS); 2427 setProcessState(ActivityManager.PROCESS_STATE_TOP); 2428 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now 2429 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 2430 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 2431 mQuotaController.prepareForExecutionLocked(jobBg2); 2432 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2433 mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false); 2434 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2435 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false); 2436 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false); 2437 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); 2438 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 2439 } 2440 2441 /** 2442 * Tests that TOP jobs aren't stopped when an app runs out of quota. 2443 */ 2444 @Test testTracking_OutOfQuota_ForegroundAndBackground()2445 public void testTracking_OutOfQuota_ForegroundAndBackground() { 2446 setDischarging(); 2447 2448 JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1); 2449 JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2); 2450 trackJobs(jobBg, jobTop); 2451 setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window 2452 // Now the package only has 20 seconds to run. 2453 final long remainingTimeMs = 20 * SECOND_IN_MILLIS; 2454 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2455 createTimingSession( 2456 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS, 2457 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1)); 2458 2459 InOrder inOrder = inOrder(mJobSchedulerService); 2460 2461 // UID starts out inactive. 2462 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 2463 // Start the job. 2464 mQuotaController.prepareForExecutionLocked(jobBg); 2465 advanceElapsedClock(remainingTimeMs / 2); 2466 // New job starts after UID is in the foreground. Since the app is now in the foreground, it 2467 // should continue to have remainingTimeMs / 2 time remaining. 2468 setProcessState(ActivityManager.PROCESS_STATE_TOP); 2469 mQuotaController.prepareForExecutionLocked(jobTop); 2470 advanceElapsedClock(remainingTimeMs); 2471 2472 // Wait for some extra time to allow for job processing. 2473 inOrder.verify(mJobSchedulerService, 2474 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) 2475 .onControllerStateChanged(); 2476 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobBg)); 2477 assertEquals(remainingTimeMs / 2, mQuotaController.getRemainingExecutionTimeLocked(jobTop)); 2478 // Go to a background state. 2479 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 2480 advanceElapsedClock(remainingTimeMs / 2 + 1); 2481 inOrder.verify(mJobSchedulerService, 2482 timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) 2483 .onControllerStateChanged(); 2484 // Top job should still be allowed to run. 2485 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2486 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2487 2488 // New jobs to run. 2489 JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3); 2490 JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4); 2491 JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5); 2492 setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg); 2493 2494 advanceElapsedClock(20 * SECOND_IN_MILLIS); 2495 setProcessState(ActivityManager.PROCESS_STATE_TOP); 2496 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) 2497 .onControllerStateChanged(); 2498 trackJobs(jobFg, jobTop); 2499 mQuotaController.prepareForExecutionLocked(jobTop); 2500 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2501 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2502 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2503 2504 // App still in foreground so everything should be in quota. 2505 advanceElapsedClock(20 * SECOND_IN_MILLIS); 2506 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 2507 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2508 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2509 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2510 2511 advanceElapsedClock(20 * SECOND_IN_MILLIS); 2512 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 2513 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) 2514 .onControllerStateChanged(); 2515 // App is now in background and out of quota. Fg should now change to out of quota since it 2516 // wasn't started. Top should remain in quota since it started when the app was in TOP. 2517 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2518 assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2519 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2520 trackJobs(jobBg2); 2521 assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2522 } 2523 2524 /** 2525 * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches 2526 * its quota. 2527 */ 2528 @Test testTracking_OutOfQuota()2529 public void testTracking_OutOfQuota() { 2530 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); 2531 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2532 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window 2533 setProcessState(ActivityManager.PROCESS_STATE_HOME); 2534 // Now the package only has two seconds to run. 2535 final long remainingTimeMs = 2 * SECOND_IN_MILLIS; 2536 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2537 createTimingSession( 2538 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS, 2539 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1)); 2540 2541 // Start the job. 2542 mQuotaController.prepareForExecutionLocked(jobStatus); 2543 advanceElapsedClock(remainingTimeMs); 2544 2545 // Wait for some extra time to allow for job processing. 2546 verify(mJobSchedulerService, 2547 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1)) 2548 .onControllerStateChanged(); 2549 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2550 assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(), 2551 jobStatus.getWhenStandbyDeferred()); 2552 } 2553 2554 /** 2555 * Tests that a job is properly handled when it's at the edge of its quota and the old quota is 2556 * being phased out. 2557 */ 2558 @Test testTracking_RollingQuota()2559 public void testTracking_RollingQuota() { 2560 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); 2561 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2562 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window 2563 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 2564 Handler handler = mQuotaController.getHandler(); 2565 spyOn(handler); 2566 2567 long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2568 final long remainingTimeMs = SECOND_IN_MILLIS; 2569 // The package only has one second to run, but this session is at the edge of the rolling 2570 // window, so as the package "reaches its quota" it will have more to keep running. 2571 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2572 createTimingSession(now - 2 * HOUR_IN_MILLIS, 2573 10 * SECOND_IN_MILLIS - remainingTimeMs, 1)); 2574 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2575 createTimingSession(now - HOUR_IN_MILLIS, 2576 9 * MINUTE_IN_MILLIS + 50 * SECOND_IN_MILLIS, 1)); 2577 2578 assertEquals(remainingTimeMs, mQuotaController.getRemainingExecutionTimeLocked(jobStatus)); 2579 // Start the job. 2580 mQuotaController.prepareForExecutionLocked(jobStatus); 2581 advanceElapsedClock(remainingTimeMs); 2582 2583 // Wait for some extra time to allow for job processing. 2584 verify(mJobSchedulerService, 2585 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) 2586 .onControllerStateChanged(); 2587 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2588 // The job used up the remaining quota, but in that time, the same amount of time in the 2589 // old TimingSession also fell out of the quota window, so it should still have the same 2590 // amount of remaining time left its quota. 2591 assertEquals(remainingTimeMs, 2592 mQuotaController.getRemainingExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 2593 // Handler is told to check when the quota will be consumed, not when the initial 2594 // remaining time is over. 2595 verify(handler, atLeast(1)).sendMessageDelayed(any(), eq(10 * SECOND_IN_MILLIS)); 2596 verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); 2597 2598 // After 10 seconds, the job should finally be out of quota. 2599 advanceElapsedClock(10 * SECOND_IN_MILLIS - remainingTimeMs); 2600 // Wait for some extra time to allow for job processing. 2601 verify(mJobSchedulerService, 2602 timeout(12 * SECOND_IN_MILLIS).times(1)) 2603 .onControllerStateChanged(); 2604 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2605 verify(handler, never()).sendMessageDelayed(any(), anyInt()); 2606 } 2607 2608 /** 2609 * Tests that the start alarm is properly scheduled when a job has been throttled due to the job 2610 * count rate limiting. 2611 */ 2612 @Test testStartAlarmScheduled_JobCount_RateLimitingWindow()2613 public void testStartAlarmScheduled_JobCount_RateLimitingWindow() { 2614 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 2615 // because it schedules an alarm too. Prevent it from doing so. 2616 spyOn(mQuotaController); 2617 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 2618 2619 // Essentially disable session throttling. 2620 mQcConstants.MAX_SESSION_COUNT_WORKING = 2621 mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE; 2622 mQcConstants.updateConstants(); 2623 2624 final int standbyBucket = WORKING_INDEX; 2625 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 2626 2627 // No sessions saved yet. 2628 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 2629 standbyBucket); 2630 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 2631 2632 // Ran jobs up to the job limit. All of them should be allowed to run. 2633 for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) { 2634 JobStatus job = createJobStatus("testStartAlarmScheduled_JobCount_AllowedTime", i); 2635 setStandbyBucket(WORKING_INDEX, job); 2636 mQuotaController.maybeStartTrackingJobLocked(job, null); 2637 assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2638 mQuotaController.prepareForExecutionLocked(job); 2639 advanceElapsedClock(SECOND_IN_MILLIS); 2640 mQuotaController.maybeStopTrackingJobLocked(job, null, false); 2641 advanceElapsedClock(SECOND_IN_MILLIS); 2642 } 2643 // Start alarm shouldn't have been scheduled since the app was in quota up until this point. 2644 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 2645 2646 // The app is now out of job count quota 2647 JobStatus throttledJob = createJobStatus( 2648 "testStartAlarmScheduled_JobCount_AllowedTime", 42); 2649 setStandbyBucket(WORKING_INDEX, throttledJob); 2650 mQuotaController.maybeStartTrackingJobLocked(throttledJob, null); 2651 assertFalse(throttledJob.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2652 2653 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, 2654 SOURCE_PACKAGE, standbyBucket); 2655 final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed; 2656 verify(mAlarmManager, times(1)) 2657 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 2658 } 2659 2660 /** 2661 * Tests that the start alarm is properly scheduled when a job has been throttled due to the 2662 * session count rate limiting. 2663 */ 2664 @Test testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow()2665 public void testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow() { 2666 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 2667 // because it schedules an alarm too. Prevent it from doing so. 2668 spyOn(mQuotaController); 2669 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 2670 2671 // Essentially disable job count throttling. 2672 mQcConstants.MAX_JOB_COUNT_FREQUENT = 2673 mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW = Integer.MAX_VALUE; 2674 // Make sure throttling is because of COUNT_PER_RATE_LIMITING_WINDOW. 2675 mQcConstants.MAX_SESSION_COUNT_FREQUENT = 2676 mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW + 1; 2677 mQcConstants.updateConstants(); 2678 2679 final int standbyBucket = FREQUENT_INDEX; 2680 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 2681 2682 // No sessions saved yet. 2683 mQuotaController.maybeScheduleStartAlarmLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 2684 standbyBucket); 2685 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 2686 2687 // Ran jobs up to the job limit. All of them should be allowed to run. 2688 for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) { 2689 JobStatus job = createJobStatus( 2690 "testStartAlarmScheduled_TimingSessionCount_AllowedTime", i); 2691 setStandbyBucket(FREQUENT_INDEX, job); 2692 mQuotaController.maybeStartTrackingJobLocked(job, null); 2693 assertTrue("Constraint not satisfied for job #" + (i + 1), 2694 job.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2695 mQuotaController.prepareForExecutionLocked(job); 2696 advanceElapsedClock(SECOND_IN_MILLIS); 2697 mQuotaController.maybeStopTrackingJobLocked(job, null, false); 2698 advanceElapsedClock(SECOND_IN_MILLIS); 2699 } 2700 // Start alarm shouldn't have been scheduled since the app was in quota up until this point. 2701 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any()); 2702 2703 // The app is now out of session count quota 2704 JobStatus throttledJob = createJobStatus( 2705 "testStartAlarmScheduled_TimingSessionCount_AllowedTime", 42); 2706 mQuotaController.maybeStartTrackingJobLocked(throttledJob, null); 2707 assertFalse(throttledJob.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 2708 assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(), 2709 throttledJob.getWhenStandbyDeferred()); 2710 2711 ExecutionStats stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, 2712 SOURCE_PACKAGE, standbyBucket); 2713 final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed; 2714 verify(mAlarmManager, times(1)) 2715 .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any()); 2716 } 2717 } 2718