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.doAnswer; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; 21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; 23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; 24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; 27 import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; 28 import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; 29 import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; 30 import static com.android.server.job.JobSchedulerService.NEVER_INDEX; 31 import static com.android.server.job.JobSchedulerService.RARE_INDEX; 32 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; 33 import static com.android.server.job.JobSchedulerService.WORKING_INDEX; 34 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; 35 import static com.android.server.job.JobSchedulerService.sSystemClock; 36 37 import static org.junit.Assert.assertEquals; 38 import static org.junit.Assert.assertFalse; 39 import static org.junit.Assert.assertNotEquals; 40 import static org.junit.Assert.assertNotNull; 41 import static org.junit.Assert.assertNull; 42 import static org.junit.Assert.assertTrue; 43 import static org.junit.Assert.fail; 44 import static org.mockito.ArgumentMatchers.any; 45 import static org.mockito.ArgumentMatchers.anyInt; 46 import static org.mockito.ArgumentMatchers.anyLong; 47 import static org.mockito.ArgumentMatchers.anyString; 48 import static org.mockito.ArgumentMatchers.argThat; 49 import static org.mockito.Mockito.atLeast; 50 import static org.mockito.Mockito.eq; 51 import static org.mockito.Mockito.never; 52 import static org.mockito.Mockito.timeout; 53 import static org.mockito.Mockito.times; 54 import static org.mockito.Mockito.verify; 55 56 import android.Manifest; 57 import android.app.ActivityManager; 58 import android.app.ActivityManagerInternal; 59 import android.app.AlarmManager; 60 import android.app.AppGlobals; 61 import android.app.IActivityManager; 62 import android.app.IUidObserver; 63 import android.app.job.JobInfo; 64 import android.app.usage.UsageEvents; 65 import android.app.usage.UsageStatsManager; 66 import android.app.usage.UsageStatsManagerInternal; 67 import android.content.ComponentName; 68 import android.content.Context; 69 import android.content.pm.ApplicationInfo; 70 import android.content.pm.PackageInfo; 71 import android.content.pm.PackageManager; 72 import android.content.pm.PackageManagerInternal; 73 import android.os.BatteryManagerInternal; 74 import android.os.Handler; 75 import android.os.Looper; 76 import android.os.RemoteException; 77 import android.os.SystemClock; 78 import android.platform.test.annotations.LargeTest; 79 import android.provider.DeviceConfig; 80 import android.util.ArraySet; 81 import android.util.SparseBooleanArray; 82 83 import androidx.test.runner.AndroidJUnit4; 84 85 import com.android.internal.util.ArrayUtils; 86 import com.android.server.LocalServices; 87 import com.android.server.PowerAllowlistInternal; 88 import com.android.server.job.JobSchedulerInternal; 89 import com.android.server.job.JobSchedulerService; 90 import com.android.server.job.JobStore; 91 import com.android.server.job.controllers.QuotaController.ExecutionStats; 92 import com.android.server.job.controllers.QuotaController.QcConstants; 93 import com.android.server.job.controllers.QuotaController.QuotaBump; 94 import com.android.server.job.controllers.QuotaController.ShrinkableDebits; 95 import com.android.server.job.controllers.QuotaController.TimedEvent; 96 import com.android.server.job.controllers.QuotaController.TimingSession; 97 import com.android.server.usage.AppStandbyInternal; 98 99 import org.junit.After; 100 import org.junit.Before; 101 import org.junit.Test; 102 import org.junit.runner.RunWith; 103 import org.mockito.ArgumentCaptor; 104 import org.mockito.ArgumentMatchers; 105 import org.mockito.InOrder; 106 import org.mockito.Mock; 107 import org.mockito.MockitoSession; 108 import org.mockito.quality.Strictness; 109 import org.mockito.stubbing.Answer; 110 111 import java.time.Clock; 112 import java.time.Duration; 113 import java.time.ZoneOffset; 114 import java.util.ArrayList; 115 import java.util.List; 116 import java.util.concurrent.Executor; 117 118 @RunWith(AndroidJUnit4.class) 119 public class QuotaControllerTest { 120 private static final long SECOND_IN_MILLIS = 1000L; 121 private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; 122 private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; 123 private static final String TAG_CLEANUP = "*job.cleanup*"; 124 private static final String TAG_QUOTA_CHECK = "*job.quota_check*"; 125 private static final int CALLING_UID = 1000; 126 private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; 127 private static final int SOURCE_USER_ID = 0; 128 129 private QuotaController mQuotaController; 130 private QuotaController.QcConstants mQcConstants; 131 private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); 132 private int mSourceUid; 133 private AppStandbyInternal.AppIdleStateChangeListener mAppIdleStateChangeListener; 134 private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener; 135 private IUidObserver mUidObserver; 136 private UsageStatsManagerInternal.UsageEventListener mUsageEventListener; 137 DeviceConfig.Properties.Builder mDeviceConfigPropertiesBuilder; 138 139 private MockitoSession mMockingSession; 140 @Mock 141 private ActivityManagerInternal mActivityMangerInternal; 142 @Mock 143 private AlarmManager mAlarmManager; 144 @Mock 145 private Context mContext; 146 @Mock 147 private JobSchedulerService mJobSchedulerService; 148 @Mock 149 private PackageManager mPackageManager; 150 @Mock 151 private PackageManagerInternal mPackageManagerInternal; 152 @Mock 153 private PowerAllowlistInternal mPowerAllowlistInternal; 154 @Mock 155 private UsageStatsManagerInternal mUsageStatsManager; 156 157 private JobStore mJobStore; 158 159 @Before setUp()160 public void setUp() { 161 mMockingSession = mockitoSession() 162 .initMocks(this) 163 .strictness(Strictness.LENIENT) 164 .spyStatic(DeviceConfig.class) 165 .mockStatic(LocalServices.class) 166 .startMocking(); 167 168 // Called in StateController constructor. 169 when(mJobSchedulerService.getTestableContext()).thenReturn(mContext); 170 when(mJobSchedulerService.getLock()).thenReturn(mJobSchedulerService); 171 when(mJobSchedulerService.getConstants()).thenReturn(mConstants); 172 // Called in QuotaController constructor. 173 IActivityManager activityManager = ActivityManager.getService(); 174 spyOn(activityManager); 175 try { 176 doNothing().when(activityManager).registerUidObserver(any(), anyInt(), anyInt(), any()); 177 } catch (RemoteException e) { 178 fail("registerUidObserver threw exception: " + e.getMessage()); 179 } 180 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); 181 when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager); 182 doReturn(mActivityMangerInternal) 183 .when(() -> LocalServices.getService(ActivityManagerInternal.class)); 184 final AppStandbyInternal appStandbyInternal = mock(AppStandbyInternal.class); 185 doReturn(appStandbyInternal) 186 .when(() -> LocalServices.getService(AppStandbyInternal.class)); 187 doReturn(mock(BatteryManagerInternal.class)) 188 .when(() -> LocalServices.getService(BatteryManagerInternal.class)); 189 doReturn(mUsageStatsManager) 190 .when(() -> LocalServices.getService(UsageStatsManagerInternal.class)); 191 doReturn(mPowerAllowlistInternal) 192 .when(() -> LocalServices.getService(PowerAllowlistInternal.class)); 193 // Used in JobStatus. 194 doReturn(mock(JobSchedulerInternal.class)) 195 .when(() -> LocalServices.getService(JobSchedulerInternal.class)); 196 doReturn(mPackageManagerInternal) 197 .when(() -> LocalServices.getService(PackageManagerInternal.class)); 198 // Used in QuotaController.Handler. 199 mJobStore = JobStore.initAndGetForTesting(mContext, mContext.getFilesDir()); 200 when(mJobSchedulerService.getJobStore()).thenReturn(mJobStore); 201 // Used in QuotaController.QcConstants 202 doAnswer((Answer<Void>) invocationOnMock -> null) 203 .when(() -> DeviceConfig.addOnPropertiesChangedListener( 204 anyString(), any(Executor.class), 205 any(DeviceConfig.OnPropertiesChangedListener.class))); 206 mDeviceConfigPropertiesBuilder = 207 new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER); 208 doAnswer( 209 (Answer<DeviceConfig.Properties>) invocationOnMock 210 -> mDeviceConfigPropertiesBuilder.build()) 211 .when(() -> DeviceConfig.getProperties( 212 eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER), ArgumentMatchers.<String>any())); 213 // Used in QuotaController.onSystemServicesReady 214 when(mContext.getPackageManager()).thenReturn(mPackageManager); 215 216 // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions 217 // in the past, and QuotaController sometimes floors values at 0, so if the test time 218 // causes sessions with negative timestamps, they will fail. 219 JobSchedulerService.sSystemClock = 220 getAdvancedClock(Clock.fixed(Clock.systemUTC().instant(), ZoneOffset.UTC), 221 24 * HOUR_IN_MILLIS); 222 JobSchedulerService.sUptimeMillisClock = getAdvancedClock( 223 Clock.fixed(SystemClock.uptimeClock().instant(), ZoneOffset.UTC), 224 24 * HOUR_IN_MILLIS); 225 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock( 226 Clock.fixed(SystemClock.elapsedRealtimeClock().instant(), ZoneOffset.UTC), 227 24 * HOUR_IN_MILLIS); 228 229 // Initialize real objects. 230 // Capture the listeners. 231 ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> aiscListenerCaptor = 232 ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class); 233 ArgumentCaptor<IUidObserver> uidObserverCaptor = 234 ArgumentCaptor.forClass(IUidObserver.class); 235 ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor = 236 ArgumentCaptor.forClass(PowerAllowlistInternal.TempAllowlistChangeListener.class); 237 ArgumentCaptor<UsageStatsManagerInternal.UsageEventListener> ueListenerCaptor = 238 ArgumentCaptor.forClass(UsageStatsManagerInternal.UsageEventListener.class); 239 mQuotaController = new QuotaController(mJobSchedulerService, 240 mock(BackgroundJobsController.class), mock(ConnectivityController.class)); 241 242 verify(appStandbyInternal).addListener(aiscListenerCaptor.capture()); 243 mAppIdleStateChangeListener = aiscListenerCaptor.getValue(); 244 verify(mPowerAllowlistInternal) 245 .registerTempAllowlistChangeListener(taChangeCaptor.capture()); 246 mTempAllowlistListener = taChangeCaptor.getValue(); 247 verify(mUsageStatsManager).registerListener(ueListenerCaptor.capture()); 248 mUsageEventListener = ueListenerCaptor.getValue(); 249 try { 250 verify(activityManager).registerUidObserver( 251 uidObserverCaptor.capture(), 252 eq(ActivityManager.UID_OBSERVER_PROCSTATE), 253 eq(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE), 254 any()); 255 mUidObserver = uidObserverCaptor.getValue(); 256 mSourceUid = AppGlobals.getPackageManager().getPackageUid(SOURCE_PACKAGE, 0, 0); 257 // Need to do this since we're using a mock JS and not a real object. 258 doReturn(new ArraySet<>(new String[]{SOURCE_PACKAGE})) 259 .when(mJobSchedulerService).getPackagesForUidLocked(mSourceUid); 260 } catch (RemoteException e) { 261 fail(e.getMessage()); 262 } 263 mQcConstants = mQuotaController.getQcConstants(); 264 } 265 266 @After tearDown()267 public void tearDown() { 268 if (mMockingSession != null) { 269 mMockingSession.finishMocking(); 270 } 271 } 272 getAdvancedClock(Clock clock, long incrementMs)273 private Clock getAdvancedClock(Clock clock, long incrementMs) { 274 return Clock.offset(clock, Duration.ofMillis(incrementMs)); 275 } 276 advanceElapsedClock(long incrementMs)277 private void advanceElapsedClock(long incrementMs) { 278 JobSchedulerService.sElapsedRealtimeClock = getAdvancedClock( 279 JobSchedulerService.sElapsedRealtimeClock, incrementMs); 280 } 281 setCharging()282 private void setCharging() { 283 doReturn(true).when(mJobSchedulerService).isBatteryCharging(); 284 synchronized (mQuotaController.mLock) { 285 mQuotaController.onBatteryStateChangedLocked(); 286 } 287 } 288 setDischarging()289 private void setDischarging() { 290 doReturn(false).when(mJobSchedulerService).isBatteryCharging(); 291 synchronized (mQuotaController.mLock) { 292 mQuotaController.onBatteryStateChangedLocked(); 293 } 294 } 295 setProcessState(int procState)296 private void setProcessState(int procState) { 297 setProcessState(procState, mSourceUid); 298 } 299 setProcessState(int procState, int uid)300 private void setProcessState(int procState, int uid) { 301 try { 302 doReturn(procState).when(mActivityMangerInternal).getUidProcessState(uid); 303 SparseBooleanArray foregroundUids = mQuotaController.getForegroundUids(); 304 spyOn(foregroundUids); 305 final boolean contained = foregroundUids.get(uid); 306 mUidObserver.onUidStateChanged(uid, procState, 0, 307 ActivityManager.PROCESS_CAPABILITY_NONE); 308 if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) { 309 if (!contained) { 310 verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)) 311 .put(eq(uid), eq(true)); 312 } 313 assertTrue(foregroundUids.get(uid)); 314 } else { 315 if (contained) { 316 verify(foregroundUids, timeout(2 * SECOND_IN_MILLIS).times(1)) 317 .delete(eq(uid)); 318 } 319 assertFalse(foregroundUids.get(uid)); 320 } 321 waitForNonDelayedMessagesProcessed(); 322 } catch (Exception e) { 323 fail("exception encountered: " + e.getMessage()); 324 } 325 } 326 bucketIndexToUsageStatsBucket(int bucketIndex)327 private int bucketIndexToUsageStatsBucket(int bucketIndex) { 328 switch (bucketIndex) { 329 case EXEMPTED_INDEX: 330 return UsageStatsManager.STANDBY_BUCKET_EXEMPTED; 331 case ACTIVE_INDEX: 332 return UsageStatsManager.STANDBY_BUCKET_ACTIVE; 333 case WORKING_INDEX: 334 return UsageStatsManager.STANDBY_BUCKET_WORKING_SET; 335 case FREQUENT_INDEX: 336 return UsageStatsManager.STANDBY_BUCKET_FREQUENT; 337 case RARE_INDEX: 338 return UsageStatsManager.STANDBY_BUCKET_RARE; 339 case RESTRICTED_INDEX: 340 return UsageStatsManager.STANDBY_BUCKET_RESTRICTED; 341 default: 342 return UsageStatsManager.STANDBY_BUCKET_NEVER; 343 } 344 } 345 setStandbyBucket(int bucketIndex)346 private void setStandbyBucket(int bucketIndex) { 347 when(mUsageStatsManager.getAppStandbyBucket(eq(SOURCE_PACKAGE), eq(SOURCE_USER_ID), 348 anyLong())).thenReturn(bucketIndexToUsageStatsBucket(bucketIndex)); 349 mQuotaController.updateStandbyBucket(SOURCE_USER_ID, SOURCE_PACKAGE, bucketIndex); 350 } 351 setStandbyBucket(int bucketIndex, JobStatus... jobs)352 private void setStandbyBucket(int bucketIndex, JobStatus... jobs) { 353 setStandbyBucket(bucketIndex); 354 for (JobStatus job : jobs) { 355 job.setStandbyBucket(bucketIndex); 356 when(mUsageStatsManager.getAppStandbyBucket( 357 eq(job.getSourcePackageName()), eq(job.getSourceUserId()), anyLong())) 358 .thenReturn(bucketIndexToUsageStatsBucket(bucketIndex)); 359 } 360 } 361 trackJobs(JobStatus... jobs)362 private void trackJobs(JobStatus... jobs) { 363 for (JobStatus job : jobs) { 364 mJobStore.add(job); 365 synchronized (mQuotaController.mLock) { 366 mQuotaController.maybeStartTrackingJobLocked(job, null); 367 } 368 } 369 } 370 createJobStatus(String testTag, int jobId)371 private JobStatus createJobStatus(String testTag, int jobId) { 372 JobInfo jobInfo = new JobInfo.Builder(jobId, 373 new ComponentName(mContext, "TestQuotaJobService")) 374 .build(); 375 return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo); 376 } 377 createExpeditedJobStatus(String testTag, int jobId)378 private JobStatus createExpeditedJobStatus(String testTag, int jobId) { 379 JobInfo jobInfo = new JobInfo.Builder(jobId, 380 new ComponentName(mContext, "TestQuotaExpeditedJobService")) 381 .setExpedited(true) 382 .build(); 383 return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo); 384 } 385 createJobStatus(String testTag, String packageName, int callingUid, JobInfo jobInfo)386 private JobStatus createJobStatus(String testTag, String packageName, int callingUid, 387 JobInfo jobInfo) { 388 JobStatus js = JobStatus.createFromJobInfo( 389 jobInfo, callingUid, packageName, SOURCE_USER_ID, "QCTest", testTag); 390 js.serviceProcessName = "testProcess"; 391 // Make sure tests aren't passing just because the default bucket is likely ACTIVE. 392 js.setStandbyBucket(FREQUENT_INDEX); 393 // Make sure Doze and background-not-restricted don't affect tests. 394 js.setDeviceNotDozingConstraintSatisfied(/* nowElapsed */ sElapsedRealtimeClock.millis(), 395 /* state */ true, /* allowlisted */false); 396 js.setBackgroundNotRestrictedConstraintSatisfied( 397 sElapsedRealtimeClock.millis(), true, false); 398 js.setTareWealthConstraintSatisfied(sElapsedRealtimeClock.millis(), true); 399 js.setExpeditedJobTareApproved(sElapsedRealtimeClock.millis(), true); 400 js.setFlexibilityConstraintSatisfied(sElapsedRealtimeClock.millis(), true); 401 return js; 402 } 403 createTimingSession(long start, long duration, int count)404 private TimingSession createTimingSession(long start, long duration, int count) { 405 return new TimingSession(start, start + duration, count); 406 } 407 setDeviceConfigLong(String key, long val)408 private void setDeviceConfigLong(String key, long val) { 409 mDeviceConfigPropertiesBuilder.setLong(key, val); 410 synchronized (mQuotaController.mLock) { 411 mQuotaController.prepareForUpdatedConstantsLocked(); 412 mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key); 413 } 414 } 415 setDeviceConfigInt(String key, int val)416 private void setDeviceConfigInt(String key, int val) { 417 mDeviceConfigPropertiesBuilder.setInt(key, val); 418 synchronized (mQuotaController.mLock) { 419 mQuotaController.prepareForUpdatedConstantsLocked(); 420 mQcConstants.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key); 421 } 422 } 423 waitForNonDelayedMessagesProcessed()424 private void waitForNonDelayedMessagesProcessed() { 425 mQuotaController.getHandler().runWithScissors(() -> {}, 15_000); 426 } 427 428 @Test testSaveTimingSession()429 public void testSaveTimingSession() { 430 assertNull(mQuotaController.getTimingSessions(0, "com.android.test")); 431 432 List<TimingSession> expectedRegular = new ArrayList<>(); 433 List<TimingSession> expectedEJ = new ArrayList<>(); 434 TimingSession one = new TimingSession(1, 10, 1); 435 TimingSession two = new TimingSession(11, 20, 2); 436 TimingSession thr = new TimingSession(21, 30, 3); 437 TimingSession fou = new TimingSession(31, 40, 4); 438 439 mQuotaController.saveTimingSession(0, "com.android.test", one, false); 440 expectedRegular.add(one); 441 assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test")); 442 assertTrue( 443 ArrayUtils.isEmpty(mQuotaController.getEJTimingSessions(0, "com.android.test"))); 444 445 mQuotaController.saveTimingSession(0, "com.android.test", two, false); 446 expectedRegular.add(two); 447 assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test")); 448 assertTrue( 449 ArrayUtils.isEmpty(mQuotaController.getEJTimingSessions(0, "com.android.test"))); 450 451 mQuotaController.saveTimingSession(0, "com.android.test", thr, true); 452 expectedEJ.add(thr); 453 assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test")); 454 assertEquals(expectedEJ, mQuotaController.getEJTimingSessions(0, "com.android.test")); 455 456 mQuotaController.saveTimingSession(0, "com.android.test", fou, false); 457 mQuotaController.saveTimingSession(0, "com.android.test", fou, true); 458 expectedRegular.add(fou); 459 expectedEJ.add(fou); 460 assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test")); 461 assertEquals(expectedEJ, mQuotaController.getEJTimingSessions(0, "com.android.test")); 462 } 463 464 @Test testDeleteObsoleteSessionsLocked()465 public void testDeleteObsoleteSessionsLocked() { 466 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 467 TimingSession one = createTimingSession( 468 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); 469 TimingSession two = createTimingSession( 470 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); 471 QuotaBump bump1 = new QuotaBump(now - 2 * HOUR_IN_MILLIS); 472 TimingSession thr = createTimingSession( 473 now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); 474 // Overlaps 24 hour boundary. 475 TimingSession fou = createTimingSession( 476 now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1); 477 // Way past the 24 hour boundary. 478 QuotaBump bump2 = new QuotaBump(now - 24 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); 479 TimingSession fiv = createTimingSession( 480 now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4); 481 List<TimedEvent> expectedRegular = new ArrayList<>(); 482 List<TimedEvent> expectedEJ = new ArrayList<>(); 483 // Added in correct (chronological) order. 484 expectedRegular.add(fou); 485 expectedRegular.add(thr); 486 expectedRegular.add(bump1); 487 expectedRegular.add(two); 488 expectedRegular.add(one); 489 expectedEJ.add(fou); 490 expectedEJ.add(one); 491 mQuotaController.saveTimingSession(0, "com.android.test", fiv, false); 492 mQuotaController.getTimingSessions(0, "com.android.test").add(bump2); 493 mQuotaController.saveTimingSession(0, "com.android.test", fou, false); 494 mQuotaController.saveTimingSession(0, "com.android.test", thr, false); 495 mQuotaController.getTimingSessions(0, "com.android.test").add(bump1); 496 mQuotaController.saveTimingSession(0, "com.android.test", two, false); 497 mQuotaController.saveTimingSession(0, "com.android.test", one, false); 498 mQuotaController.saveTimingSession(0, "com.android.test", fiv, true); 499 mQuotaController.saveTimingSession(0, "com.android.test", fou, true); 500 mQuotaController.saveTimingSession(0, "com.android.test", one, true); 501 502 synchronized (mQuotaController.mLock) { 503 mQuotaController.deleteObsoleteSessionsLocked(); 504 } 505 506 assertEquals(expectedRegular, mQuotaController.getTimingSessions(0, "com.android.test")); 507 assertEquals(expectedEJ, mQuotaController.getEJTimingSessions(0, "com.android.test")); 508 } 509 510 @Test testOnAppRemovedLocked()511 public void testOnAppRemovedLocked() { 512 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 513 mQuotaController.saveTimingSession(0, "com.android.test.remove", 514 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 515 mQuotaController.saveTimingSession(0, "com.android.test.remove", 516 createTimingSession( 517 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5), 518 false); 519 mQuotaController.saveTimingSession(0, "com.android.test.remove", 520 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false); 521 mQuotaController.saveTimingSession(0, "com.android.test.remove", 522 createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), true); 523 mQuotaController.saveTimingSession(0, "com.android.test.remove", 524 createTimingSession(now - (15 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), true); 525 // Test that another app isn't affected. 526 TimingSession one = createTimingSession( 527 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); 528 TimingSession two = createTimingSession( 529 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); 530 List<TimingSession> expected = new ArrayList<>(); 531 // Added in correct (chronological) order. 532 expected.add(two); 533 expected.add(one); 534 mQuotaController.saveTimingSession(0, "com.android.test.stay", two, false); 535 mQuotaController.saveTimingSession(0, "com.android.test.stay", one, false); 536 mQuotaController.saveTimingSession(0, "com.android.test.stay", one, true); 537 538 assertNotNull(mQuotaController.getTimingSessions(0, "com.android.test.remove")); 539 assertNotNull(mQuotaController.getEJTimingSessions(0, "com.android.test.remove")); 540 assertNotNull(mQuotaController.getTimingSessions(0, "com.android.test.stay")); 541 assertNotNull(mQuotaController.getEJTimingSessions(0, "com.android.test.stay")); 542 543 ExecutionStats expectedStats = new ExecutionStats(); 544 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; 545 expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; 546 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 547 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 548 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; 549 550 final int uid = 10001; 551 synchronized (mQuotaController.mLock) { 552 mQuotaController.onAppRemovedLocked("com.android.test.remove", uid); 553 } 554 assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove")); 555 assertNull(mQuotaController.getEJTimingSessions(0, "com.android.test.remove")); 556 assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay")); 557 synchronized (mQuotaController.mLock) { 558 assertEquals(expectedStats, 559 mQuotaController.getExecutionStatsLocked( 560 0, "com.android.test.remove", RARE_INDEX)); 561 assertNotEquals(expectedStats, 562 mQuotaController.getExecutionStatsLocked( 563 0, "com.android.test.stay", RARE_INDEX)); 564 565 assertFalse(mQuotaController.getForegroundUids().get(uid)); 566 } 567 } 568 569 @Test testOnUserRemovedLocked()570 public void testOnUserRemovedLocked() { 571 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 572 mQuotaController.saveTimingSession(0, "com.android.test", 573 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 574 mQuotaController.saveTimingSession(0, "com.android.test", 575 createTimingSession( 576 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5), 577 false); 578 mQuotaController.saveTimingSession(0, "com.android.test", 579 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false); 580 mQuotaController.saveTimingSession(0, "com.android.test", 581 createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), true); 582 mQuotaController.saveTimingSession(0, "com.android.test", 583 createTimingSession(now - (15 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), true); 584 // Test that another user isn't affected. 585 TimingSession one = createTimingSession( 586 now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); 587 TimingSession two = createTimingSession( 588 now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); 589 List<TimingSession> expectedRegular = new ArrayList<>(); 590 List<TimingSession> expectedEJ = new ArrayList<>(); 591 // Added in correct (chronological) order. 592 expectedRegular.add(two); 593 expectedRegular.add(one); 594 expectedEJ.add(one); 595 mQuotaController.saveTimingSession(10, "com.android.test", two, false); 596 mQuotaController.saveTimingSession(10, "com.android.test", one, false); 597 mQuotaController.saveTimingSession(10, "com.android.test", one, true); 598 599 assertNotNull(mQuotaController.getTimingSessions(0, "com.android.test")); 600 assertNotNull(mQuotaController.getEJTimingSessions(0, "com.android.test")); 601 assertNotNull(mQuotaController.getTimingSessions(10, "com.android.test")); 602 assertNotNull(mQuotaController.getEJTimingSessions(10, "com.android.test")); 603 604 ExecutionStats expectedStats = new ExecutionStats(); 605 expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; 606 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; 607 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 608 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 609 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; 610 611 synchronized (mQuotaController.mLock) { 612 mQuotaController.onUserRemovedLocked(0); 613 assertNull(mQuotaController.getTimingSessions(0, "com.android.test")); 614 assertNull(mQuotaController.getEJTimingSessions(0, "com.android.test")); 615 assertEquals(expectedRegular, 616 mQuotaController.getTimingSessions(10, "com.android.test")); 617 assertEquals(expectedEJ, 618 mQuotaController.getEJTimingSessions(10, "com.android.test")); 619 assertEquals(expectedStats, 620 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX)); 621 assertNotEquals(expectedStats, 622 mQuotaController.getExecutionStatsLocked(10, "com.android.test", RARE_INDEX)); 623 } 624 } 625 626 @Test testUpdateExecutionStatsLocked_NoTimer()627 public void testUpdateExecutionStatsLocked_NoTimer() { 628 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 629 // Added in chronological order. 630 mQuotaController.saveTimingSession(0, "com.android.test", 631 createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 632 mQuotaController.saveTimingSession(0, "com.android.test", 633 createTimingSession( 634 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5), 635 false); 636 mQuotaController.saveTimingSession(0, "com.android.test", 637 createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1), false); 638 mQuotaController.saveTimingSession(0, "com.android.test", 639 createTimingSession( 640 now - (HOUR_IN_MILLIS - 10 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), 641 false); 642 mQuotaController.saveTimingSession(0, "com.android.test", 643 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3), false); 644 645 // Test an app that hasn't had any activity. 646 ExecutionStats expectedStats = new ExecutionStats(); 647 ExecutionStats inputStats = new ExecutionStats(); 648 649 inputStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; 650 inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS; 651 inputStats.jobCountLimit = expectedStats.jobCountLimit = 100; 652 inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100; 653 // Invalid time is now +24 hours since there are no sessions at all for the app. 654 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; 655 expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; 656 synchronized (mQuotaController.mLock) { 657 mQuotaController.updateExecutionStatsLocked(0, "com.android.test.not.run", inputStats); 658 } 659 assertEquals(expectedStats, inputStats); 660 661 inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS; 662 // Invalid time is now +18 hours since there are no sessions in the window but the earliest 663 // session is 6 hours ago. 664 expectedStats.expirationTimeElapsed = now + 18 * HOUR_IN_MILLIS; 665 expectedStats.executionTimeInWindowMs = 0; 666 expectedStats.bgJobCountInWindow = 0; 667 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 668 expectedStats.bgJobCountInMaxPeriod = 15; 669 expectedStats.sessionCountInWindow = 0; 670 synchronized (mQuotaController.mLock) { 671 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 672 } 673 assertEquals(expectedStats, inputStats); 674 675 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS; 676 // Invalid time is now since the session straddles the window cutoff time. 677 expectedStats.expirationTimeElapsed = now; 678 expectedStats.executionTimeInWindowMs = 2 * MINUTE_IN_MILLIS; 679 expectedStats.bgJobCountInWindow = 3; 680 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 681 expectedStats.bgJobCountInMaxPeriod = 15; 682 expectedStats.sessionCountInWindow = 1; 683 synchronized (mQuotaController.mLock) { 684 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 685 } 686 assertEquals(expectedStats, inputStats); 687 688 inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * MINUTE_IN_MILLIS; 689 // Invalid time is now since the start of the session is at the very edge of the window 690 // cutoff time. 691 expectedStats.expirationTimeElapsed = now; 692 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; 693 expectedStats.bgJobCountInWindow = 3; 694 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 695 expectedStats.bgJobCountInMaxPeriod = 15; 696 expectedStats.sessionCountInWindow = 1; 697 synchronized (mQuotaController.mLock) { 698 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 699 } 700 assertEquals(expectedStats, inputStats); 701 702 inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS; 703 // Invalid time is now +44 minutes since the earliest session in the window is now-5 704 // minutes. 705 expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS; 706 expectedStats.executionTimeInWindowMs = 4 * MINUTE_IN_MILLIS; 707 expectedStats.bgJobCountInWindow = 3; 708 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 709 expectedStats.bgJobCountInMaxPeriod = 15; 710 expectedStats.sessionCountInWindow = 1; 711 synchronized (mQuotaController.mLock) { 712 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 713 } 714 assertEquals(expectedStats, inputStats); 715 716 inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS; 717 // Invalid time is now since the session is at the very edge of the window cutoff time. 718 expectedStats.expirationTimeElapsed = now; 719 expectedStats.executionTimeInWindowMs = 5 * MINUTE_IN_MILLIS; 720 expectedStats.bgJobCountInWindow = 4; 721 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 722 expectedStats.bgJobCountInMaxPeriod = 15; 723 expectedStats.sessionCountInWindow = 2; 724 synchronized (mQuotaController.mLock) { 725 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 726 } 727 assertEquals(expectedStats, inputStats); 728 729 inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS; 730 inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 2; 731 // Invalid time is now since the start of the session is at the very edge of the window 732 // cutoff time. 733 expectedStats.expirationTimeElapsed = now; 734 expectedStats.executionTimeInWindowMs = 6 * MINUTE_IN_MILLIS; 735 expectedStats.bgJobCountInWindow = 5; 736 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 737 expectedStats.bgJobCountInMaxPeriod = 15; 738 expectedStats.sessionCountInWindow = 3; 739 expectedStats.inQuotaTimeElapsed = now + 11 * MINUTE_IN_MILLIS; 740 synchronized (mQuotaController.mLock) { 741 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 742 } 743 assertEquals(expectedStats, inputStats); 744 745 inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; 746 inputStats.jobCountLimit = expectedStats.jobCountLimit = 6; 747 inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 100; 748 // Invalid time is now since the session straddles the window cutoff time. 749 expectedStats.expirationTimeElapsed = now; 750 expectedStats.executionTimeInWindowMs = 11 * MINUTE_IN_MILLIS; 751 expectedStats.bgJobCountInWindow = 10; 752 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 753 expectedStats.bgJobCountInMaxPeriod = 15; 754 expectedStats.sessionCountInWindow = 4; 755 expectedStats.inQuotaTimeElapsed = now + 5 * MINUTE_IN_MILLIS; 756 synchronized (mQuotaController.mLock) { 757 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 758 } 759 assertEquals(expectedStats, inputStats); 760 761 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * HOUR_IN_MILLIS; 762 // Invalid time is now +59 minutes since the earliest session in the window is now-121 763 // minutes. 764 expectedStats.expirationTimeElapsed = now + 59 * MINUTE_IN_MILLIS; 765 expectedStats.executionTimeInWindowMs = 12 * MINUTE_IN_MILLIS; 766 expectedStats.bgJobCountInWindow = 10; 767 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 768 expectedStats.bgJobCountInMaxPeriod = 15; 769 expectedStats.sessionCountInWindow = 4; 770 // App goes under job execution time limit in ~61 minutes, but will be under job count limit 771 // in 65 minutes. 772 expectedStats.inQuotaTimeElapsed = now + 65 * MINUTE_IN_MILLIS; 773 synchronized (mQuotaController.mLock) { 774 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 775 } 776 assertEquals(expectedStats, inputStats); 777 778 inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS; 779 // Invalid time is now since the start of the session is at the very edge of the window 780 // cutoff time. 781 expectedStats.expirationTimeElapsed = now; 782 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; 783 expectedStats.bgJobCountInWindow = 15; 784 expectedStats.executionTimeInMaxPeriodMs = 22 * MINUTE_IN_MILLIS; 785 expectedStats.bgJobCountInMaxPeriod = 15; 786 expectedStats.sessionCountInWindow = 5; 787 expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS + 5 * MINUTE_IN_MILLIS; 788 synchronized (mQuotaController.mLock) { 789 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 790 } 791 assertEquals(expectedStats, inputStats); 792 793 // Make sure expirationTimeElapsed is set correctly when it's dependent on the max period. 794 mQuotaController.getTimingSessions(0, "com.android.test") 795 .add(0, 796 createTimingSession(now - (23 * HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 3)); 797 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; 798 inputStats.jobCountLimit = expectedStats.jobCountLimit = 100; 799 // Invalid time is now +1 hour since the earliest session in the max period is 1 hour 800 // before the end of the max period cutoff time. 801 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 802 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; 803 expectedStats.bgJobCountInWindow = 15; 804 expectedStats.executionTimeInMaxPeriodMs = 23 * MINUTE_IN_MILLIS; 805 expectedStats.bgJobCountInMaxPeriod = 18; 806 expectedStats.sessionCountInWindow = 5; 807 expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS 808 + mQcConstants.IN_QUOTA_BUFFER_MS; 809 synchronized (mQuotaController.mLock) { 810 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 811 } 812 assertEquals(expectedStats, inputStats); 813 814 mQuotaController.getTimingSessions(0, "com.android.test") 815 .add(0, 816 createTimingSession(now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 817 2 * MINUTE_IN_MILLIS, 2)); 818 inputStats.windowSizeMs = expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; 819 // Invalid time is now since the earliest session straddles the max period cutoff time. 820 expectedStats.expirationTimeElapsed = now; 821 expectedStats.executionTimeInWindowMs = 22 * MINUTE_IN_MILLIS; 822 expectedStats.bgJobCountInWindow = 15; 823 expectedStats.executionTimeInMaxPeriodMs = 24 * MINUTE_IN_MILLIS; 824 expectedStats.bgJobCountInMaxPeriod = 20; 825 expectedStats.sessionCountInWindow = 5; 826 expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS 827 + mQcConstants.IN_QUOTA_BUFFER_MS; 828 synchronized (mQuotaController.mLock) { 829 mQuotaController.updateExecutionStatsLocked(0, "com.android.test", inputStats); 830 } 831 assertEquals(expectedStats, inputStats); 832 } 833 834 @Test testUpdateExecutionStatsLocked_WithTimer()835 public void testUpdateExecutionStatsLocked_WithTimer() { 836 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 837 838 ExecutionStats expectedStats = new ExecutionStats(); 839 ExecutionStats inputStats = new ExecutionStats(); 840 inputStats.allowedTimePerPeriodMs = expectedStats.allowedTimePerPeriodMs = 841 10 * MINUTE_IN_MILLIS; 842 inputStats.windowSizeMs = expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 843 inputStats.jobCountLimit = expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 844 inputStats.sessionCountLimit = expectedStats.sessionCountLimit = 845 mQcConstants.MAX_SESSION_COUNT_RARE; 846 // Active timer isn't counted as session yet. 847 expectedStats.sessionCountInWindow = 0; 848 // Timer only, under quota. 849 for (int i = 1; i < mQcConstants.MAX_JOB_COUNT_RARE; ++i) { 850 JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", i); 851 setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window 852 synchronized (mQuotaController.mLock) { 853 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 854 mQuotaController.prepareForExecutionLocked(jobStatus); 855 } 856 advanceElapsedClock(7000); 857 858 expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis(); 859 expectedStats.executionTimeInWindowMs = expectedStats.executionTimeInMaxPeriodMs = 860 7000 * i; 861 expectedStats.bgJobCountInWindow = expectedStats.bgJobCountInMaxPeriod = i; 862 synchronized (mQuotaController.mLock) { 863 mQuotaController.updateExecutionStatsLocked( 864 SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); 865 assertEquals(expectedStats, inputStats); 866 assertTrue(mQuotaController.isWithinQuotaLocked( 867 SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); 868 } 869 assertTrue("Job not ready: " + jobStatus, jobStatus.isReady()); 870 } 871 872 // Add old session. Make sure values are combined correctly. 873 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 874 createTimingSession(sElapsedRealtimeClock.millis() - (6 * HOUR_IN_MILLIS), 875 10 * MINUTE_IN_MILLIS, 5), false); 876 expectedStats.sessionCountInWindow = 1; 877 878 expectedStats.expirationTimeElapsed = sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS; 879 expectedStats.executionTimeInWindowMs += 10 * MINUTE_IN_MILLIS; 880 expectedStats.executionTimeInMaxPeriodMs += 10 * MINUTE_IN_MILLIS; 881 expectedStats.bgJobCountInWindow += 5; 882 expectedStats.bgJobCountInMaxPeriod += 5; 883 // Active timer is under quota, so out of quota due to old session. 884 expectedStats.inQuotaTimeElapsed = 885 sElapsedRealtimeClock.millis() + 18 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS; 886 synchronized (mQuotaController.mLock) { 887 mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); 888 assertEquals(expectedStats, inputStats); 889 assertFalse( 890 mQuotaController.isWithinQuotaLocked( 891 SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); 892 } 893 894 // Quota should be exceeded due to activity in active timer. 895 JobStatus jobStatus = createJobStatus("testUpdateExecutionStatsLocked_WithTimer", 0); 896 setStandbyBucket(RARE_INDEX, jobStatus); // 24 hour window 897 synchronized (mQuotaController.mLock) { 898 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 899 mQuotaController.prepareForExecutionLocked(jobStatus); 900 } 901 advanceElapsedClock(10000); 902 903 expectedStats.executionTimeInWindowMs += 10000; 904 expectedStats.executionTimeInMaxPeriodMs += 10000; 905 expectedStats.bgJobCountInWindow++; 906 expectedStats.bgJobCountInMaxPeriod++; 907 // Out of quota due to activity in active timer, so in quota time should be when enough 908 // time has passed since active timer. 909 expectedStats.inQuotaTimeElapsed = 910 sElapsedRealtimeClock.millis() + expectedStats.windowSizeMs; 911 synchronized (mQuotaController.mLock) { 912 mQuotaController.updateExecutionStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE, inputStats); 913 assertEquals(expectedStats, inputStats); 914 assertFalse( 915 mQuotaController.isWithinQuotaLocked( 916 SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX)); 917 assertFalse("Job unexpectedly ready: " + jobStatus, jobStatus.isReady()); 918 } 919 } 920 921 /** 922 * Tests that getExecutionStatsLocked returns the correct stats. 923 */ 924 @Test testGetExecutionStatsLocked_Values()925 public void testGetExecutionStatsLocked_Values() { 926 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 927 mQuotaController.saveTimingSession(0, "com.android.test", 928 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 929 mQuotaController.saveTimingSession(0, "com.android.test", 930 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 931 mQuotaController.saveTimingSession(0, "com.android.test", 932 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 933 mQuotaController.saveTimingSession(0, "com.android.test", 934 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 935 936 ExecutionStats expectedStats = new ExecutionStats(); 937 938 // Active 939 expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; 940 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; 941 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; 942 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; 943 expectedStats.expirationTimeElapsed = now + 4 * MINUTE_IN_MILLIS; 944 expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS; 945 expectedStats.bgJobCountInWindow = 5; 946 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; 947 expectedStats.bgJobCountInMaxPeriod = 20; 948 expectedStats.sessionCountInWindow = 1; 949 synchronized (mQuotaController.mLock) { 950 assertEquals(expectedStats, 951 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX)); 952 } 953 954 // Working 955 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; 956 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING; 957 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING; 958 expectedStats.expirationTimeElapsed = now; 959 expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS; 960 expectedStats.bgJobCountInWindow = 10; 961 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; 962 expectedStats.bgJobCountInMaxPeriod = 20; 963 expectedStats.sessionCountInWindow = 2; 964 expectedStats.inQuotaTimeElapsed = now + 3 * MINUTE_IN_MILLIS 965 + mQcConstants.IN_QUOTA_BUFFER_MS; 966 synchronized (mQuotaController.mLock) { 967 assertEquals(expectedStats, 968 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX)); 969 } 970 971 // Frequent 972 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; 973 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT; 974 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT; 975 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 976 expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS; 977 expectedStats.bgJobCountInWindow = 15; 978 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; 979 expectedStats.bgJobCountInMaxPeriod = 20; 980 expectedStats.sessionCountInWindow = 3; 981 expectedStats.inQuotaTimeElapsed = now + 6 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS 982 + mQcConstants.IN_QUOTA_BUFFER_MS; 983 synchronized (mQuotaController.mLock) { 984 assertEquals(expectedStats, 985 mQuotaController.getExecutionStatsLocked( 986 0, "com.android.test", FREQUENT_INDEX)); 987 } 988 989 // Rare 990 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 991 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 992 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; 993 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 994 expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS; 995 expectedStats.bgJobCountInWindow = 20; 996 expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; 997 expectedStats.bgJobCountInMaxPeriod = 20; 998 expectedStats.sessionCountInWindow = 4; 999 expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS 1000 + mQcConstants.IN_QUOTA_BUFFER_MS; 1001 synchronized (mQuotaController.mLock) { 1002 assertEquals(expectedStats, 1003 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX)); 1004 } 1005 } 1006 1007 /** 1008 * Tests that getExecutionStatsLocked returns the correct stats soon after device startup. 1009 */ 1010 @Test testGetExecutionStatsLocked_Values_BeginningOfTime()1011 public void testGetExecutionStatsLocked_Values_BeginningOfTime() { 1012 // Set time to 3 minutes after boot. 1013 advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis()); 1014 advanceElapsedClock(3 * MINUTE_IN_MILLIS); 1015 1016 mQuotaController.saveTimingSession(0, "com.android.test", 1017 createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), false); 1018 1019 ExecutionStats expectedStats = new ExecutionStats(); 1020 1021 // Active 1022 expectedStats.allowedTimePerPeriodMs = 10 * MINUTE_IN_MILLIS; 1023 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; 1024 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; 1025 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; 1026 expectedStats.expirationTimeElapsed = 11 * MINUTE_IN_MILLIS; 1027 expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS; 1028 expectedStats.bgJobCountInWindow = 2; 1029 expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS; 1030 expectedStats.bgJobCountInMaxPeriod = 2; 1031 expectedStats.sessionCountInWindow = 1; 1032 synchronized (mQuotaController.mLock) { 1033 assertEquals(expectedStats, 1034 mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX)); 1035 } 1036 1037 // Working 1038 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; 1039 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING; 1040 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING; 1041 expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; 1042 synchronized (mQuotaController.mLock) { 1043 assertEquals(expectedStats, 1044 mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX)); 1045 } 1046 1047 // Frequent 1048 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; 1049 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT; 1050 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT; 1051 expectedStats.expirationTimeElapsed = 8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; 1052 synchronized (mQuotaController.mLock) { 1053 assertEquals(expectedStats, 1054 mQuotaController.getExecutionStatsLocked( 1055 0, "com.android.test", FREQUENT_INDEX)); 1056 } 1057 1058 // Rare 1059 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 1060 expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; 1061 expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; 1062 expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; 1063 synchronized (mQuotaController.mLock) { 1064 assertEquals(expectedStats, 1065 mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX)); 1066 } 1067 } 1068 1069 /** 1070 * Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing. 1071 */ 1072 @Test testGetExecutionStatsLocked_CoalescingSessions()1073 public void testGetExecutionStatsLocked_CoalescingSessions() { 1074 for (int i = 0; i < 10; ++i) { 1075 mQuotaController.saveTimingSession(0, "com.android.test", 1076 createTimingSession( 1077 JobSchedulerService.sElapsedRealtimeClock.millis(), 1078 5 * MINUTE_IN_MILLIS, 5), false); 1079 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 1080 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 1081 for (int j = 0; j < 5; ++j) { 1082 mQuotaController.saveTimingSession(0, "com.android.test", 1083 createTimingSession( 1084 JobSchedulerService.sElapsedRealtimeClock.millis(), 1085 MINUTE_IN_MILLIS, 2), false); 1086 advanceElapsedClock(MINUTE_IN_MILLIS); 1087 advanceElapsedClock(54 * SECOND_IN_MILLIS); 1088 mQuotaController.saveTimingSession(0, "com.android.test", 1089 createTimingSession( 1090 JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false); 1091 advanceElapsedClock(500); 1092 advanceElapsedClock(400); 1093 mQuotaController.saveTimingSession(0, "com.android.test", 1094 createTimingSession( 1095 JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false); 1096 advanceElapsedClock(100); 1097 advanceElapsedClock(5 * SECOND_IN_MILLIS); 1098 } 1099 advanceElapsedClock(40 * MINUTE_IN_MILLIS); 1100 } 1101 1102 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 0); 1103 1104 synchronized (mQuotaController.mLock) { 1105 mQuotaController.invalidateAllExecutionStatsLocked(); 1106 assertEquals(0, mQuotaController.getExecutionStatsLocked( 1107 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 1108 assertEquals(32, mQuotaController.getExecutionStatsLocked( 1109 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 1110 assertEquals(128, mQuotaController.getExecutionStatsLocked( 1111 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 1112 assertEquals(160, mQuotaController.getExecutionStatsLocked( 1113 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 1114 } 1115 1116 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 500); 1117 1118 synchronized (mQuotaController.mLock) { 1119 mQuotaController.invalidateAllExecutionStatsLocked(); 1120 assertEquals(0, mQuotaController.getExecutionStatsLocked( 1121 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 1122 assertEquals(22, mQuotaController.getExecutionStatsLocked( 1123 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 1124 assertEquals(88, mQuotaController.getExecutionStatsLocked( 1125 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 1126 assertEquals(110, mQuotaController.getExecutionStatsLocked( 1127 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 1128 } 1129 1130 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1000); 1131 1132 synchronized (mQuotaController.mLock) { 1133 mQuotaController.invalidateAllExecutionStatsLocked(); 1134 assertEquals(0, mQuotaController.getExecutionStatsLocked( 1135 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 1136 assertEquals(22, mQuotaController.getExecutionStatsLocked( 1137 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 1138 assertEquals(88, mQuotaController.getExecutionStatsLocked( 1139 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 1140 assertEquals(110, mQuotaController.getExecutionStatsLocked( 1141 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 1142 } 1143 1144 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1145 5 * SECOND_IN_MILLIS); 1146 1147 synchronized (mQuotaController.mLock) { 1148 mQuotaController.invalidateAllExecutionStatsLocked(); 1149 assertEquals(0, mQuotaController.getExecutionStatsLocked( 1150 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 1151 assertEquals(14, mQuotaController.getExecutionStatsLocked( 1152 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 1153 assertEquals(56, mQuotaController.getExecutionStatsLocked( 1154 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 1155 assertEquals(70, mQuotaController.getExecutionStatsLocked( 1156 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 1157 } 1158 1159 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1160 MINUTE_IN_MILLIS); 1161 1162 synchronized (mQuotaController.mLock) { 1163 mQuotaController.invalidateAllExecutionStatsLocked(); 1164 assertEquals(0, mQuotaController.getExecutionStatsLocked( 1165 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 1166 assertEquals(4, mQuotaController.getExecutionStatsLocked( 1167 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 1168 assertEquals(16, mQuotaController.getExecutionStatsLocked( 1169 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 1170 assertEquals(20, mQuotaController.getExecutionStatsLocked( 1171 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 1172 } 1173 1174 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1175 5 * MINUTE_IN_MILLIS); 1176 1177 synchronized (mQuotaController.mLock) { 1178 mQuotaController.invalidateAllExecutionStatsLocked(); 1179 assertEquals(0, mQuotaController.getExecutionStatsLocked( 1180 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 1181 assertEquals(2, mQuotaController.getExecutionStatsLocked( 1182 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 1183 assertEquals(8, mQuotaController.getExecutionStatsLocked( 1184 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 1185 assertEquals(10, mQuotaController.getExecutionStatsLocked( 1186 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 1187 } 1188 1189 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1190 15 * MINUTE_IN_MILLIS); 1191 1192 synchronized (mQuotaController.mLock) { 1193 mQuotaController.invalidateAllExecutionStatsLocked(); 1194 assertEquals(0, mQuotaController.getExecutionStatsLocked( 1195 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 1196 assertEquals(2, mQuotaController.getExecutionStatsLocked( 1197 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 1198 assertEquals(8, mQuotaController.getExecutionStatsLocked( 1199 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 1200 assertEquals(10, mQuotaController.getExecutionStatsLocked( 1201 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 1202 } 1203 1204 // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference 1205 // between an hour and 15 minutes. 1206 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, HOUR_IN_MILLIS); 1207 1208 synchronized (mQuotaController.mLock) { 1209 mQuotaController.invalidateAllExecutionStatsLocked(); 1210 assertEquals(0, mQuotaController.getExecutionStatsLocked( 1211 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); 1212 assertEquals(2, mQuotaController.getExecutionStatsLocked( 1213 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); 1214 assertEquals(8, mQuotaController.getExecutionStatsLocked( 1215 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); 1216 assertEquals(10, mQuotaController.getExecutionStatsLocked( 1217 0, "com.android.test", RARE_INDEX).sessionCountInWindow); 1218 } 1219 } 1220 1221 /** 1222 * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object. 1223 */ 1224 @Test testGetExecutionStatsLocked_Caching()1225 public void testGetExecutionStatsLocked_Caching() { 1226 spyOn(mQuotaController); 1227 doNothing().when(mQuotaController).invalidateAllExecutionStatsLocked(); 1228 1229 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1230 mQuotaController.saveTimingSession(0, "com.android.test", 1231 createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 1232 mQuotaController.saveTimingSession(0, "com.android.test", 1233 createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 1234 mQuotaController.saveTimingSession(0, "com.android.test", 1235 createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 1236 mQuotaController.saveTimingSession(0, "com.android.test", 1237 createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 1238 final ExecutionStats originalStatsActive; 1239 final ExecutionStats originalStatsWorking; 1240 final ExecutionStats originalStatsFrequent; 1241 final ExecutionStats originalStatsRare; 1242 synchronized (mQuotaController.mLock) { 1243 originalStatsActive = mQuotaController.getExecutionStatsLocked( 1244 0, "com.android.test", ACTIVE_INDEX); 1245 originalStatsWorking = mQuotaController.getExecutionStatsLocked( 1246 0, "com.android.test", WORKING_INDEX); 1247 originalStatsFrequent = mQuotaController.getExecutionStatsLocked( 1248 0, "com.android.test", FREQUENT_INDEX); 1249 originalStatsRare = mQuotaController.getExecutionStatsLocked( 1250 0, "com.android.test", RARE_INDEX); 1251 } 1252 1253 // Advance clock so that the working stats shouldn't be the same. 1254 advanceElapsedClock(MINUTE_IN_MILLIS); 1255 // Change frequent bucket size so that the stats need to be recalculated. 1256 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 6 * HOUR_IN_MILLIS); 1257 1258 ExecutionStats expectedStats = new ExecutionStats(); 1259 expectedStats.allowedTimePerPeriodMs = originalStatsActive.allowedTimePerPeriodMs; 1260 expectedStats.windowSizeMs = originalStatsActive.windowSizeMs; 1261 expectedStats.jobCountLimit = originalStatsActive.jobCountLimit; 1262 expectedStats.sessionCountLimit = originalStatsActive.sessionCountLimit; 1263 expectedStats.expirationTimeElapsed = originalStatsActive.expirationTimeElapsed; 1264 expectedStats.executionTimeInWindowMs = originalStatsActive.executionTimeInWindowMs; 1265 expectedStats.bgJobCountInWindow = originalStatsActive.bgJobCountInWindow; 1266 expectedStats.executionTimeInMaxPeriodMs = originalStatsActive.executionTimeInMaxPeriodMs; 1267 expectedStats.bgJobCountInMaxPeriod = originalStatsActive.bgJobCountInMaxPeriod; 1268 expectedStats.sessionCountInWindow = originalStatsActive.sessionCountInWindow; 1269 expectedStats.inQuotaTimeElapsed = originalStatsActive.inQuotaTimeElapsed; 1270 final ExecutionStats newStatsActive; 1271 synchronized (mQuotaController.mLock) { 1272 newStatsActive = mQuotaController.getExecutionStatsLocked( 1273 0, "com.android.test", ACTIVE_INDEX); 1274 } 1275 // Stats for the same bucket should use the same object. 1276 assertTrue(originalStatsActive == newStatsActive); 1277 assertEquals(expectedStats, newStatsActive); 1278 1279 expectedStats.allowedTimePerPeriodMs = originalStatsWorking.allowedTimePerPeriodMs; 1280 expectedStats.windowSizeMs = originalStatsWorking.windowSizeMs; 1281 expectedStats.jobCountLimit = originalStatsWorking.jobCountLimit; 1282 expectedStats.sessionCountLimit = originalStatsWorking.sessionCountLimit; 1283 expectedStats.expirationTimeElapsed = originalStatsWorking.expirationTimeElapsed; 1284 expectedStats.executionTimeInWindowMs = originalStatsWorking.executionTimeInWindowMs; 1285 expectedStats.bgJobCountInWindow = originalStatsWorking.bgJobCountInWindow; 1286 expectedStats.sessionCountInWindow = originalStatsWorking.sessionCountInWindow; 1287 expectedStats.inQuotaTimeElapsed = originalStatsWorking.inQuotaTimeElapsed; 1288 final ExecutionStats newStatsWorking; 1289 synchronized (mQuotaController.mLock) { 1290 newStatsWorking = mQuotaController.getExecutionStatsLocked( 1291 0, "com.android.test", WORKING_INDEX); 1292 } 1293 assertTrue(originalStatsWorking == newStatsWorking); 1294 assertNotEquals(expectedStats, newStatsWorking); 1295 1296 expectedStats.allowedTimePerPeriodMs = originalStatsFrequent.allowedTimePerPeriodMs; 1297 expectedStats.windowSizeMs = originalStatsFrequent.windowSizeMs; 1298 expectedStats.jobCountLimit = originalStatsFrequent.jobCountLimit; 1299 expectedStats.sessionCountLimit = originalStatsFrequent.sessionCountLimit; 1300 expectedStats.expirationTimeElapsed = originalStatsFrequent.expirationTimeElapsed; 1301 expectedStats.executionTimeInWindowMs = originalStatsFrequent.executionTimeInWindowMs; 1302 expectedStats.bgJobCountInWindow = originalStatsFrequent.bgJobCountInWindow; 1303 expectedStats.sessionCountInWindow = originalStatsFrequent.sessionCountInWindow; 1304 expectedStats.inQuotaTimeElapsed = originalStatsFrequent.inQuotaTimeElapsed; 1305 final ExecutionStats newStatsFrequent; 1306 synchronized (mQuotaController.mLock) { 1307 newStatsFrequent = mQuotaController.getExecutionStatsLocked( 1308 0, "com.android.test", FREQUENT_INDEX); 1309 } 1310 assertTrue(originalStatsFrequent == newStatsFrequent); 1311 assertNotEquals(expectedStats, newStatsFrequent); 1312 1313 expectedStats.allowedTimePerPeriodMs = originalStatsRare.allowedTimePerPeriodMs; 1314 expectedStats.windowSizeMs = originalStatsRare.windowSizeMs; 1315 expectedStats.jobCountLimit = originalStatsRare.jobCountLimit; 1316 expectedStats.sessionCountLimit = originalStatsRare.sessionCountLimit; 1317 expectedStats.expirationTimeElapsed = originalStatsRare.expirationTimeElapsed; 1318 expectedStats.executionTimeInWindowMs = originalStatsRare.executionTimeInWindowMs; 1319 expectedStats.bgJobCountInWindow = originalStatsRare.bgJobCountInWindow; 1320 expectedStats.sessionCountInWindow = originalStatsRare.sessionCountInWindow; 1321 expectedStats.inQuotaTimeElapsed = originalStatsRare.inQuotaTimeElapsed; 1322 final ExecutionStats newStatsRare; 1323 synchronized (mQuotaController.mLock) { 1324 newStatsRare = mQuotaController.getExecutionStatsLocked( 1325 0, "com.android.test", RARE_INDEX); 1326 } 1327 assertTrue(originalStatsRare == newStatsRare); 1328 assertEquals(expectedStats, newStatsRare); 1329 } 1330 1331 @Test testGetMaxJobExecutionTimeLocked_Regular()1332 public void testGetMaxJobExecutionTimeLocked_Regular() { 1333 mQuotaController.saveTimingSession(0, SOURCE_PACKAGE, 1334 createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS), 1335 3 * MINUTE_IN_MILLIS, 5), false); 1336 JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0); 1337 setStandbyBucket(RARE_INDEX, job); 1338 1339 setCharging(); 1340 synchronized (mQuotaController.mLock) { 1341 assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, 1342 mQuotaController.getMaxJobExecutionTimeMsLocked((job))); 1343 } 1344 1345 setDischarging(); 1346 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 1347 synchronized (mQuotaController.mLock) { 1348 assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, 1349 mQuotaController.getMaxJobExecutionTimeMsLocked((job))); 1350 } 1351 1352 // Top-started job 1353 setProcessState(ActivityManager.PROCESS_STATE_TOP); 1354 synchronized (mQuotaController.mLock) { 1355 mQuotaController.maybeStartTrackingJobLocked(job, null); 1356 mQuotaController.prepareForExecutionLocked(job); 1357 } 1358 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 1359 synchronized (mQuotaController.mLock) { 1360 assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, 1361 mQuotaController.getMaxJobExecutionTimeMsLocked((job))); 1362 mQuotaController.maybeStopTrackingJobLocked(job, null); 1363 } 1364 1365 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 1366 synchronized (mQuotaController.mLock) { 1367 assertEquals(7 * MINUTE_IN_MILLIS, 1368 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 1369 } 1370 } 1371 1372 @Test testGetMaxJobExecutionTimeLocked_Regular_Active()1373 public void testGetMaxJobExecutionTimeLocked_Regular_Active() { 1374 JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked_Regular_Active", 0); 1375 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 1376 10 * MINUTE_IN_MILLIS); 1377 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 10 * MINUTE_IN_MILLIS); 1378 setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 2 * HOUR_IN_MILLIS); 1379 setDischarging(); 1380 setStandbyBucket(ACTIVE_INDEX, job); 1381 setProcessState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY); 1382 1383 // ACTIVE apps (where allowed time = window size) should be capped at max execution limit. 1384 synchronized (mQuotaController.mLock) { 1385 assertEquals(2 * HOUR_IN_MILLIS, 1386 mQuotaController.getMaxJobExecutionTimeMsLocked((job))); 1387 } 1388 1389 // Make sure sessions are factored in properly. 1390 mQuotaController.saveTimingSession(0, SOURCE_PACKAGE, 1391 createTimingSession(sElapsedRealtimeClock.millis() - (6 * HOUR_IN_MILLIS), 1392 30 * MINUTE_IN_MILLIS, 1), false); 1393 synchronized (mQuotaController.mLock) { 1394 assertEquals(90 * MINUTE_IN_MILLIS, 1395 mQuotaController.getMaxJobExecutionTimeMsLocked((job))); 1396 } 1397 1398 mQuotaController.saveTimingSession(0, SOURCE_PACKAGE, 1399 createTimingSession(sElapsedRealtimeClock.millis() - (5 * HOUR_IN_MILLIS), 1400 30 * MINUTE_IN_MILLIS, 1), false); 1401 mQuotaController.saveTimingSession(0, SOURCE_PACKAGE, 1402 createTimingSession(sElapsedRealtimeClock.millis() - (4 * HOUR_IN_MILLIS), 1403 30 * MINUTE_IN_MILLIS, 1), false); 1404 mQuotaController.saveTimingSession(0, SOURCE_PACKAGE, 1405 createTimingSession(sElapsedRealtimeClock.millis() - (3 * HOUR_IN_MILLIS), 1406 25 * MINUTE_IN_MILLIS, 1), false); 1407 synchronized (mQuotaController.mLock) { 1408 assertEquals(5 * MINUTE_IN_MILLIS, 1409 mQuotaController.getMaxJobExecutionTimeMsLocked((job))); 1410 } 1411 } 1412 1413 @Test testGetMaxJobExecutionTimeLocked_EJ()1414 public void testGetMaxJobExecutionTimeLocked_EJ() { 1415 final long timeUsedMs = 3 * MINUTE_IN_MILLIS; 1416 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1417 createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS), 1418 timeUsedMs, 5), true); 1419 JobStatus job = createExpeditedJobStatus("testGetMaxJobExecutionTimeLocked_EJ", 0); 1420 setStandbyBucket(RARE_INDEX, job); 1421 synchronized (mQuotaController.mLock) { 1422 mQuotaController.maybeStartTrackingJobLocked(job, null); 1423 } 1424 1425 setCharging(); 1426 synchronized (mQuotaController.mLock) { 1427 assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, 1428 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 1429 } 1430 1431 setDischarging(); 1432 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 1433 synchronized (mQuotaController.mLock) { 1434 assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2, 1435 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 1436 } 1437 1438 // Top-started job 1439 setProcessState(ActivityManager.PROCESS_STATE_TOP); 1440 synchronized (mQuotaController.mLock) { 1441 mQuotaController.prepareForExecutionLocked(job); 1442 } 1443 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 1444 synchronized (mQuotaController.mLock) { 1445 assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2, 1446 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 1447 mQuotaController.maybeStopTrackingJobLocked(job, null); 1448 } 1449 1450 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 1451 synchronized (mQuotaController.mLock) { 1452 assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs, 1453 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 1454 } 1455 1456 // Test used quota rolling out of window. 1457 synchronized (mQuotaController.mLock) { 1458 mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); 1459 } 1460 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1461 createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS, 1462 timeUsedMs, 5), true); 1463 1464 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 1465 synchronized (mQuotaController.mLock) { 1466 assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2, 1467 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 1468 } 1469 1470 // Top-started job 1471 setProcessState(ActivityManager.PROCESS_STATE_TOP); 1472 synchronized (mQuotaController.mLock) { 1473 mQuotaController.maybeStartTrackingJobLocked(job, null); 1474 mQuotaController.prepareForExecutionLocked(job); 1475 } 1476 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 1477 synchronized (mQuotaController.mLock) { 1478 assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2, 1479 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 1480 mQuotaController.maybeStopTrackingJobLocked(job, null); 1481 } 1482 1483 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 1484 synchronized (mQuotaController.mLock) { 1485 assertEquals(mQcConstants.EJ_LIMIT_RARE_MS, 1486 mQuotaController.getMaxJobExecutionTimeMsLocked(job)); 1487 } 1488 } 1489 1490 /** 1491 * Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size. 1492 */ 1493 @Test testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow()1494 public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow() { 1495 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1496 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1497 createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false); 1498 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1499 createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), 1500 false); 1501 1502 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, 1503 10 * MINUTE_IN_MILLIS); 1504 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 10 * MINUTE_IN_MILLIS); 1505 // window size = allowed time, so jobs can essentially run non-stop until they reach the 1506 // max execution time. 1507 setStandbyBucket(EXEMPTED_INDEX); 1508 synchronized (mQuotaController.mLock) { 1509 assertEquals(0, 1510 mQuotaController.getRemainingExecutionTimeLocked( 1511 SOURCE_USER_ID, SOURCE_PACKAGE)); 1512 assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS, 1513 mQuotaController.getTimeUntilQuotaConsumedLocked( 1514 SOURCE_USER_ID, SOURCE_PACKAGE)); 1515 } 1516 } 1517 1518 /** 1519 * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket 1520 * window. 1521 */ 1522 @Test testGetTimeUntilQuotaConsumedLocked_BucketWindow()1523 public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() { 1524 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1525 // Close to RARE boundary. 1526 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1527 createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), 1528 30 * SECOND_IN_MILLIS, 5), false); 1529 // Far away from FREQUENT boundary. 1530 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1531 createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 1532 // Overlap WORKING_SET boundary. 1533 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1534 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 1535 3 * MINUTE_IN_MILLIS, 5), false); 1536 // Close to ACTIVE boundary. 1537 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1538 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 1539 1540 setStandbyBucket(RARE_INDEX); 1541 synchronized (mQuotaController.mLock) { 1542 assertEquals(30 * SECOND_IN_MILLIS, 1543 mQuotaController.getRemainingExecutionTimeLocked( 1544 SOURCE_USER_ID, SOURCE_PACKAGE)); 1545 assertEquals(MINUTE_IN_MILLIS, 1546 mQuotaController.getTimeUntilQuotaConsumedLocked( 1547 SOURCE_USER_ID, SOURCE_PACKAGE)); 1548 } 1549 1550 setStandbyBucket(FREQUENT_INDEX); 1551 synchronized (mQuotaController.mLock) { 1552 assertEquals(MINUTE_IN_MILLIS, 1553 mQuotaController.getRemainingExecutionTimeLocked( 1554 SOURCE_USER_ID, SOURCE_PACKAGE)); 1555 assertEquals(MINUTE_IN_MILLIS, 1556 mQuotaController.getTimeUntilQuotaConsumedLocked( 1557 SOURCE_USER_ID, SOURCE_PACKAGE)); 1558 } 1559 1560 setStandbyBucket(WORKING_INDEX); 1561 synchronized (mQuotaController.mLock) { 1562 assertEquals(5 * MINUTE_IN_MILLIS, 1563 mQuotaController.getRemainingExecutionTimeLocked( 1564 SOURCE_USER_ID, SOURCE_PACKAGE)); 1565 assertEquals(7 * MINUTE_IN_MILLIS, 1566 mQuotaController.getTimeUntilQuotaConsumedLocked( 1567 SOURCE_USER_ID, SOURCE_PACKAGE)); 1568 } 1569 1570 // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the 1571 // max execution time. 1572 setStandbyBucket(ACTIVE_INDEX); 1573 synchronized (mQuotaController.mLock) { 1574 assertEquals(7 * MINUTE_IN_MILLIS, 1575 mQuotaController.getRemainingExecutionTimeLocked( 1576 SOURCE_USER_ID, SOURCE_PACKAGE)); 1577 assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS, 1578 mQuotaController.getTimeUntilQuotaConsumedLocked( 1579 SOURCE_USER_ID, SOURCE_PACKAGE)); 1580 } 1581 } 1582 1583 /** 1584 * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit. 1585 */ 1586 @Test testGetTimeUntilQuotaConsumedLocked_MaxExecution()1587 public void testGetTimeUntilQuotaConsumedLocked_MaxExecution() { 1588 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1589 // Overlap boundary. 1590 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1591 createTimingSession( 1592 now - (24 * HOUR_IN_MILLIS + 8 * MINUTE_IN_MILLIS), 4 * HOUR_IN_MILLIS, 5), 1593 false); 1594 1595 setStandbyBucket(WORKING_INDEX); 1596 synchronized (mQuotaController.mLock) { 1597 assertEquals(8 * MINUTE_IN_MILLIS, 1598 mQuotaController.getRemainingExecutionTimeLocked( 1599 SOURCE_USER_ID, SOURCE_PACKAGE)); 1600 // Max time will phase out, so should use bucket limit. 1601 assertEquals(10 * MINUTE_IN_MILLIS, 1602 mQuotaController.getTimeUntilQuotaConsumedLocked( 1603 SOURCE_USER_ID, SOURCE_PACKAGE)); 1604 } 1605 1606 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1607 // Close to boundary. 1608 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1609 createTimingSession(now - (24 * HOUR_IN_MILLIS - MINUTE_IN_MILLIS), 1610 4 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS, 5), false); 1611 1612 setStandbyBucket(WORKING_INDEX); 1613 synchronized (mQuotaController.mLock) { 1614 assertEquals(5 * MINUTE_IN_MILLIS, 1615 mQuotaController.getRemainingExecutionTimeLocked( 1616 SOURCE_USER_ID, SOURCE_PACKAGE)); 1617 assertEquals(10 * MINUTE_IN_MILLIS, 1618 mQuotaController.getTimeUntilQuotaConsumedLocked( 1619 SOURCE_USER_ID, SOURCE_PACKAGE)); 1620 } 1621 1622 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1623 // Far from boundary. 1624 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1625 createTimingSession( 1626 now - (20 * HOUR_IN_MILLIS), 4 * HOUR_IN_MILLIS - 3 * MINUTE_IN_MILLIS, 5), 1627 false); 1628 1629 setStandbyBucket(WORKING_INDEX); 1630 synchronized (mQuotaController.mLock) { 1631 assertEquals(3 * MINUTE_IN_MILLIS, 1632 mQuotaController.getRemainingExecutionTimeLocked( 1633 SOURCE_USER_ID, SOURCE_PACKAGE)); 1634 assertEquals(3 * MINUTE_IN_MILLIS, 1635 mQuotaController.getTimeUntilQuotaConsumedLocked( 1636 SOURCE_USER_ID, SOURCE_PACKAGE)); 1637 } 1638 } 1639 1640 /** 1641 * Test getTimeUntilQuotaConsumedLocked when the max execution time and bucket window time 1642 * remaining are equal. 1643 */ 1644 @Test testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining()1645 public void testGetTimeUntilQuotaConsumedLocked_EqualTimeRemaining() { 1646 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1647 setStandbyBucket(FREQUENT_INDEX); 1648 1649 // Overlap boundary. 1650 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1651 createTimingSession( 1652 now - (24 * HOUR_IN_MILLIS + 11 * MINUTE_IN_MILLIS), 1653 4 * HOUR_IN_MILLIS, 1654 5), false); 1655 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1656 createTimingSession( 1657 now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), 1658 false); 1659 1660 synchronized (mQuotaController.mLock) { 1661 // Both max and bucket time have 8 minutes left. 1662 assertEquals(8 * MINUTE_IN_MILLIS, 1663 mQuotaController.getRemainingExecutionTimeLocked( 1664 SOURCE_USER_ID, SOURCE_PACKAGE)); 1665 // Max time essentially free. Bucket time has 2 min phase out plus original 8 minute 1666 // window time. 1667 assertEquals(10 * MINUTE_IN_MILLIS, 1668 mQuotaController.getTimeUntilQuotaConsumedLocked( 1669 SOURCE_USER_ID, SOURCE_PACKAGE)); 1670 } 1671 1672 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 1673 // Overlap boundary. 1674 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1675 createTimingSession( 1676 now - (24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 5), 1677 false); 1678 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1679 createTimingSession( 1680 now - (20 * HOUR_IN_MILLIS), 1681 3 * HOUR_IN_MILLIS + 48 * MINUTE_IN_MILLIS, 1682 5), false); 1683 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1684 createTimingSession( 1685 now - (8 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), 1686 false); 1687 1688 synchronized (mQuotaController.mLock) { 1689 // Both max and bucket time have 8 minutes left. 1690 assertEquals(8 * MINUTE_IN_MILLIS, 1691 mQuotaController.getRemainingExecutionTimeLocked( 1692 SOURCE_USER_ID, SOURCE_PACKAGE)); 1693 // Max time only has one minute phase out. Bucket time has 2 minute phase out. 1694 assertEquals(9 * MINUTE_IN_MILLIS, 1695 mQuotaController.getTimeUntilQuotaConsumedLocked( 1696 SOURCE_USER_ID, SOURCE_PACKAGE)); 1697 } 1698 } 1699 1700 /** 1701 * Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size. 1702 */ 1703 @Test testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow()1704 public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow() { 1705 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1706 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1707 createTimingSession(now - (24 * HOUR_IN_MILLIS), 1708 mQcConstants.MAX_EXECUTION_TIME_MS - 10 * MINUTE_IN_MILLIS, 5), 1709 false); 1710 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1711 createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), 1712 false); 1713 1714 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, 1715 10 * MINUTE_IN_MILLIS); 1716 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 10 * MINUTE_IN_MILLIS); 1717 // window size = allowed time, so jobs can essentially run non-stop until they reach the 1718 // max execution time. 1719 setStandbyBucket(EXEMPTED_INDEX); 1720 synchronized (mQuotaController.mLock) { 1721 assertEquals(0, 1722 mQuotaController.getRemainingExecutionTimeLocked( 1723 SOURCE_USER_ID, SOURCE_PACKAGE)); 1724 assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 10 * MINUTE_IN_MILLIS, 1725 mQuotaController.getTimeUntilQuotaConsumedLocked( 1726 SOURCE_USER_ID, SOURCE_PACKAGE)); 1727 } 1728 } 1729 1730 /** 1731 * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket 1732 * window and the session is rolling out of the window. 1733 */ 1734 @Test testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow()1735 public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow() { 1736 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1737 1738 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1739 createTimingSession(now - (24 * HOUR_IN_MILLIS), 1740 10 * MINUTE_IN_MILLIS, 5), false); 1741 setStandbyBucket(RARE_INDEX); 1742 synchronized (mQuotaController.mLock) { 1743 assertEquals(0, 1744 mQuotaController.getRemainingExecutionTimeLocked( 1745 SOURCE_USER_ID, SOURCE_PACKAGE)); 1746 assertEquals(10 * MINUTE_IN_MILLIS, 1747 mQuotaController.getTimeUntilQuotaConsumedLocked( 1748 SOURCE_USER_ID, SOURCE_PACKAGE)); 1749 } 1750 1751 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1752 createTimingSession(now - (8 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); 1753 setStandbyBucket(FREQUENT_INDEX); 1754 synchronized (mQuotaController.mLock) { 1755 assertEquals(0, 1756 mQuotaController.getRemainingExecutionTimeLocked( 1757 SOURCE_USER_ID, SOURCE_PACKAGE)); 1758 assertEquals(10 * MINUTE_IN_MILLIS, 1759 mQuotaController.getTimeUntilQuotaConsumedLocked( 1760 SOURCE_USER_ID, SOURCE_PACKAGE)); 1761 } 1762 1763 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1764 createTimingSession(now - (2 * HOUR_IN_MILLIS), 1765 10 * MINUTE_IN_MILLIS, 5), false); 1766 setStandbyBucket(WORKING_INDEX); 1767 synchronized (mQuotaController.mLock) { 1768 assertEquals(0, 1769 mQuotaController.getRemainingExecutionTimeLocked( 1770 SOURCE_USER_ID, SOURCE_PACKAGE)); 1771 assertEquals(10 * MINUTE_IN_MILLIS, 1772 mQuotaController.getTimeUntilQuotaConsumedLocked( 1773 SOURCE_USER_ID, SOURCE_PACKAGE)); 1774 } 1775 1776 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1777 createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), 1778 false); 1779 // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the 1780 // max execution time. 1781 setStandbyBucket(ACTIVE_INDEX); 1782 synchronized (mQuotaController.mLock) { 1783 assertEquals(0, 1784 mQuotaController.getRemainingExecutionTimeLocked( 1785 SOURCE_USER_ID, SOURCE_PACKAGE)); 1786 assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS, 1787 mQuotaController.getTimeUntilQuotaConsumedLocked( 1788 SOURCE_USER_ID, SOURCE_PACKAGE)); 1789 } 1790 } 1791 1792 /** 1793 * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket 1794 * window and there are valid QuotaBumps in the history. 1795 */ 1796 @Test testGetTimeUntilQuotaConsumedLocked_QuotaBump()1797 public void testGetTimeUntilQuotaConsumedLocked_QuotaBump() { 1798 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS); 1799 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); 1800 1801 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1802 // Close to RARE boundary. 1803 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1804 createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), 1805 30 * SECOND_IN_MILLIS, 5), false); 1806 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) 1807 .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS)); 1808 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) 1809 .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS)); 1810 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) 1811 .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS)); 1812 // Far away from FREQUENT boundary. 1813 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1814 createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 1815 // Overlap WORKING_SET boundary. 1816 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) 1817 .add(new QuotaBump(now - 2 * HOUR_IN_MILLIS)); 1818 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1819 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 1820 3 * MINUTE_IN_MILLIS, 5), false); 1821 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) 1822 .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS)); 1823 // Close to ACTIVE boundary. 1824 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1825 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 1826 1827 setStandbyBucket(RARE_INDEX); 1828 synchronized (mQuotaController.mLock) { 1829 assertEquals(3 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, 1830 mQuotaController.getRemainingExecutionTimeLocked( 1831 SOURCE_USER_ID, SOURCE_PACKAGE)); 1832 assertEquals(4 * MINUTE_IN_MILLIS, 1833 mQuotaController.getTimeUntilQuotaConsumedLocked( 1834 SOURCE_USER_ID, SOURCE_PACKAGE)); 1835 } 1836 1837 setStandbyBucket(FREQUENT_INDEX); 1838 synchronized (mQuotaController.mLock) { 1839 assertEquals(4 * MINUTE_IN_MILLIS, 1840 mQuotaController.getRemainingExecutionTimeLocked( 1841 SOURCE_USER_ID, SOURCE_PACKAGE)); 1842 assertEquals(4 * MINUTE_IN_MILLIS, 1843 mQuotaController.getTimeUntilQuotaConsumedLocked( 1844 SOURCE_USER_ID, SOURCE_PACKAGE)); 1845 } 1846 1847 setStandbyBucket(WORKING_INDEX); 1848 synchronized (mQuotaController.mLock) { 1849 assertEquals(8 * MINUTE_IN_MILLIS, 1850 mQuotaController.getRemainingExecutionTimeLocked( 1851 SOURCE_USER_ID, SOURCE_PACKAGE)); 1852 assertEquals(10 * MINUTE_IN_MILLIS, 1853 mQuotaController.getTimeUntilQuotaConsumedLocked( 1854 SOURCE_USER_ID, SOURCE_PACKAGE)); 1855 } 1856 1857 // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the 1858 // max execution time. 1859 setStandbyBucket(ACTIVE_INDEX); 1860 synchronized (mQuotaController.mLock) { 1861 assertEquals(10 * MINUTE_IN_MILLIS, 1862 mQuotaController.getRemainingExecutionTimeLocked( 1863 SOURCE_USER_ID, SOURCE_PACKAGE)); 1864 assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS, 1865 mQuotaController.getTimeUntilQuotaConsumedLocked( 1866 SOURCE_USER_ID, SOURCE_PACKAGE)); 1867 } 1868 } 1869 1870 /** 1871 * Test getTimeUntilQuotaConsumedLocked when there are valid QuotaBumps in recent history that 1872 * provide enough additional quota to bridge gaps between sessions. 1873 */ 1874 @Test testGetTimeUntilQuotaConsumedLocked_QuotaBump_CrucialBumps()1875 public void testGetTimeUntilQuotaConsumedLocked_QuotaBump_CrucialBumps() { 1876 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS); 1877 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); 1878 1879 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1880 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1881 createTimingSession(now - (25 * HOUR_IN_MILLIS), 1882 30 * MINUTE_IN_MILLIS, 25), false); 1883 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) 1884 .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS)); 1885 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) 1886 .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS)); 1887 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) 1888 .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS)); 1889 // Without the valid quota bumps, the app would only 3 minutes until the quota was consumed. 1890 // The quota bumps provide enough quota to bridge the gap between the two earliest sessions. 1891 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1892 createTimingSession(now - (8 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false); 1893 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1894 createTimingSession(now - (8 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS), 1895 2 * MINUTE_IN_MILLIS, 5), false); 1896 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1897 createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 1898 3 * MINUTE_IN_MILLIS, 1), false); 1899 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) 1900 .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS)); 1901 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 1902 createTimingSession(now - (9 * MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 1), false); 1903 1904 setStandbyBucket(FREQUENT_INDEX); 1905 synchronized (mQuotaController.mLock) { 1906 assertEquals(2 * MINUTE_IN_MILLIS, 1907 mQuotaController.getRemainingExecutionTimeLocked( 1908 SOURCE_USER_ID, SOURCE_PACKAGE)); 1909 assertEquals(7 * MINUTE_IN_MILLIS, 1910 mQuotaController.getTimeUntilQuotaConsumedLocked( 1911 SOURCE_USER_ID, SOURCE_PACKAGE)); 1912 } 1913 } 1914 1915 @Test testIsWithinQuotaLocked_NeverApp()1916 public void testIsWithinQuotaLocked_NeverApp() { 1917 synchronized (mQuotaController.mLock) { 1918 assertFalse( 1919 mQuotaController.isWithinQuotaLocked(0, "com.android.test.never", NEVER_INDEX)); 1920 } 1921 } 1922 1923 @Test testIsWithinQuotaLocked_Charging()1924 public void testIsWithinQuotaLocked_Charging() { 1925 setCharging(); 1926 synchronized (mQuotaController.mLock) { 1927 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX)); 1928 } 1929 } 1930 1931 @Test testIsWithinQuotaLocked_UnderDuration_UnderJobCount()1932 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount() { 1933 setDischarging(); 1934 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1935 mQuotaController.saveTimingSession(0, "com.android.test", 1936 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 1937 mQuotaController.saveTimingSession(0, "com.android.test", 1938 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 1939 synchronized (mQuotaController.mLock) { 1940 mQuotaController.incrementJobCountLocked(0, "com.android.test", 5); 1941 assertTrue(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); 1942 } 1943 } 1944 1945 @Test testIsWithinQuotaLocked_UnderDuration_OverJobCount()1946 public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() { 1947 setDischarging(); 1948 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1949 final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; 1950 mQuotaController.saveTimingSession(0, "com.android.test.spam", 1951 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25), false); 1952 mQuotaController.saveTimingSession(0, "com.android.test.spam", 1953 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount), 1954 false); 1955 synchronized (mQuotaController.mLock) { 1956 mQuotaController.incrementJobCountLocked(0, "com.android.test.spam", jobCount); 1957 assertFalse(mQuotaController.isWithinQuotaLocked( 1958 0, "com.android.test.spam", WORKING_INDEX)); 1959 } 1960 1961 mQuotaController.saveTimingSession(0, "com.android.test.frequent", 1962 createTimingSession(now - (2 * HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 2000), 1963 false); 1964 mQuotaController.saveTimingSession(0, "com.android.test.frequent", 1965 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 500), false); 1966 synchronized (mQuotaController.mLock) { 1967 assertFalse(mQuotaController.isWithinQuotaLocked( 1968 0, "com.android.test.frequent", FREQUENT_INDEX)); 1969 } 1970 } 1971 1972 @Test testIsWithinQuotaLocked_OverDuration_UnderJobCount()1973 public void testIsWithinQuotaLocked_OverDuration_UnderJobCount() { 1974 setDischarging(); 1975 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1976 mQuotaController.saveTimingSession(0, "com.android.test", 1977 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 1978 mQuotaController.saveTimingSession(0, "com.android.test", 1979 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); 1980 mQuotaController.saveTimingSession(0, "com.android.test", 1981 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), false); 1982 synchronized (mQuotaController.mLock) { 1983 mQuotaController.incrementJobCountLocked(0, "com.android.test", 5); 1984 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); 1985 } 1986 } 1987 1988 @Test testIsWithinQuotaLocked_OverDuration_OverJobCount()1989 public void testIsWithinQuotaLocked_OverDuration_OverJobCount() { 1990 setDischarging(); 1991 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 1992 final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; 1993 mQuotaController.saveTimingSession(0, "com.android.test", 1994 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25), false); 1995 mQuotaController.saveTimingSession(0, "com.android.test", 1996 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount), 1997 false); 1998 synchronized (mQuotaController.mLock) { 1999 mQuotaController.incrementJobCountLocked(0, "com.android.test", jobCount); 2000 assertFalse(mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); 2001 } 2002 } 2003 2004 @Test testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS()2005 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS() { 2006 setDischarging(); 2007 2008 JobStatus jobStatus = createJobStatus( 2009 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_BelowFGS", 1); 2010 setStandbyBucket(ACTIVE_INDEX, jobStatus); 2011 setProcessState(ActivityManager.PROCESS_STATE_BACKUP); 2012 2013 synchronized (mQuotaController.mLock) { 2014 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2015 mQuotaController.prepareForExecutionLocked(jobStatus); 2016 } 2017 for (int i = 0; i < 20; ++i) { 2018 advanceElapsedClock(SECOND_IN_MILLIS); 2019 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 2020 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 2021 } 2022 synchronized (mQuotaController.mLock) { 2023 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 2024 } 2025 2026 advanceElapsedClock(15 * SECOND_IN_MILLIS); 2027 2028 synchronized (mQuotaController.mLock) { 2029 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2030 mQuotaController.prepareForExecutionLocked(jobStatus); 2031 } 2032 for (int i = 0; i < 20; ++i) { 2033 advanceElapsedClock(SECOND_IN_MILLIS); 2034 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 2035 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 2036 } 2037 synchronized (mQuotaController.mLock) { 2038 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 2039 } 2040 2041 advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS); 2042 2043 synchronized (mQuotaController.mLock) { 2044 assertEquals(2, mQuotaController.getExecutionStatsLocked( 2045 SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX).jobCountInRateLimitingWindow); 2046 assertTrue(mQuotaController.isWithinQuotaLocked(jobStatus)); 2047 assertTrue(jobStatus.isReady()); 2048 } 2049 } 2050 2051 @Test testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps()2052 public void testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps() 2053 throws Exception { 2054 setDischarging(); 2055 2056 final String unaffectedPkgName = "com.android.unaffected"; 2057 final int unaffectedUid = 10987; 2058 JobInfo unaffectedJobInfo = new JobInfo.Builder(1, 2059 new ComponentName(unaffectedPkgName, "foo")) 2060 .build(); 2061 JobStatus unaffected = createJobStatus( 2062 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps", 2063 unaffectedPkgName, unaffectedUid, unaffectedJobInfo); 2064 setStandbyBucket(FREQUENT_INDEX, unaffected); 2065 setProcessState(ActivityManager.PROCESS_STATE_SERVICE, unaffectedUid); 2066 2067 final String fgChangerPkgName = "com.android.foreground.changer"; 2068 final int fgChangerUid = 10234; 2069 JobInfo fgChangerJobInfo = new JobInfo.Builder(2, 2070 new ComponentName(fgChangerPkgName, "foo")) 2071 .build(); 2072 JobStatus fgStateChanger = createJobStatus( 2073 "testIsWithinQuotaLocked_UnderDuration_UnderJobCount_MultiStateChange_SeparateApps", 2074 fgChangerPkgName, fgChangerUid, fgChangerJobInfo); 2075 setStandbyBucket(ACTIVE_INDEX, fgStateChanger); 2076 setProcessState(ActivityManager.PROCESS_STATE_BACKUP, fgChangerUid); 2077 2078 doReturn(new ArraySet<>(new String[]{unaffectedPkgName})) 2079 .when(mJobSchedulerService).getPackagesForUidLocked(unaffectedUid); 2080 doReturn(new ArraySet<>(new String[]{fgChangerPkgName})) 2081 .when(mJobSchedulerService).getPackagesForUidLocked(fgChangerUid); 2082 2083 synchronized (mQuotaController.mLock) { 2084 mQuotaController.maybeStartTrackingJobLocked(unaffected, null); 2085 mQuotaController.prepareForExecutionLocked(unaffected); 2086 2087 mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null); 2088 mQuotaController.prepareForExecutionLocked(fgStateChanger); 2089 } 2090 for (int i = 0; i < 20; ++i) { 2091 advanceElapsedClock(SECOND_IN_MILLIS); 2092 setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid); 2093 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid); 2094 } 2095 synchronized (mQuotaController.mLock) { 2096 mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null); 2097 } 2098 2099 advanceElapsedClock(15 * SECOND_IN_MILLIS); 2100 2101 synchronized (mQuotaController.mLock) { 2102 mQuotaController.maybeStartTrackingJobLocked(fgStateChanger, null); 2103 mQuotaController.prepareForExecutionLocked(fgStateChanger); 2104 } 2105 for (int i = 0; i < 20; ++i) { 2106 advanceElapsedClock(SECOND_IN_MILLIS); 2107 setProcessState(ActivityManager.PROCESS_STATE_TOP, fgChangerUid); 2108 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid); 2109 } 2110 synchronized (mQuotaController.mLock) { 2111 mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null); 2112 2113 mQuotaController.maybeStopTrackingJobLocked(unaffected, null); 2114 2115 assertTrue(mQuotaController.isWithinQuotaLocked(unaffected)); 2116 assertTrue(unaffected.isReady()); 2117 assertFalse(mQuotaController.isWithinQuotaLocked(fgStateChanger)); 2118 assertFalse(fgStateChanger.isReady()); 2119 } 2120 assertEquals(1, 2121 mQuotaController.getTimingSessions(SOURCE_USER_ID, unaffectedPkgName).size()); 2122 assertEquals(42, 2123 mQuotaController.getTimingSessions(SOURCE_USER_ID, fgChangerPkgName).size()); 2124 synchronized (mQuotaController.mLock) { 2125 for (int i = ACTIVE_INDEX; i < RARE_INDEX; ++i) { 2126 assertEquals(42, mQuotaController.getExecutionStatsLocked( 2127 SOURCE_USER_ID, fgChangerPkgName, i).jobCountInRateLimitingWindow); 2128 assertEquals(1, mQuotaController.getExecutionStatsLocked( 2129 SOURCE_USER_ID, unaffectedPkgName, i).jobCountInRateLimitingWindow); 2130 } 2131 } 2132 } 2133 2134 @Test testIsWithinQuotaLocked_TimingSession()2135 public void testIsWithinQuotaLocked_TimingSession() { 2136 setDischarging(); 2137 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2138 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 3); 2139 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 4); 2140 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 5); 2141 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 6); 2142 2143 for (int i = 0; i < 7; ++i) { 2144 mQuotaController.saveTimingSession(0, "com.android.test", 2145 createTimingSession(now - ((10 - i) * MINUTE_IN_MILLIS), 30 * SECOND_IN_MILLIS, 2146 2), false); 2147 2148 synchronized (mQuotaController.mLock) { 2149 mQuotaController.incrementJobCountLocked(0, "com.android.test", 2); 2150 2151 assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions", 2152 i < 2, 2153 mQuotaController.isWithinQuotaLocked(0, "com.android.test", RARE_INDEX)); 2154 assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions", 2155 i < 3, 2156 mQuotaController.isWithinQuotaLocked( 2157 0, "com.android.test", FREQUENT_INDEX)); 2158 assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions", 2159 i < 4, 2160 mQuotaController.isWithinQuotaLocked(0, "com.android.test", WORKING_INDEX)); 2161 assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions", 2162 i < 5, 2163 mQuotaController.isWithinQuotaLocked(0, "com.android.test", ACTIVE_INDEX)); 2164 } 2165 } 2166 } 2167 2168 @Test testIsWithinQuotaLocked_UserInitiated()2169 public void testIsWithinQuotaLocked_UserInitiated() { 2170 // Put app in a state where regular jobs are out of quota. 2171 setDischarging(); 2172 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2173 final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; 2174 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2175 createTimingSession(now - (HOUR_IN_MILLIS), 15 * MINUTE_IN_MILLIS, 25), false); 2176 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2177 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, jobCount), 2178 false); 2179 JobStatus job = createJobStatus("testIsWithinQuotaLocked_UserInitiated", 1); 2180 spyOn(job); 2181 synchronized (mQuotaController.mLock) { 2182 mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, jobCount); 2183 assertFalse(mQuotaController 2184 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2185 doReturn(false).when(job).shouldTreatAsUserInitiatedJob(); 2186 assertFalse(mQuotaController.isWithinQuotaLocked(job)); 2187 // User-initiated job should still be allowed. 2188 doReturn(true).when(job).shouldTreatAsUserInitiatedJob(); 2189 assertTrue(mQuotaController.isWithinQuotaLocked(job)); 2190 } 2191 } 2192 2193 @Test testIsWithinQuotaLocked_WithQuotaBump_Duration()2194 public void testIsWithinQuotaLocked_WithQuotaBump_Duration() { 2195 setDischarging(); 2196 int standbyBucket = WORKING_INDEX; 2197 setStandbyBucket(standbyBucket); 2198 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 2199 5 * MINUTE_IN_MILLIS); 2200 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10); 2201 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS); 2202 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0); 2203 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0); 2204 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); 2205 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5); 2206 2207 long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2208 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2209 createTimingSession( 2210 now - (HOUR_IN_MILLIS - 2 * MINUTE_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1), 2211 false); 2212 final ExecutionStats stats; 2213 synchronized (mQuotaController.mLock) { 2214 stats = mQuotaController.getExecutionStatsLocked( 2215 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2216 mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1); 2217 assertFalse(mQuotaController 2218 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2219 assertEquals(5 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); 2220 } 2221 mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); 2222 synchronized (mQuotaController.mLock) { 2223 assertTrue(mQuotaController 2224 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2225 assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); 2226 } 2227 2228 advanceElapsedClock(HOUR_IN_MILLIS); 2229 2230 synchronized (mQuotaController.mLock) { 2231 assertTrue(mQuotaController 2232 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2233 assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); 2234 } 2235 2236 // Emulate a quota bump while some jobs are executing 2237 JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 1); 2238 JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 2); 2239 2240 synchronized (mQuotaController.mLock) { 2241 mQuotaController.maybeStartTrackingJobLocked(job1, null); 2242 mQuotaController.prepareForExecutionLocked(job1); 2243 } 2244 2245 advanceElapsedClock(MINUTE_IN_MILLIS); 2246 mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); 2247 synchronized (mQuotaController.mLock) { 2248 assertTrue(mQuotaController 2249 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2250 assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); 2251 mQuotaController.maybeStartTrackingJobLocked(job2, null); 2252 mQuotaController.prepareForExecutionLocked(job2); 2253 } 2254 2255 advanceElapsedClock(MINUTE_IN_MILLIS); 2256 synchronized (mQuotaController.mLock) { 2257 mQuotaController.maybeStopTrackingJobLocked(job1, null); 2258 mQuotaController.maybeStopTrackingJobLocked(job2, null); 2259 assertFalse(mQuotaController 2260 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2261 assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); 2262 } 2263 2264 // Phase out the first session 2265 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 2266 synchronized (mQuotaController.mLock) { 2267 assertTrue(mQuotaController 2268 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2269 assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); 2270 } 2271 2272 // Phase out the first quota bump 2273 advanceElapsedClock(7 * HOUR_IN_MILLIS); 2274 synchronized (mQuotaController.mLock) { 2275 assertTrue(mQuotaController 2276 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2277 assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); 2278 } 2279 } 2280 2281 @Test testIsWithinQuotaLocked_WithQuotaBump_JobCount()2282 public void testIsWithinQuotaLocked_WithQuotaBump_JobCount() { 2283 setDischarging(); 2284 int standbyBucket = WORKING_INDEX; 2285 setStandbyBucket(standbyBucket); 2286 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 2287 20 * MINUTE_IN_MILLIS); 2288 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10); 2289 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0); 2290 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 1); 2291 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0); 2292 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); 2293 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5); 2294 2295 long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2296 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2297 createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 10), false); 2298 final ExecutionStats stats; 2299 synchronized (mQuotaController.mLock) { 2300 stats = mQuotaController.getExecutionStatsLocked( 2301 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2302 mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 10); 2303 assertFalse(mQuotaController 2304 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2305 assertEquals(10, stats.jobCountLimit); 2306 } 2307 mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); 2308 synchronized (mQuotaController.mLock) { 2309 assertTrue(mQuotaController 2310 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2311 assertEquals(11, stats.jobCountLimit); 2312 } 2313 2314 advanceElapsedClock(HOUR_IN_MILLIS); 2315 2316 synchronized (mQuotaController.mLock) { 2317 assertTrue(mQuotaController 2318 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2319 assertEquals(11, stats.jobCountLimit); 2320 } 2321 2322 // Emulate a quota bump while some jobs are executing 2323 JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1); 2324 JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2); 2325 2326 synchronized (mQuotaController.mLock) { 2327 mQuotaController.maybeStartTrackingJobLocked(job1, null); 2328 mQuotaController.prepareForExecutionLocked(job1); 2329 } 2330 2331 advanceElapsedClock(MINUTE_IN_MILLIS); 2332 mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); 2333 synchronized (mQuotaController.mLock) { 2334 assertTrue(mQuotaController 2335 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2336 assertEquals(12, stats.jobCountLimit); 2337 mQuotaController.maybeStartTrackingJobLocked(job2, null); 2338 mQuotaController.prepareForExecutionLocked(job2); 2339 } 2340 2341 advanceElapsedClock(MINUTE_IN_MILLIS); 2342 synchronized (mQuotaController.mLock) { 2343 mQuotaController.maybeStopTrackingJobLocked(job1, null); 2344 mQuotaController.maybeStopTrackingJobLocked(job2, null); 2345 assertFalse(mQuotaController 2346 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2347 assertEquals(12, stats.jobCountLimit); 2348 } 2349 2350 // Phase out the first session 2351 advanceElapsedClock(3 * MINUTE_IN_MILLIS); 2352 synchronized (mQuotaController.mLock) { 2353 assertTrue(mQuotaController 2354 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2355 assertEquals(12, stats.jobCountLimit); 2356 } 2357 2358 // Phase out the first quota bump 2359 advanceElapsedClock(7 * HOUR_IN_MILLIS); 2360 synchronized (mQuotaController.mLock) { 2361 assertTrue(mQuotaController 2362 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2363 assertEquals(11, stats.jobCountLimit); 2364 } 2365 } 2366 2367 @Test testIsWithinQuotaLocked_WithQuotaBump_SessionCount()2368 public void testIsWithinQuotaLocked_WithQuotaBump_SessionCount() { 2369 setDischarging(); 2370 int standbyBucket = WORKING_INDEX; 2371 setStandbyBucket(standbyBucket); 2372 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 2373 20 * MINUTE_IN_MILLIS); 2374 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 2); 2375 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0); 2376 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0); 2377 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 1); 2378 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); 2379 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5); 2380 2381 long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2382 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2383 createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1), false); 2384 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2385 createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), false); 2386 final ExecutionStats stats; 2387 synchronized (mQuotaController.mLock) { 2388 stats = mQuotaController.getExecutionStatsLocked( 2389 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2390 assertFalse(mQuotaController 2391 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2392 assertEquals(2, stats.sessionCountLimit); 2393 } 2394 mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); 2395 synchronized (mQuotaController.mLock) { 2396 assertTrue(mQuotaController 2397 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2398 assertEquals(3, stats.sessionCountLimit); 2399 } 2400 2401 advanceElapsedClock(HOUR_IN_MILLIS); 2402 2403 synchronized (mQuotaController.mLock) { 2404 assertTrue(mQuotaController 2405 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2406 assertEquals(3, stats.sessionCountLimit); 2407 } 2408 2409 // Emulate a quota bump while some jobs are executing 2410 JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1); 2411 JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2); 2412 2413 synchronized (mQuotaController.mLock) { 2414 mQuotaController.maybeStartTrackingJobLocked(job1, null); 2415 mQuotaController.prepareForExecutionLocked(job1); 2416 } 2417 2418 advanceElapsedClock(MINUTE_IN_MILLIS); 2419 mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); 2420 synchronized (mQuotaController.mLock) { 2421 mQuotaController.maybeStopTrackingJobLocked(job1, null); 2422 assertTrue(mQuotaController 2423 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2424 assertEquals(4, stats.sessionCountLimit); 2425 } 2426 2427 advanceElapsedClock(MINUTE_IN_MILLIS); 2428 synchronized (mQuotaController.mLock) { 2429 mQuotaController.maybeStartTrackingJobLocked(job2, null); 2430 mQuotaController.prepareForExecutionLocked(job2); 2431 } 2432 2433 advanceElapsedClock(MINUTE_IN_MILLIS); 2434 synchronized (mQuotaController.mLock) { 2435 mQuotaController.maybeStopTrackingJobLocked(job2, null); 2436 assertFalse(mQuotaController 2437 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2438 assertEquals(4, stats.sessionCountLimit); 2439 } 2440 2441 // Phase out the first session 2442 advanceElapsedClock(2 * MINUTE_IN_MILLIS); 2443 synchronized (mQuotaController.mLock) { 2444 assertTrue(mQuotaController 2445 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2446 assertEquals(4, stats.sessionCountLimit); 2447 } 2448 2449 // Phase out the first quota bump 2450 advanceElapsedClock(7 * HOUR_IN_MILLIS); 2451 synchronized (mQuotaController.mLock) { 2452 assertTrue(mQuotaController 2453 .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); 2454 assertEquals(3, stats.sessionCountLimit); 2455 } 2456 } 2457 2458 @Test testIsWithinEJQuotaLocked_NeverApp()2459 public void testIsWithinEJQuotaLocked_NeverApp() { 2460 JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_NeverApp", 1); 2461 setStandbyBucket(NEVER_INDEX, js); 2462 synchronized (mQuotaController.mLock) { 2463 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2464 } 2465 } 2466 2467 @Test testIsWithinEJQuotaLocked_Charging()2468 public void testIsWithinEJQuotaLocked_Charging() { 2469 setCharging(); 2470 JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_Charging", 1); 2471 synchronized (mQuotaController.mLock) { 2472 assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); 2473 } 2474 } 2475 2476 @Test testIsWithinEJQuotaLocked_UnderDuration()2477 public void testIsWithinEJQuotaLocked_UnderDuration() { 2478 setDischarging(); 2479 JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_UnderDuration", 1); 2480 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2481 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2482 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2483 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2484 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2485 synchronized (mQuotaController.mLock) { 2486 assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); 2487 } 2488 } 2489 2490 @Test testIsWithinEJQuotaLocked_OverDuration()2491 public void testIsWithinEJQuotaLocked_OverDuration() { 2492 setDischarging(); 2493 JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_OverDuration", 1); 2494 setStandbyBucket(FREQUENT_INDEX, js); 2495 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS); 2496 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2497 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2498 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2499 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2500 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2501 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2502 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true); 2503 synchronized (mQuotaController.mLock) { 2504 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2505 } 2506 } 2507 2508 @Test testIsWithinEJQuotaLocked_TimingSession()2509 public void testIsWithinEJQuotaLocked_TimingSession() { 2510 setDischarging(); 2511 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 2512 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2513 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS); 2514 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS); 2515 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS); 2516 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); 2517 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 8 * MINUTE_IN_MILLIS); 2518 2519 JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TimingSession", 1); 2520 for (int i = 0; i < 25; ++i) { 2521 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2522 createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 2523 2), true); 2524 2525 synchronized (mQuotaController.mLock) { 2526 setStandbyBucket(ACTIVE_INDEX, js); 2527 assertEquals("Active has incorrect quota status with " + (i + 1) + " sessions", 2528 i < 19, mQuotaController.isWithinEJQuotaLocked(js)); 2529 2530 setStandbyBucket(WORKING_INDEX, js); 2531 assertEquals("Working has incorrect quota status with " + (i + 1) + " sessions", 2532 i < 14, mQuotaController.isWithinEJQuotaLocked(js)); 2533 2534 setStandbyBucket(FREQUENT_INDEX, js); 2535 assertEquals("Frequent has incorrect quota status with " + (i + 1) + " sessions", 2536 i < 12, mQuotaController.isWithinEJQuotaLocked(js)); 2537 2538 setStandbyBucket(RARE_INDEX, js); 2539 assertEquals("Rare has incorrect quota status with " + (i + 1) + " sessions", 2540 i < 9, mQuotaController.isWithinEJQuotaLocked(js)); 2541 2542 setStandbyBucket(RESTRICTED_INDEX, js); 2543 assertEquals("Restricted has incorrect quota status with " + (i + 1) + " sessions", 2544 i < 7, mQuotaController.isWithinEJQuotaLocked(js)); 2545 } 2546 } 2547 } 2548 2549 /** 2550 * Tests that Timers properly track sessions when an app is added and removed from the temp 2551 * allowlist. 2552 */ 2553 @Test testIsWithinEJQuotaLocked_TempAllowlisting()2554 public void testIsWithinEJQuotaLocked_TempAllowlisting() { 2555 setDischarging(); 2556 JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting", 1); 2557 setStandbyBucket(FREQUENT_INDEX, js); 2558 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS); 2559 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2560 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2561 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2562 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2563 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2564 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2565 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true); 2566 synchronized (mQuotaController.mLock) { 2567 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2568 } 2569 2570 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 2571 final long gracePeriodMs = 15 * SECOND_IN_MILLIS; 2572 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs); 2573 Handler handler = mQuotaController.getHandler(); 2574 spyOn(handler); 2575 2576 // Apps on the temp allowlist should be able to schedule & start EJs, even if they're out 2577 // of quota (as long as they are in the temp allowlist grace period). 2578 mTempAllowlistListener.onAppAdded(mSourceUid); 2579 synchronized (mQuotaController.mLock) { 2580 assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); 2581 } 2582 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2583 mTempAllowlistListener.onAppRemoved(mSourceUid); 2584 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2585 // Still in grace period 2586 synchronized (mQuotaController.mLock) { 2587 assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); 2588 } 2589 advanceElapsedClock(6 * SECOND_IN_MILLIS); 2590 // Out of grace period. 2591 synchronized (mQuotaController.mLock) { 2592 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2593 } 2594 } 2595 2596 @Test testIsWithinEJQuotaLocked_TempAllowlisting_Restricted()2597 public void testIsWithinEJQuotaLocked_TempAllowlisting_Restricted() { 2598 setDischarging(); 2599 JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TempAllowlisting_Restricted", 1); 2600 setStandbyBucket(RESTRICTED_INDEX, js); 2601 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS); 2602 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2603 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2604 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2605 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2606 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2607 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2608 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true); 2609 synchronized (mQuotaController.mLock) { 2610 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2611 } 2612 2613 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 2614 final long gracePeriodMs = 15 * SECOND_IN_MILLIS; 2615 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs); 2616 Handler handler = mQuotaController.getHandler(); 2617 spyOn(handler); 2618 2619 // The temp allowlist should not enable RESTRICTED apps' to schedule & start EJs if they're 2620 // out of quota. 2621 mTempAllowlistListener.onAppAdded(mSourceUid); 2622 synchronized (mQuotaController.mLock) { 2623 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2624 } 2625 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2626 mTempAllowlistListener.onAppRemoved(mSourceUid); 2627 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2628 // Still in grace period 2629 synchronized (mQuotaController.mLock) { 2630 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2631 } 2632 advanceElapsedClock(6 * SECOND_IN_MILLIS); 2633 // Out of grace period. 2634 synchronized (mQuotaController.mLock) { 2635 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2636 } 2637 } 2638 2639 /** 2640 * Tests that Timers properly track sessions when an app becomes top and is closed. 2641 */ 2642 @Test testIsWithinEJQuotaLocked_TopApp()2643 public void testIsWithinEJQuotaLocked_TopApp() { 2644 setDischarging(); 2645 JobStatus js = createExpeditedJobStatus("testIsWithinEJQuotaLocked_TopApp", 1); 2646 setStandbyBucket(FREQUENT_INDEX, js); 2647 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 10 * MINUTE_IN_MILLIS); 2648 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2649 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2650 createTimingSession(now - (HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2651 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2652 createTimingSession(now - (30 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), true); 2653 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2654 createTimingSession(now - (5 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 5), true); 2655 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 2656 synchronized (mQuotaController.mLock) { 2657 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2658 } 2659 2660 final long gracePeriodMs = 15 * SECOND_IN_MILLIS; 2661 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, gracePeriodMs); 2662 Handler handler = mQuotaController.getHandler(); 2663 spyOn(handler); 2664 2665 // Apps on top should be able to schedule & start EJs, even if they're out 2666 // of quota (as long as they are in the top grace period). 2667 setProcessState(ActivityManager.PROCESS_STATE_TOP); 2668 synchronized (mQuotaController.mLock) { 2669 assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); 2670 } 2671 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2672 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 2673 advanceElapsedClock(10 * SECOND_IN_MILLIS); 2674 // Still in grace period 2675 synchronized (mQuotaController.mLock) { 2676 assertTrue(mQuotaController.isWithinEJQuotaLocked(js)); 2677 } 2678 advanceElapsedClock(6 * SECOND_IN_MILLIS); 2679 // Out of grace period. 2680 synchronized (mQuotaController.mLock) { 2681 assertFalse(mQuotaController.isWithinEJQuotaLocked(js)); 2682 } 2683 } 2684 2685 @Test testMaybeScheduleCleanupAlarmLocked()2686 public void testMaybeScheduleCleanupAlarmLocked() { 2687 // No sessions saved yet. 2688 synchronized (mQuotaController.mLock) { 2689 mQuotaController.maybeScheduleCleanupAlarmLocked(); 2690 } 2691 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any()); 2692 2693 // Test with only one timing session saved. 2694 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2695 final long end = now - (6 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); 2696 mQuotaController.saveTimingSession(0, "com.android.test", 2697 new TimingSession(now - 6 * HOUR_IN_MILLIS, end, 1), false); 2698 synchronized (mQuotaController.mLock) { 2699 mQuotaController.maybeScheduleCleanupAlarmLocked(); 2700 } 2701 verify(mAlarmManager, times(1)) 2702 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); 2703 2704 // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again. 2705 mQuotaController.saveTimingSession(0, "com.android.test", 2706 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); 2707 mQuotaController.saveTimingSession(0, "com.android.test", 2708 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false); 2709 synchronized (mQuotaController.mLock) { 2710 mQuotaController.maybeScheduleCleanupAlarmLocked(); 2711 } 2712 verify(mAlarmManager, times(1)) 2713 .set(anyInt(), eq(end + 24 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); 2714 } 2715 2716 @Test testMaybeScheduleStartAlarmLocked_Active()2717 public void testMaybeScheduleStartAlarmLocked_Active() { 2718 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 2719 // because it schedules an alarm too. Prevent it from doing so. 2720 spyOn(mQuotaController); 2721 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 2722 2723 // Active window size is 10 minutes. 2724 final int standbyBucket = ACTIVE_INDEX; 2725 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND); 2726 2727 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Active", 1); 2728 setStandbyBucket(standbyBucket, jobStatus); 2729 synchronized (mQuotaController.mLock) { 2730 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2731 } 2732 2733 // No sessions saved yet. 2734 synchronized (mQuotaController.mLock) { 2735 mQuotaController.maybeScheduleStartAlarmLocked( 2736 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2737 } 2738 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2739 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2740 2741 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2742 // Test with timing sessions out of window but still under max execution limit. 2743 final long expectedAlarmTime = 2744 (now - 18 * HOUR_IN_MILLIS) + 24 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; 2745 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2746 createTimingSession(now - 18 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1), false); 2747 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2748 createTimingSession(now - 12 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1), false); 2749 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2750 createTimingSession(now - 7 * HOUR_IN_MILLIS, HOUR_IN_MILLIS, 1), false); 2751 synchronized (mQuotaController.mLock) { 2752 mQuotaController.maybeScheduleStartAlarmLocked( 2753 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2754 } 2755 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2756 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2757 2758 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2759 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1), false); 2760 synchronized (mQuotaController.mLock) { 2761 mQuotaController.maybeScheduleStartAlarmLocked( 2762 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2763 } 2764 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2765 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2766 2767 synchronized (mQuotaController.mLock) { 2768 mQuotaController.prepareForExecutionLocked(jobStatus); 2769 } 2770 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 2771 synchronized (mQuotaController.mLock) { 2772 // Timer has only been going for 5 minutes in the past 10 minutes, which is under the 2773 // window size limit, but the total execution time for the past 24 hours is 6 hours, so 2774 // the job no longer has quota. 2775 assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked(jobStatus)); 2776 mQuotaController.maybeScheduleStartAlarmLocked( 2777 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2778 } 2779 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 2780 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 2781 any(Handler.class)); 2782 } 2783 2784 @Test testMaybeScheduleStartAlarmLocked_WorkingSet()2785 public void testMaybeScheduleStartAlarmLocked_WorkingSet() { 2786 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 2787 // because it schedules an alarm too. Prevent it from doing so. 2788 spyOn(mQuotaController); 2789 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 2790 2791 // Working set window size is 2 hours. 2792 final int standbyBucket = WORKING_INDEX; 2793 2794 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_WorkingSet", 1); 2795 setStandbyBucket(standbyBucket, jobStatus); 2796 synchronized (mQuotaController.mLock) { 2797 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 2798 // No sessions saved yet. 2799 mQuotaController.maybeScheduleStartAlarmLocked( 2800 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2801 } 2802 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2803 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2804 2805 // Test with timing sessions out of window. 2806 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2807 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2808 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false); 2809 synchronized (mQuotaController.mLock) { 2810 mQuotaController.maybeScheduleStartAlarmLocked( 2811 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2812 } 2813 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2814 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2815 2816 // Test with timing sessions in window but still in quota. 2817 final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); 2818 // Counting backwards, the quota will come back one minute before the end. 2819 final long expectedAlarmTime = 2820 end - MINUTE_IN_MILLIS + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; 2821 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2822 new TimingSession(now - 2 * HOUR_IN_MILLIS, end, 1), false); 2823 synchronized (mQuotaController.mLock) { 2824 mQuotaController.maybeScheduleStartAlarmLocked( 2825 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2826 } 2827 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2828 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2829 2830 // Add some more sessions, but still in quota. 2831 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2832 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); 2833 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2834 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false); 2835 synchronized (mQuotaController.mLock) { 2836 mQuotaController.maybeScheduleStartAlarmLocked( 2837 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2838 } 2839 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2840 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2841 2842 // Test when out of quota. 2843 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2844 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false); 2845 synchronized (mQuotaController.mLock) { 2846 mQuotaController.maybeScheduleStartAlarmLocked( 2847 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2848 } 2849 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 2850 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 2851 any(Handler.class)); 2852 2853 // Alarm already scheduled, so make sure it's not scheduled again. 2854 synchronized (mQuotaController.mLock) { 2855 mQuotaController.maybeScheduleStartAlarmLocked( 2856 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2857 } 2858 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 2859 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 2860 any(Handler.class)); 2861 } 2862 2863 @Test testMaybeScheduleStartAlarmLocked_Frequent()2864 public void testMaybeScheduleStartAlarmLocked_Frequent() { 2865 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 2866 // because it schedules an alarm too. Prevent it from doing so. 2867 spyOn(mQuotaController); 2868 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 2869 2870 synchronized (mQuotaController.mLock) { 2871 mQuotaController.maybeStartTrackingJobLocked( 2872 createJobStatus("testMaybeScheduleStartAlarmLocked_Frequent", 1), null); 2873 } 2874 2875 // Frequent window size is 8 hours. 2876 final int standbyBucket = FREQUENT_INDEX; 2877 2878 // No sessions saved yet. 2879 synchronized (mQuotaController.mLock) { 2880 mQuotaController.maybeScheduleStartAlarmLocked( 2881 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2882 } 2883 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2884 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2885 2886 // Test with timing sessions out of window. 2887 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2888 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2889 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false); 2890 synchronized (mQuotaController.mLock) { 2891 mQuotaController.maybeScheduleStartAlarmLocked( 2892 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2893 } 2894 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2895 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2896 2897 // Test with timing sessions in window but still in quota. 2898 final long start = now - (6 * HOUR_IN_MILLIS); 2899 final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; 2900 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2901 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false); 2902 synchronized (mQuotaController.mLock) { 2903 mQuotaController.maybeScheduleStartAlarmLocked( 2904 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2905 } 2906 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2907 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2908 2909 // Add some more sessions, but still in quota. 2910 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2911 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); 2912 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2913 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false); 2914 synchronized (mQuotaController.mLock) { 2915 mQuotaController.maybeScheduleStartAlarmLocked( 2916 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2917 } 2918 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2919 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2920 2921 // Test when out of quota. 2922 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2923 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); 2924 synchronized (mQuotaController.mLock) { 2925 mQuotaController.maybeScheduleStartAlarmLocked( 2926 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2927 } 2928 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 2929 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 2930 any(Handler.class)); 2931 2932 // Alarm already scheduled, so make sure it's not scheduled again. 2933 synchronized (mQuotaController.mLock) { 2934 mQuotaController.maybeScheduleStartAlarmLocked( 2935 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 2936 } 2937 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 2938 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 2939 any(Handler.class)); 2940 } 2941 2942 /** 2943 * Test that QC handles invalid cases where an app is in the NEVER bucket but has still run 2944 * jobs. 2945 */ 2946 @Test testMaybeScheduleStartAlarmLocked_Never_EffectiveNotNever()2947 public void testMaybeScheduleStartAlarmLocked_Never_EffectiveNotNever() { 2948 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 2949 // because it schedules an alarm too. Prevent it from doing so. 2950 spyOn(mQuotaController); 2951 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 2952 2953 synchronized (mQuotaController.mLock) { 2954 mQuotaController.maybeStartTrackingJobLocked( 2955 createJobStatus("testMaybeScheduleStartAlarmLocked_Never", 1), null); 2956 } 2957 2958 // The app is really in the NEVER bucket but is elevated somehow (eg via uidActive). 2959 setStandbyBucket(NEVER_INDEX); 2960 final int effectiveStandbyBucket = FREQUENT_INDEX; 2961 2962 // No sessions saved yet. 2963 synchronized (mQuotaController.mLock) { 2964 mQuotaController.maybeScheduleStartAlarmLocked( 2965 SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); 2966 } 2967 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2968 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2969 2970 // Test with timing sessions out of window. 2971 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 2972 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2973 createTimingSession(now - 10 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false); 2974 synchronized (mQuotaController.mLock) { 2975 mQuotaController.maybeScheduleStartAlarmLocked( 2976 SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); 2977 } 2978 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2979 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2980 2981 // Test with timing sessions in window but still in quota. 2982 final long start = now - (6 * HOUR_IN_MILLIS); 2983 final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; 2984 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2985 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false); 2986 synchronized (mQuotaController.mLock) { 2987 mQuotaController.maybeScheduleStartAlarmLocked( 2988 SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); 2989 } 2990 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 2991 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 2992 2993 // Add some more sessions, but still in quota. 2994 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2995 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); 2996 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 2997 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false); 2998 synchronized (mQuotaController.mLock) { 2999 mQuotaController.maybeScheduleStartAlarmLocked( 3000 SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); 3001 } 3002 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 3003 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3004 3005 // Test when out of quota. 3006 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3007 createTimingSession(now - HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); 3008 synchronized (mQuotaController.mLock) { 3009 mQuotaController.maybeScheduleStartAlarmLocked( 3010 SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); 3011 } 3012 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3013 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 3014 any(Handler.class)); 3015 3016 // Alarm already scheduled, so make sure it's not scheduled again. 3017 synchronized (mQuotaController.mLock) { 3018 mQuotaController.maybeScheduleStartAlarmLocked( 3019 SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket); 3020 } 3021 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3022 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 3023 any(Handler.class)); 3024 } 3025 3026 @Test testMaybeScheduleStartAlarmLocked_Rare()3027 public void testMaybeScheduleStartAlarmLocked_Rare() { 3028 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 3029 // because it schedules an alarm too. Prevent it from doing so. 3030 spyOn(mQuotaController); 3031 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 3032 3033 // Rare window size is 24 hours. 3034 final int standbyBucket = RARE_INDEX; 3035 3036 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_Rare", 1); 3037 setStandbyBucket(standbyBucket, jobStatus); 3038 synchronized (mQuotaController.mLock) { 3039 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3040 } 3041 3042 // Prevent timing session throttling from affecting the test. 3043 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 50); 3044 3045 // No sessions saved yet. 3046 synchronized (mQuotaController.mLock) { 3047 mQuotaController.maybeScheduleStartAlarmLocked( 3048 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3049 } 3050 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 3051 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3052 3053 // Test with timing sessions out of window. 3054 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 3055 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3056 createTimingSession(now - 25 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), false); 3057 synchronized (mQuotaController.mLock) { 3058 mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket); 3059 } 3060 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 3061 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3062 3063 // Test with timing sessions in window but still in quota. 3064 final long start = now - (6 * HOUR_IN_MILLIS); 3065 // Counting backwards, the first minute in the session is over the allowed time, so it 3066 // needs to be excluded. 3067 final long expectedAlarmTime = 3068 start + MINUTE_IN_MILLIS + 24 * HOUR_IN_MILLIS 3069 + mQcConstants.IN_QUOTA_BUFFER_MS; 3070 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3071 createTimingSession(start, 5 * MINUTE_IN_MILLIS, 1), false); 3072 synchronized (mQuotaController.mLock) { 3073 mQuotaController.maybeScheduleStartAlarmLocked( 3074 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3075 } 3076 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 3077 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3078 3079 // Add some more sessions, but still in quota. 3080 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3081 createTimingSession(now - 3 * HOUR_IN_MILLIS, MINUTE_IN_MILLIS, 1), false); 3082 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3083 createTimingSession(now - HOUR_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 1), false); 3084 synchronized (mQuotaController.mLock) { 3085 mQuotaController.maybeScheduleStartAlarmLocked( 3086 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3087 } 3088 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 3089 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3090 3091 // Test when out of quota. 3092 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3093 createTimingSession(now - HOUR_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 1), false); 3094 synchronized (mQuotaController.mLock) { 3095 mQuotaController.maybeScheduleStartAlarmLocked( 3096 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3097 } 3098 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3099 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 3100 any(Handler.class)); 3101 3102 // Alarm already scheduled, so make sure it's not scheduled again. 3103 synchronized (mQuotaController.mLock) { 3104 mQuotaController.maybeScheduleStartAlarmLocked( 3105 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3106 } 3107 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3108 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 3109 any(Handler.class)); 3110 } 3111 3112 /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */ 3113 @Test testMaybeScheduleStartAlarmLocked_BucketChange()3114 public void testMaybeScheduleStartAlarmLocked_BucketChange() { 3115 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 3116 // because it schedules an alarm too. Prevent it from doing so. 3117 spyOn(mQuotaController); 3118 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 3119 3120 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 3121 3122 // Affects rare bucket 3123 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3124 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3), false); 3125 // Affects frequent and rare buckets 3126 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3127 createTimingSession(now - 4 * HOUR_IN_MILLIS, 4 * MINUTE_IN_MILLIS, 3), false); 3128 // Affects working, frequent, and rare buckets 3129 final long outOfQuotaTime = now - HOUR_IN_MILLIS; 3130 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3131 createTimingSession(outOfQuotaTime, 7 * MINUTE_IN_MILLIS, 10), false); 3132 // Affects all buckets 3133 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3134 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 3 * MINUTE_IN_MILLIS, 3), false); 3135 3136 InOrder inOrder = inOrder(mAlarmManager); 3137 3138 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked_BucketChange", 1); 3139 3140 // Start in ACTIVE bucket. 3141 setStandbyBucket(ACTIVE_INDEX, jobStatus); 3142 synchronized (mQuotaController.mLock) { 3143 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3144 mQuotaController.maybeScheduleStartAlarmLocked( 3145 SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); 3146 } 3147 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 3148 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 3149 any(Handler.class)); 3150 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 3151 .cancel(any(AlarmManager.OnAlarmListener.class)); 3152 3153 // And down from there. 3154 final long expectedWorkingAlarmTime = 3155 outOfQuotaTime + (2 * HOUR_IN_MILLIS) 3156 + mQcConstants.IN_QUOTA_BUFFER_MS; 3157 setStandbyBucket(WORKING_INDEX, jobStatus); 3158 synchronized (mQuotaController.mLock) { 3159 mQuotaController.maybeScheduleStartAlarmLocked( 3160 SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); 3161 } 3162 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3163 anyInt(), eq(expectedWorkingAlarmTime), anyLong(), 3164 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3165 3166 final long expectedFrequentAlarmTime = 3167 outOfQuotaTime + (8 * HOUR_IN_MILLIS) 3168 + mQcConstants.IN_QUOTA_BUFFER_MS; 3169 setStandbyBucket(FREQUENT_INDEX, jobStatus); 3170 synchronized (mQuotaController.mLock) { 3171 mQuotaController.maybeScheduleStartAlarmLocked( 3172 SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX); 3173 } 3174 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3175 anyInt(), eq(expectedFrequentAlarmTime), anyLong(), 3176 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3177 3178 final long expectedRareAlarmTime = 3179 outOfQuotaTime + (24 * HOUR_IN_MILLIS) 3180 + mQcConstants.IN_QUOTA_BUFFER_MS; 3181 setStandbyBucket(RARE_INDEX, jobStatus); 3182 synchronized (mQuotaController.mLock) { 3183 mQuotaController.maybeScheduleStartAlarmLocked( 3184 SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX); 3185 } 3186 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3187 anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 3188 any(Handler.class)); 3189 3190 // And back up again. 3191 setStandbyBucket(FREQUENT_INDEX, jobStatus); 3192 synchronized (mQuotaController.mLock) { 3193 mQuotaController.maybeScheduleStartAlarmLocked( 3194 SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX); 3195 } 3196 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3197 anyInt(), eq(expectedFrequentAlarmTime), anyLong(), 3198 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3199 3200 setStandbyBucket(WORKING_INDEX, jobStatus); 3201 synchronized (mQuotaController.mLock) { 3202 mQuotaController.maybeScheduleStartAlarmLocked( 3203 SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); 3204 } 3205 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3206 anyInt(), eq(expectedWorkingAlarmTime), anyLong(), 3207 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3208 3209 setStandbyBucket(ACTIVE_INDEX, jobStatus); 3210 synchronized (mQuotaController.mLock) { 3211 mQuotaController.maybeScheduleStartAlarmLocked( 3212 SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); 3213 } 3214 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 3215 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 3216 any(Handler.class)); 3217 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 3218 .cancel(any(AlarmManager.OnAlarmListener.class)); 3219 } 3220 3221 @Test testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow()3222 public void testMaybeScheduleStartAlarmLocked_JobCount_RateLimitingWindow() { 3223 // Set rate limiting period different from allowed time to confirm code sets based on 3224 // the former. 3225 final int standbyBucket = WORKING_INDEX; 3226 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 3227 10 * MINUTE_IN_MILLIS); 3228 setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 5 * MINUTE_IN_MILLIS); 3229 3230 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 3231 3232 JobStatus jobStatus = createJobStatus("testMaybeScheduleStartAlarmLocked", 1); 3233 setStandbyBucket(standbyBucket, jobStatus); 3234 synchronized (mQuotaController.mLock) { 3235 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3236 } 3237 3238 ExecutionStats stats; 3239 synchronized (mQuotaController.mLock) { 3240 stats = mQuotaController.getExecutionStatsLocked( 3241 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3242 } 3243 stats.jobCountInRateLimitingWindow = 3244 mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW + 2; 3245 3246 // Invalid time in the past, so the count shouldn't be used. 3247 stats.jobRateLimitExpirationTimeElapsed = now - 5 * MINUTE_IN_MILLIS / 2; 3248 synchronized (mQuotaController.mLock) { 3249 mQuotaController.maybeScheduleStartAlarmLocked( 3250 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3251 } 3252 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 3253 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3254 3255 // Valid time in the future, so the count should be used. 3256 stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2; 3257 final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed; 3258 synchronized (mQuotaController.mLock) { 3259 mQuotaController.maybeScheduleStartAlarmLocked( 3260 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3261 } 3262 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3263 anyInt(), eq(expectedWorkingAlarmTime), anyLong(), 3264 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 3265 } 3266 3267 /** 3268 * Tests that the start alarm is properly rescheduled if the earliest session that contributes 3269 * to the app being out of quota contributes less than the quota buffer time. 3270 */ 3271 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues()3272 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_DefaultValues() { 3273 // Use the default values 3274 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 3275 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 3276 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 3277 } 3278 3279 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize()3280 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedBufferSize() { 3281 // Make sure any new value is used correctly. 3282 setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 3283 mQcConstants.IN_QUOTA_BUFFER_MS * 2); 3284 3285 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 3286 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 3287 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 3288 } 3289 3290 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime()3291 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedAllowedTime() { 3292 // Make sure any new value is used correctly. 3293 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 3294 mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2); 3295 3296 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 3297 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 3298 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 3299 } 3300 3301 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime()3302 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedMaxTime() { 3303 // Make sure any new value is used correctly. 3304 setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 3305 mQcConstants.MAX_EXECUTION_TIME_MS / 2); 3306 3307 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 3308 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 3309 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 3310 } 3311 3312 @Test testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything()3313 public void testMaybeScheduleStartAlarmLocked_SmallRollingQuota_UpdatedEverything() { 3314 // Make sure any new value is used correctly. 3315 setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 3316 mQcConstants.IN_QUOTA_BUFFER_MS * 2); 3317 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 3318 mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS / 2); 3319 setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 3320 mQcConstants.MAX_EXECUTION_TIME_MS / 2); 3321 3322 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck(); 3323 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE).clear(); 3324 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck(); 3325 } 3326 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck()3327 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_AllowedTimeCheck() { 3328 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 3329 // because it schedules an alarm too. Prevent it from doing so. 3330 spyOn(mQuotaController); 3331 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 3332 3333 synchronized (mQuotaController.mLock) { 3334 mQuotaController.maybeStartTrackingJobLocked( 3335 createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null); 3336 } 3337 3338 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 3339 // Working set window size is 2 hours. 3340 final int standbyBucket = WORKING_INDEX; 3341 final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2; 3342 final long remainingTimeMs = 3343 mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS - contributionMs; 3344 3345 // Session straddles edge of bucket window. Only the contribution should be counted towards 3346 // the quota. 3347 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3348 createTimingSession(now - (2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS), 3349 3 * MINUTE_IN_MILLIS + contributionMs, 3), false); 3350 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3351 createTimingSession(now - HOUR_IN_MILLIS, remainingTimeMs, 2), false); 3352 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which 3353 // is 2 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session. 3354 final long expectedAlarmTime = now - HOUR_IN_MILLIS + 2 * HOUR_IN_MILLIS 3355 + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs); 3356 synchronized (mQuotaController.mLock) { 3357 mQuotaController.maybeScheduleStartAlarmLocked( 3358 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3359 } 3360 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3361 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 3362 any(Handler.class)); 3363 } 3364 runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck()3365 private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() { 3366 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 3367 // because it schedules an alarm too. Prevent it from doing so. 3368 spyOn(mQuotaController); 3369 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 3370 3371 synchronized (mQuotaController.mLock) { 3372 mQuotaController.maybeStartTrackingJobLocked( 3373 createJobStatus("testMaybeScheduleStartAlarmLocked", 1), null); 3374 } 3375 3376 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 3377 // Working set window size is 2 hours. 3378 final int standbyBucket = WORKING_INDEX; 3379 final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2; 3380 final long remainingTimeMs = mQcConstants.MAX_EXECUTION_TIME_MS - contributionMs; 3381 3382 // Session straddles edge of 24 hour window. Only the contribution should be counted towards 3383 // the quota. 3384 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3385 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS), 3386 3 * MINUTE_IN_MILLIS + contributionMs, 3), false); 3387 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 3388 createTimingSession(now - 20 * HOUR_IN_MILLIS, remainingTimeMs, 300), false); 3389 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which 3390 // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session. 3391 final long expectedAlarmTime = now - 20 * HOUR_IN_MILLIS 3392 + 24 * HOUR_IN_MILLIS 3393 + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs); 3394 synchronized (mQuotaController.mLock) { 3395 mQuotaController.maybeScheduleStartAlarmLocked( 3396 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 3397 } 3398 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 3399 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 3400 any(Handler.class)); 3401 } 3402 3403 @Test testConstantsUpdating_ValidValues()3404 public void testConstantsUpdating_ValidValues() { 3405 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, 3406 8 * MINUTE_IN_MILLIS); 3407 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 3408 5 * MINUTE_IN_MILLIS); 3409 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 3410 7 * MINUTE_IN_MILLIS); 3411 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 3412 2 * MINUTE_IN_MILLIS); 3413 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 4 * MINUTE_IN_MILLIS); 3414 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, 3415 11 * MINUTE_IN_MILLIS); 3416 setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 2 * MINUTE_IN_MILLIS); 3417 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 99 * MINUTE_IN_MILLIS); 3418 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 15 * MINUTE_IN_MILLIS); 3419 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 30 * MINUTE_IN_MILLIS); 3420 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 45 * MINUTE_IN_MILLIS); 3421 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 60 * MINUTE_IN_MILLIS); 3422 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 120 * MINUTE_IN_MILLIS); 3423 setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 3 * HOUR_IN_MILLIS); 3424 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, 6000); 3425 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, 5000); 3426 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 4000); 3427 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 3000); 3428 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RARE, 2000); 3429 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, 2000); 3430 setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * MINUTE_IN_MILLIS); 3431 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 500); 3432 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, 600); 3433 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, 500); 3434 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 400); 3435 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 300); 3436 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 200); 3437 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RESTRICTED, 100); 3438 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 50); 3439 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 3440 10 * SECOND_IN_MILLIS); 3441 setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 7 * MINUTE_IN_MILLIS); 3442 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 3 * HOUR_IN_MILLIS); 3443 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 2 * HOUR_IN_MILLIS); 3444 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 90 * MINUTE_IN_MILLIS); 3445 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 1 * HOUR_IN_MILLIS); 3446 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 30 * MINUTE_IN_MILLIS); 3447 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 27 * MINUTE_IN_MILLIS); 3448 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, 7 * HOUR_IN_MILLIS); 3449 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, 10 * HOUR_IN_MILLIS); 3450 setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 12 * HOUR_IN_MILLIS); 3451 setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 10 * MINUTE_IN_MILLIS); 3452 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 87 * SECOND_IN_MILLIS); 3453 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, 86 * SECOND_IN_MILLIS); 3454 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 85 * SECOND_IN_MILLIS); 3455 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 3456 84 * SECOND_IN_MILLIS); 3457 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS); 3458 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 3459 93 * SECOND_IN_MILLIS); 3460 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 92); 3461 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 91); 3462 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 90 * MINUTE_IN_MILLIS); 3463 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 89); 3464 3465 assertEquals(8 * MINUTE_IN_MILLIS, 3466 mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); 3467 assertEquals(5 * MINUTE_IN_MILLIS, 3468 mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]); 3469 assertEquals(7 * MINUTE_IN_MILLIS, 3470 mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]); 3471 assertEquals(2 * MINUTE_IN_MILLIS, 3472 mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); 3473 assertEquals(4 * MINUTE_IN_MILLIS, 3474 mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]); 3475 assertEquals(11 * MINUTE_IN_MILLIS, 3476 mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]); 3477 assertEquals(2 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); 3478 assertEquals(99 * MINUTE_IN_MILLIS, 3479 mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]); 3480 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); 3481 assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); 3482 assertEquals(45 * MINUTE_IN_MILLIS, 3483 mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); 3484 assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); 3485 assertEquals(120 * MINUTE_IN_MILLIS, 3486 mQuotaController.getBucketWindowSizes()[RESTRICTED_INDEX]); 3487 assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); 3488 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); 3489 assertEquals(500, mQuotaController.getMaxJobCountPerRateLimitingWindow()); 3490 assertEquals(6000, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]); 3491 assertEquals(5000, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); 3492 assertEquals(4000, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); 3493 assertEquals(3000, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); 3494 assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); 3495 assertEquals(2000, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]); 3496 assertEquals(50, mQuotaController.getMaxSessionCountPerRateLimitingWindow()); 3497 assertEquals(600, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]); 3498 assertEquals(500, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]); 3499 assertEquals(400, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]); 3500 assertEquals(300, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]); 3501 assertEquals(200, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]); 3502 assertEquals(100, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]); 3503 assertEquals(10 * SECOND_IN_MILLIS, 3504 mQuotaController.getTimingSessionCoalescingDurationMs()); 3505 assertEquals(7 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs()); 3506 assertEquals(3 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]); 3507 assertEquals(2 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]); 3508 assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]); 3509 assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); 3510 assertEquals(30 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]); 3511 assertEquals(27 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]); 3512 assertEquals(7 * HOUR_IN_MILLIS, mQuotaController.getEjLimitAdditionInstallerMs()); 3513 assertEquals(10 * HOUR_IN_MILLIS, mQuotaController.getEjLimitAdditionSpecialMs()); 3514 assertEquals(12 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs()); 3515 assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs()); 3516 assertEquals(87 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs()); 3517 assertEquals(86 * SECOND_IN_MILLIS, mQuotaController.getEJRewardInteractionMs()); 3518 assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs()); 3519 assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs()); 3520 assertEquals(83 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs()); 3521 assertEquals(93 * SECOND_IN_MILLIS, mQuotaController.getQuotaBumpAdditionDurationMs()); 3522 assertEquals(92, mQuotaController.getQuotaBumpAdditionJobCount()); 3523 assertEquals(91, mQuotaController.getQuotaBumpAdditionSessionCount()); 3524 assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs()); 3525 assertEquals(89, mQuotaController.getQuotaBumpLimit()); 3526 } 3527 3528 @Test testConstantsUpdating_InvalidValues()3529 public void testConstantsUpdating_InvalidValues() { 3530 // Test negatives/too low. 3531 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, -MINUTE_IN_MILLIS); 3532 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, -MINUTE_IN_MILLIS); 3533 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, -MINUTE_IN_MILLIS); 3534 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, -MINUTE_IN_MILLIS); 3535 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, -MINUTE_IN_MILLIS); 3536 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, 3537 -MINUTE_IN_MILLIS); 3538 setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, -MINUTE_IN_MILLIS); 3539 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, -MINUTE_IN_MILLIS); 3540 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, -MINUTE_IN_MILLIS); 3541 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, -MINUTE_IN_MILLIS); 3542 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, -MINUTE_IN_MILLIS); 3543 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, -MINUTE_IN_MILLIS); 3544 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, -MINUTE_IN_MILLIS); 3545 setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, -MINUTE_IN_MILLIS); 3546 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_EXEMPTED, -1); 3547 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_ACTIVE, -1); 3548 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 1); 3549 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, 1); 3550 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RARE, 1); 3551 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_RESTRICTED, -1); 3552 setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 15 * SECOND_IN_MILLIS); 3553 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 0); 3554 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_EXEMPTED, -1); 3555 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_ACTIVE, -1); 3556 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 0); 3557 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, -3); 3558 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RARE, 0); 3559 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_RESTRICTED, -5); 3560 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 0); 3561 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, -1); 3562 setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, -1); 3563 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, -1); 3564 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, -1); 3565 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, -1); 3566 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, -1); 3567 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, -1); 3568 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, -1); 3569 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, -1); 3570 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, -1); 3571 setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, -1); 3572 setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, -1); 3573 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, -1); 3574 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, -1); 3575 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, -1); 3576 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1); 3577 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1); 3578 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, -1); 3579 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, -1); 3580 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, -1); 3581 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 59 * MINUTE_IN_MILLIS); 3582 setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, -1); 3583 3584 assertEquals(MINUTE_IN_MILLIS, 3585 mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); 3586 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]); 3587 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]); 3588 assertEquals(MINUTE_IN_MILLIS, 3589 mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); 3590 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]); 3591 assertEquals(MINUTE_IN_MILLIS, 3592 mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]); 3593 assertEquals(0, mQuotaController.getInQuotaBufferMs()); 3594 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]); 3595 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); 3596 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); 3597 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); 3598 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); 3599 assertEquals(MINUTE_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RESTRICTED_INDEX]); 3600 assertEquals(HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); 3601 assertEquals(30 * SECOND_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); 3602 assertEquals(10, mQuotaController.getMaxJobCountPerRateLimitingWindow()); 3603 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[EXEMPTED_INDEX]); 3604 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[ACTIVE_INDEX]); 3605 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[WORKING_INDEX]); 3606 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[FREQUENT_INDEX]); 3607 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RARE_INDEX]); 3608 assertEquals(10, mQuotaController.getBucketMaxJobCounts()[RESTRICTED_INDEX]); 3609 assertEquals(10, mQuotaController.getMaxSessionCountPerRateLimitingWindow()); 3610 assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[EXEMPTED_INDEX]); 3611 assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[ACTIVE_INDEX]); 3612 assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[WORKING_INDEX]); 3613 assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[FREQUENT_INDEX]); 3614 assertEquals(1, mQuotaController.getBucketMaxSessionCounts()[RARE_INDEX]); 3615 assertEquals(0, mQuotaController.getBucketMaxSessionCounts()[RESTRICTED_INDEX]); 3616 assertEquals(0, mQuotaController.getTimingSessionCoalescingDurationMs()); 3617 assertEquals(0, mQuotaController.getMinQuotaCheckDelayMs()); 3618 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]); 3619 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]); 3620 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]); 3621 assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); 3622 assertEquals(10 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]); 3623 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]); 3624 assertEquals(0, mQuotaController.getEjLimitAdditionInstallerMs()); 3625 assertEquals(0, mQuotaController.getEjLimitAdditionSpecialMs()); 3626 assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs()); 3627 assertEquals(1, mQuotaController.getEJTopAppTimeChunkSizeMs()); 3628 assertEquals(10 * SECOND_IN_MILLIS, mQuotaController.getEJRewardTopAppMs()); 3629 assertEquals(5 * SECOND_IN_MILLIS, mQuotaController.getEJRewardInteractionMs()); 3630 assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs()); 3631 assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs()); 3632 assertEquals(0, mQuotaController.getEJGracePeriodTopAppMs()); 3633 assertEquals(0, mQuotaController.getQuotaBumpAdditionDurationMs()); 3634 assertEquals(0, mQuotaController.getQuotaBumpAdditionJobCount()); 3635 assertEquals(0, mQuotaController.getQuotaBumpAdditionSessionCount()); 3636 assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs()); 3637 assertEquals(0, mQuotaController.getQuotaBumpLimit()); 3638 3639 // Invalid configurations. 3640 // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD 3641 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, 3642 10 * MINUTE_IN_MILLIS); 3643 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 3644 10 * MINUTE_IN_MILLIS); 3645 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 3646 10 * MINUTE_IN_MILLIS); 3647 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 3648 2 * MINUTE_IN_MILLIS); 3649 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 10 * MINUTE_IN_MILLIS); 3650 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, 3651 10 * MINUTE_IN_MILLIS); 3652 setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 5 * MINUTE_IN_MILLIS); 3653 3654 assertTrue(mQuotaController.getInQuotaBufferMs() 3655 <= mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); 3656 3657 // Test larger than a day. Controller should cap at one day. 3658 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, 3659 25 * HOUR_IN_MILLIS); 3660 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 25 * HOUR_IN_MILLIS); 3661 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, 3662 25 * HOUR_IN_MILLIS); 3663 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 3664 25 * HOUR_IN_MILLIS); 3665 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RARE_MS, 25 * HOUR_IN_MILLIS); 3666 setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_RESTRICTED_MS, 3667 25 * HOUR_IN_MILLIS); 3668 setDeviceConfigLong(QcConstants.KEY_IN_QUOTA_BUFFER_MS, 25 * HOUR_IN_MILLIS); 3669 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 25 * HOUR_IN_MILLIS); 3670 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_ACTIVE_MS, 25 * HOUR_IN_MILLIS); 3671 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_WORKING_MS, 25 * HOUR_IN_MILLIS); 3672 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_FREQUENT_MS, 25 * HOUR_IN_MILLIS); 3673 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RARE_MS, 25 * HOUR_IN_MILLIS); 3674 setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_RESTRICTED_MS, 30 * 24 * HOUR_IN_MILLIS); 3675 setDeviceConfigLong(QcConstants.KEY_MAX_EXECUTION_TIME_MS, 25 * HOUR_IN_MILLIS); 3676 setDeviceConfigLong(QcConstants.KEY_RATE_LIMITING_WINDOW_MS, 25 * HOUR_IN_MILLIS); 3677 setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 3678 25 * HOUR_IN_MILLIS); 3679 setDeviceConfigLong(QcConstants.KEY_MIN_QUOTA_CHECK_DELAY_MS, 25 * HOUR_IN_MILLIS); 3680 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_EXEMPTED_MS, 25 * HOUR_IN_MILLIS); 3681 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 25 * HOUR_IN_MILLIS); 3682 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 25 * HOUR_IN_MILLIS); 3683 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 25 * HOUR_IN_MILLIS); 3684 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 25 * HOUR_IN_MILLIS); 3685 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 25 * HOUR_IN_MILLIS); 3686 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_INSTALLER_MS, 25 * HOUR_IN_MILLIS); 3687 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ADDITION_SPECIAL_MS, 25 * HOUR_IN_MILLIS); 3688 setDeviceConfigLong(QcConstants.KEY_EJ_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS); 3689 setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 25 * HOUR_IN_MILLIS); 3690 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 25 * HOUR_IN_MILLIS); 3691 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, 25 * HOUR_IN_MILLIS); 3692 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 25 * HOUR_IN_MILLIS); 3693 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS); 3694 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS); 3695 setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS); 3696 3697 assertEquals(24 * HOUR_IN_MILLIS, 3698 mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); 3699 assertEquals(24 * HOUR_IN_MILLIS, 3700 mQuotaController.getAllowedTimePerPeriodMs()[ACTIVE_INDEX]); 3701 assertEquals(24 * HOUR_IN_MILLIS, 3702 mQuotaController.getAllowedTimePerPeriodMs()[WORKING_INDEX]); 3703 assertEquals(24 * HOUR_IN_MILLIS, 3704 mQuotaController.getAllowedTimePerPeriodMs()[FREQUENT_INDEX]); 3705 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[RARE_INDEX]); 3706 assertEquals(24 * HOUR_IN_MILLIS, 3707 mQuotaController.getAllowedTimePerPeriodMs()[RESTRICTED_INDEX]); 3708 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getInQuotaBufferMs()); 3709 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[EXEMPTED_INDEX]); 3710 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[ACTIVE_INDEX]); 3711 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[WORKING_INDEX]); 3712 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[FREQUENT_INDEX]); 3713 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getBucketWindowSizes()[RARE_INDEX]); 3714 assertEquals(7 * 24 * HOUR_IN_MILLIS, 3715 mQuotaController.getBucketWindowSizes()[RESTRICTED_INDEX]); 3716 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getMaxExecutionTimeMs()); 3717 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getRateLimitingWindowMs()); 3718 assertEquals(15 * MINUTE_IN_MILLIS, 3719 mQuotaController.getTimingSessionCoalescingDurationMs()); 3720 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getMinQuotaCheckDelayMs()); 3721 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[EXEMPTED_INDEX]); 3722 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[ACTIVE_INDEX]); 3723 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[WORKING_INDEX]); 3724 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[FREQUENT_INDEX]); 3725 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RARE_INDEX]); 3726 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitsMs()[RESTRICTED_INDEX]); 3727 assertEquals(0, mQuotaController.getEjLimitAdditionInstallerMs()); 3728 assertEquals(0, mQuotaController.getEjLimitAdditionSpecialMs()); 3729 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getEJLimitWindowSizeMs()); 3730 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJTopAppTimeChunkSizeMs()); 3731 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardTopAppMs()); 3732 assertEquals(15 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardInteractionMs()); 3733 assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs()); 3734 assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs()); 3735 assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs()); 3736 assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs()); 3737 } 3738 3739 /** Tests that TimingSessions aren't saved when the device is charging. */ 3740 @Test testTimerTracking_Charging()3741 public void testTimerTracking_Charging() { 3742 setCharging(); 3743 3744 JobStatus jobStatus = createJobStatus("testTimerTracking_Charging", 1); 3745 synchronized (mQuotaController.mLock) { 3746 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3747 } 3748 3749 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3750 3751 synchronized (mQuotaController.mLock) { 3752 mQuotaController.prepareForExecutionLocked(jobStatus); 3753 } 3754 advanceElapsedClock(5 * SECOND_IN_MILLIS); 3755 synchronized (mQuotaController.mLock) { 3756 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 3757 } 3758 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3759 } 3760 3761 /** Tests that TimingSessions are saved properly when the device is discharging. */ 3762 @Test testTimerTracking_Discharging()3763 public void testTimerTracking_Discharging() { 3764 setDischarging(); 3765 setProcessState(ActivityManager.PROCESS_STATE_BACKUP); 3766 3767 JobStatus jobStatus = createJobStatus("testTimerTracking_Discharging", 1); 3768 synchronized (mQuotaController.mLock) { 3769 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3770 } 3771 3772 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3773 3774 List<TimingSession> expected = new ArrayList<>(); 3775 3776 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 3777 synchronized (mQuotaController.mLock) { 3778 mQuotaController.prepareForExecutionLocked(jobStatus); 3779 } 3780 advanceElapsedClock(5 * SECOND_IN_MILLIS); 3781 synchronized (mQuotaController.mLock) { 3782 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 3783 } 3784 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); 3785 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3786 3787 // Test overlapping jobs. 3788 JobStatus jobStatus2 = createJobStatus("testTimerTracking_Discharging", 2); 3789 synchronized (mQuotaController.mLock) { 3790 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 3791 } 3792 3793 JobStatus jobStatus3 = createJobStatus("testTimerTracking_Discharging", 3); 3794 synchronized (mQuotaController.mLock) { 3795 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); 3796 } 3797 3798 advanceElapsedClock(SECOND_IN_MILLIS); 3799 3800 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 3801 synchronized (mQuotaController.mLock) { 3802 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3803 mQuotaController.prepareForExecutionLocked(jobStatus); 3804 } 3805 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3806 synchronized (mQuotaController.mLock) { 3807 mQuotaController.prepareForExecutionLocked(jobStatus2); 3808 } 3809 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3810 synchronized (mQuotaController.mLock) { 3811 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 3812 } 3813 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3814 synchronized (mQuotaController.mLock) { 3815 mQuotaController.prepareForExecutionLocked(jobStatus3); 3816 } 3817 advanceElapsedClock(20 * SECOND_IN_MILLIS); 3818 synchronized (mQuotaController.mLock) { 3819 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); 3820 } 3821 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3822 synchronized (mQuotaController.mLock) { 3823 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); 3824 } 3825 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); 3826 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3827 } 3828 3829 /** 3830 * Tests that TimingSessions are saved properly when the device alternates between 3831 * charging and discharging. 3832 */ 3833 @Test testTimerTracking_ChargingAndDischarging()3834 public void testTimerTracking_ChargingAndDischarging() { 3835 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 3836 3837 JobStatus jobStatus = createJobStatus("testTimerTracking_ChargingAndDischarging", 1); 3838 synchronized (mQuotaController.mLock) { 3839 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3840 } 3841 JobStatus jobStatus2 = createJobStatus("testTimerTracking_ChargingAndDischarging", 2); 3842 synchronized (mQuotaController.mLock) { 3843 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 3844 } 3845 JobStatus jobStatus3 = createJobStatus("testTimerTracking_ChargingAndDischarging", 3); 3846 synchronized (mQuotaController.mLock) { 3847 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); 3848 } 3849 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3850 List<TimingSession> expected = new ArrayList<>(); 3851 3852 // A job starting while charging. Only the portion that runs during the discharging period 3853 // should be counted. 3854 setCharging(); 3855 3856 synchronized (mQuotaController.mLock) { 3857 mQuotaController.prepareForExecutionLocked(jobStatus); 3858 } 3859 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3860 setDischarging(); 3861 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 3862 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3863 synchronized (mQuotaController.mLock) { 3864 mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus); 3865 } 3866 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 3867 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3868 3869 advanceElapsedClock(SECOND_IN_MILLIS); 3870 3871 // One job starts while discharging, spans a charging session, and ends after the charging 3872 // session. Only the portions during the discharging periods should be counted. This should 3873 // result in two TimingSessions. A second job starts while discharging and ends within the 3874 // charging session. Only the portion during the first discharging portion should be 3875 // counted. A third job starts and ends within the charging session. The third job 3876 // shouldn't be included in either job count. 3877 setDischarging(); 3878 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 3879 synchronized (mQuotaController.mLock) { 3880 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3881 mQuotaController.prepareForExecutionLocked(jobStatus); 3882 } 3883 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3884 synchronized (mQuotaController.mLock) { 3885 mQuotaController.prepareForExecutionLocked(jobStatus2); 3886 } 3887 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3888 setCharging(); 3889 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); 3890 synchronized (mQuotaController.mLock) { 3891 mQuotaController.prepareForExecutionLocked(jobStatus3); 3892 } 3893 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3894 synchronized (mQuotaController.mLock) { 3895 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); 3896 } 3897 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3898 synchronized (mQuotaController.mLock) { 3899 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); 3900 } 3901 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3902 setDischarging(); 3903 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 3904 advanceElapsedClock(20 * SECOND_IN_MILLIS); 3905 synchronized (mQuotaController.mLock) { 3906 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 3907 } 3908 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); 3909 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3910 3911 // A job starting while discharging and ending while charging. Only the portion that runs 3912 // during the discharging period should be counted. 3913 setDischarging(); 3914 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 3915 synchronized (mQuotaController.mLock) { 3916 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 3917 mQuotaController.prepareForExecutionLocked(jobStatus2); 3918 } 3919 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3920 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 3921 setCharging(); 3922 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3923 synchronized (mQuotaController.mLock) { 3924 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); 3925 } 3926 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3927 } 3928 3929 /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */ 3930 @Test testTimerTracking_AllBackground()3931 public void testTimerTracking_AllBackground() { 3932 setDischarging(); 3933 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 3934 3935 JobStatus jobStatus = createJobStatus("testTimerTracking_AllBackground", 1); 3936 synchronized (mQuotaController.mLock) { 3937 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3938 } 3939 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3940 3941 List<TimingSession> expected = new ArrayList<>(); 3942 3943 // Test single job. 3944 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 3945 synchronized (mQuotaController.mLock) { 3946 mQuotaController.prepareForExecutionLocked(jobStatus); 3947 } 3948 advanceElapsedClock(5 * SECOND_IN_MILLIS); 3949 synchronized (mQuotaController.mLock) { 3950 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 3951 } 3952 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); 3953 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3954 3955 // Test overlapping jobs. 3956 JobStatus jobStatus2 = createJobStatus("testTimerTracking_AllBackground", 2); 3957 synchronized (mQuotaController.mLock) { 3958 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 3959 } 3960 3961 JobStatus jobStatus3 = createJobStatus("testTimerTracking_AllBackground", 3); 3962 synchronized (mQuotaController.mLock) { 3963 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); 3964 } 3965 3966 advanceElapsedClock(SECOND_IN_MILLIS); 3967 3968 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 3969 synchronized (mQuotaController.mLock) { 3970 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 3971 mQuotaController.prepareForExecutionLocked(jobStatus); 3972 } 3973 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3974 synchronized (mQuotaController.mLock) { 3975 mQuotaController.prepareForExecutionLocked(jobStatus2); 3976 } 3977 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3978 synchronized (mQuotaController.mLock) { 3979 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 3980 } 3981 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3982 synchronized (mQuotaController.mLock) { 3983 mQuotaController.prepareForExecutionLocked(jobStatus3); 3984 } 3985 advanceElapsedClock(20 * SECOND_IN_MILLIS); 3986 synchronized (mQuotaController.mLock) { 3987 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); 3988 } 3989 advanceElapsedClock(10 * SECOND_IN_MILLIS); 3990 synchronized (mQuotaController.mLock) { 3991 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); 3992 } 3993 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); 3994 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 3995 } 3996 3997 /** Tests that Timers don't count foreground jobs. */ 3998 @Test testTimerTracking_AllForeground()3999 public void testTimerTracking_AllForeground() { 4000 setDischarging(); 4001 4002 JobStatus jobStatus = createJobStatus("testTimerTracking_AllForeground", 1); 4003 setProcessState(ActivityManager.PROCESS_STATE_TOP); 4004 synchronized (mQuotaController.mLock) { 4005 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 4006 } 4007 4008 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4009 4010 synchronized (mQuotaController.mLock) { 4011 mQuotaController.prepareForExecutionLocked(jobStatus); 4012 } 4013 advanceElapsedClock(5 * SECOND_IN_MILLIS); 4014 // Change to a state that should still be considered foreground. 4015 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 4016 advanceElapsedClock(5 * SECOND_IN_MILLIS); 4017 synchronized (mQuotaController.mLock) { 4018 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 4019 } 4020 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4021 } 4022 4023 /** 4024 * Tests that Timers properly track sessions when switching between foreground and background 4025 * states. 4026 */ 4027 @Test testTimerTracking_ForegroundAndBackground()4028 public void testTimerTracking_ForegroundAndBackground() { 4029 setDischarging(); 4030 4031 JobStatus jobBg1 = createJobStatus("testTimerTracking_ForegroundAndBackground", 1); 4032 JobStatus jobBg2 = createJobStatus("testTimerTracking_ForegroundAndBackground", 2); 4033 JobStatus jobFg3 = createJobStatus("testTimerTracking_ForegroundAndBackground", 3); 4034 synchronized (mQuotaController.mLock) { 4035 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 4036 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 4037 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); 4038 } 4039 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4040 List<TimingSession> expected = new ArrayList<>(); 4041 4042 // UID starts out inactive. 4043 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 4044 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4045 synchronized (mQuotaController.mLock) { 4046 mQuotaController.prepareForExecutionLocked(jobBg1); 4047 } 4048 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4049 synchronized (mQuotaController.mLock) { 4050 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); 4051 } 4052 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 4053 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4054 4055 advanceElapsedClock(SECOND_IN_MILLIS); 4056 4057 // Bg job starts while inactive, spans an entire active session, and ends after the 4058 // active session. 4059 // App switching to foreground state then fg job starts. 4060 // App remains in foreground state after coming to foreground, so there should only be one 4061 // session. 4062 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4063 synchronized (mQuotaController.mLock) { 4064 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 4065 mQuotaController.prepareForExecutionLocked(jobBg2); 4066 } 4067 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4068 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 4069 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 4070 synchronized (mQuotaController.mLock) { 4071 mQuotaController.prepareForExecutionLocked(jobFg3); 4072 } 4073 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4074 synchronized (mQuotaController.mLock) { 4075 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null); 4076 } 4077 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4078 synchronized (mQuotaController.mLock) { 4079 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); 4080 } 4081 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4082 4083 advanceElapsedClock(SECOND_IN_MILLIS); 4084 4085 // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes 4086 // "inactive" and then bg job 2 starts. Then fg job ends. 4087 // This should result in two TimingSessions: 4088 // * The first should have a count of 1 4089 // * The second should have a count of 2 since it will include both jobs 4090 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4091 synchronized (mQuotaController.mLock) { 4092 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 4093 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 4094 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); 4095 } 4096 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY); 4097 synchronized (mQuotaController.mLock) { 4098 mQuotaController.prepareForExecutionLocked(jobBg1); 4099 } 4100 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4101 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 4102 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 4103 synchronized (mQuotaController.mLock) { 4104 mQuotaController.prepareForExecutionLocked(jobFg3); 4105 } 4106 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4107 synchronized (mQuotaController.mLock) { 4108 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); 4109 } 4110 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now 4111 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4112 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 4113 synchronized (mQuotaController.mLock) { 4114 mQuotaController.prepareForExecutionLocked(jobBg2); 4115 } 4116 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4117 synchronized (mQuotaController.mLock) { 4118 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null); 4119 } 4120 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4121 synchronized (mQuotaController.mLock) { 4122 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); 4123 } 4124 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); 4125 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4126 } 4127 4128 /** 4129 * Tests that Timers don't track job counts while in the foreground. 4130 */ 4131 @Test testTimerTracking_JobCount_Foreground()4132 public void testTimerTracking_JobCount_Foreground() { 4133 setDischarging(); 4134 4135 final int standbyBucket = ACTIVE_INDEX; 4136 JobStatus jobFg1 = createJobStatus("testTimerTracking_JobCount_Foreground", 1); 4137 JobStatus jobFg2 = createJobStatus("testTimerTracking_JobCount_Foreground", 2); 4138 4139 synchronized (mQuotaController.mLock) { 4140 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null); 4141 mQuotaController.maybeStartTrackingJobLocked(jobFg2, null); 4142 } 4143 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4144 ExecutionStats stats; 4145 synchronized (mQuotaController.mLock) { 4146 stats = mQuotaController.getExecutionStatsLocked( 4147 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 4148 } 4149 assertEquals(0, stats.jobCountInRateLimitingWindow); 4150 4151 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 4152 synchronized (mQuotaController.mLock) { 4153 mQuotaController.prepareForExecutionLocked(jobFg1); 4154 } 4155 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4156 synchronized (mQuotaController.mLock) { 4157 mQuotaController.prepareForExecutionLocked(jobFg2); 4158 } 4159 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4160 synchronized (mQuotaController.mLock) { 4161 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null); 4162 } 4163 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4164 synchronized (mQuotaController.mLock) { 4165 mQuotaController.maybeStopTrackingJobLocked(jobFg2, null); 4166 } 4167 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4168 4169 assertEquals(0, stats.jobCountInRateLimitingWindow); 4170 } 4171 4172 /** 4173 * Tests that Timers properly track job counts while in the background. 4174 */ 4175 @Test testTimerTracking_JobCount_Background()4176 public void testTimerTracking_JobCount_Background() { 4177 final int standbyBucket = WORKING_INDEX; 4178 JobStatus jobBg1 = createJobStatus("testTimerTracking_JobCount_Background", 1); 4179 JobStatus jobBg2 = createJobStatus("testTimerTracking_JobCount_Background", 2); 4180 ExecutionStats stats; 4181 synchronized (mQuotaController.mLock) { 4182 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 4183 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 4184 4185 stats = mQuotaController.getExecutionStatsLocked(SOURCE_USER_ID, 4186 SOURCE_PACKAGE, standbyBucket); 4187 } 4188 assertEquals(0, stats.jobCountInRateLimitingWindow); 4189 4190 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 4191 synchronized (mQuotaController.mLock) { 4192 mQuotaController.prepareForExecutionLocked(jobBg1); 4193 } 4194 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4195 synchronized (mQuotaController.mLock) { 4196 mQuotaController.prepareForExecutionLocked(jobBg2); 4197 } 4198 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4199 synchronized (mQuotaController.mLock) { 4200 mQuotaController.maybeStopTrackingJobLocked(jobBg1, null); 4201 } 4202 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4203 synchronized (mQuotaController.mLock) { 4204 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); 4205 } 4206 4207 assertEquals(2, stats.jobCountInRateLimitingWindow); 4208 } 4209 4210 /** 4211 * Tests that Timers properly track overlapping top and background jobs. 4212 */ 4213 @Test testTimerTracking_TopAndNonTop()4214 public void testTimerTracking_TopAndNonTop() { 4215 setDischarging(); 4216 4217 JobStatus jobBg1 = createJobStatus("testTimerTracking_TopAndNonTop", 1); 4218 JobStatus jobBg2 = createJobStatus("testTimerTracking_TopAndNonTop", 2); 4219 JobStatus jobFg1 = createJobStatus("testTimerTracking_TopAndNonTop", 3); 4220 JobStatus jobTop = createJobStatus("testTimerTracking_TopAndNonTop", 4); 4221 synchronized (mQuotaController.mLock) { 4222 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 4223 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 4224 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null); 4225 mQuotaController.maybeStartTrackingJobLocked(jobTop, null); 4226 } 4227 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4228 List<TimingSession> expected = new ArrayList<>(); 4229 4230 // UID starts out inactive. 4231 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 4232 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4233 synchronized (mQuotaController.mLock) { 4234 mQuotaController.prepareForExecutionLocked(jobBg1); 4235 } 4236 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4237 synchronized (mQuotaController.mLock) { 4238 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); 4239 } 4240 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 4241 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4242 4243 advanceElapsedClock(SECOND_IN_MILLIS); 4244 4245 // Bg job starts while inactive, spans an entire active session, and ends after the 4246 // active session. 4247 // App switching to top state then fg job starts. 4248 // App remains in top state after coming to top, so there should only be one 4249 // session. 4250 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4251 synchronized (mQuotaController.mLock) { 4252 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 4253 mQuotaController.prepareForExecutionLocked(jobBg2); 4254 } 4255 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4256 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 4257 setProcessState(ActivityManager.PROCESS_STATE_TOP); 4258 synchronized (mQuotaController.mLock) { 4259 mQuotaController.prepareForExecutionLocked(jobTop); 4260 } 4261 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4262 synchronized (mQuotaController.mLock) { 4263 mQuotaController.maybeStopTrackingJobLocked(jobTop, null); 4264 } 4265 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4266 synchronized (mQuotaController.mLock) { 4267 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); 4268 } 4269 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4270 4271 advanceElapsedClock(SECOND_IN_MILLIS); 4272 4273 // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to 4274 // foreground_service and a new job starts. Shortly after, uid goes 4275 // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs. 4276 // This should result in two TimingSessions: 4277 // * The first should have a count of 1 4278 // * The second should have a count of 2, which accounts for the bg2 and fg, but not top 4279 // jobs. 4280 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4281 synchronized (mQuotaController.mLock) { 4282 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 4283 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 4284 mQuotaController.maybeStartTrackingJobLocked(jobTop, null); 4285 } 4286 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY); 4287 synchronized (mQuotaController.mLock) { 4288 mQuotaController.prepareForExecutionLocked(jobBg1); 4289 } 4290 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4291 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 4292 setProcessState(ActivityManager.PROCESS_STATE_TOP); 4293 synchronized (mQuotaController.mLock) { 4294 mQuotaController.prepareForExecutionLocked(jobTop); 4295 } 4296 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4297 synchronized (mQuotaController.mLock) { 4298 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); 4299 } 4300 advanceElapsedClock(5 * SECOND_IN_MILLIS); 4301 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 4302 synchronized (mQuotaController.mLock) { 4303 mQuotaController.prepareForExecutionLocked(jobFg1); 4304 } 4305 advanceElapsedClock(5 * SECOND_IN_MILLIS); 4306 setProcessState(ActivityManager.PROCESS_STATE_TOP); 4307 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now 4308 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4309 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 4310 synchronized (mQuotaController.mLock) { 4311 mQuotaController.prepareForExecutionLocked(jobBg2); 4312 } 4313 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4314 synchronized (mQuotaController.mLock) { 4315 mQuotaController.maybeStopTrackingJobLocked(jobTop, null); 4316 } 4317 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4318 synchronized (mQuotaController.mLock) { 4319 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); 4320 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null); 4321 } 4322 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); 4323 assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4324 } 4325 4326 /** 4327 * Tests that Timers properly track regular sessions when an app is added and removed from the 4328 * temp allowlist. 4329 */ 4330 @Test testTimerTracking_TempAllowlisting()4331 public void testTimerTracking_TempAllowlisting() { 4332 // None of these should be affected purely by the temp allowlist changing. 4333 setDischarging(); 4334 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 4335 final long gracePeriodMs = 15 * SECOND_IN_MILLIS; 4336 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs); 4337 Handler handler = mQuotaController.getHandler(); 4338 spyOn(handler); 4339 4340 JobStatus job1 = createJobStatus("testTimerTracking_TempAllowlisting", 1); 4341 JobStatus job2 = createJobStatus("testTimerTracking_TempAllowlisting", 2); 4342 JobStatus job3 = createJobStatus("testTimerTracking_TempAllowlisting", 3); 4343 JobStatus job4 = createJobStatus("testTimerTracking_TempAllowlisting", 4); 4344 JobStatus job5 = createJobStatus("testTimerTracking_TempAllowlisting", 5); 4345 synchronized (mQuotaController.mLock) { 4346 mQuotaController.maybeStartTrackingJobLocked(job1, null); 4347 } 4348 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4349 List<TimingSession> expected = new ArrayList<>(); 4350 4351 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4352 synchronized (mQuotaController.mLock) { 4353 mQuotaController.prepareForExecutionLocked(job1); 4354 } 4355 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4356 synchronized (mQuotaController.mLock) { 4357 mQuotaController.maybeStopTrackingJobLocked(job1, job1); 4358 } 4359 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 4360 assertEquals(expected, 4361 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4362 4363 advanceElapsedClock(SECOND_IN_MILLIS); 4364 4365 // Job starts after app is added to temp allowlist and stops before removal. 4366 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4367 mTempAllowlistListener.onAppAdded(mSourceUid); 4368 synchronized (mQuotaController.mLock) { 4369 mQuotaController.maybeStartTrackingJobLocked(job2, null); 4370 mQuotaController.prepareForExecutionLocked(job2); 4371 } 4372 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4373 synchronized (mQuotaController.mLock) { 4374 mQuotaController.maybeStopTrackingJobLocked(job2, null); 4375 } 4376 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 4377 assertEquals(expected, 4378 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4379 4380 // Job starts after app is added to temp allowlist and stops after removal, 4381 // before grace period ends. 4382 mTempAllowlistListener.onAppAdded(mSourceUid); 4383 synchronized (mQuotaController.mLock) { 4384 mQuotaController.maybeStartTrackingJobLocked(job3, null); 4385 mQuotaController.prepareForExecutionLocked(job3); 4386 } 4387 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4388 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4389 mTempAllowlistListener.onAppRemoved(mSourceUid); 4390 long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS; 4391 advanceElapsedClock(elapsedGracePeriodMs); 4392 synchronized (mQuotaController.mLock) { 4393 mQuotaController.maybeStopTrackingJobLocked(job3, null); 4394 } 4395 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + elapsedGracePeriodMs, 1)); 4396 assertEquals(expected, 4397 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4398 4399 advanceElapsedClock(SECOND_IN_MILLIS); 4400 elapsedGracePeriodMs += SECOND_IN_MILLIS; 4401 4402 // Job starts during grace period and ends after grace period ends 4403 synchronized (mQuotaController.mLock) { 4404 mQuotaController.maybeStartTrackingJobLocked(job4, null); 4405 mQuotaController.prepareForExecutionLocked(job4); 4406 } 4407 final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs; 4408 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4409 advanceElapsedClock(remainingGracePeriod); 4410 // Wait for handler to update Timer 4411 // Can't directly evaluate the message because for some reason, the captured message returns 4412 // the wrong 'what' even though the correct message goes to the handler and the correct 4413 // path executes. 4414 verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any()); 4415 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4416 expected.add(createTimingSession(start, remainingGracePeriod + 10 * SECOND_IN_MILLIS, 1)); 4417 synchronized (mQuotaController.mLock) { 4418 mQuotaController.maybeStopTrackingJobLocked(job4, job4); 4419 } 4420 assertEquals(expected, 4421 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4422 4423 // Job starts and runs completely after temp allowlist grace period. 4424 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4425 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 4426 synchronized (mQuotaController.mLock) { 4427 mQuotaController.maybeStartTrackingJobLocked(job5, null); 4428 mQuotaController.prepareForExecutionLocked(job5); 4429 } 4430 advanceElapsedClock(10 * SECOND_IN_MILLIS); 4431 synchronized (mQuotaController.mLock) { 4432 mQuotaController.maybeStopTrackingJobLocked(job5, job5); 4433 } 4434 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 4435 assertEquals(expected, 4436 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 4437 } 4438 4439 /** 4440 * Tests that TOP jobs aren't stopped when an app runs out of quota. 4441 */ 4442 @Test testTracking_OutOfQuota_ForegroundAndBackground()4443 public void testTracking_OutOfQuota_ForegroundAndBackground() { 4444 setDischarging(); 4445 4446 JobStatus jobBg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 1); 4447 JobStatus jobTop = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 2); 4448 trackJobs(jobBg, jobTop); 4449 setStandbyBucket(WORKING_INDEX, jobTop, jobBg); // 2 hour window 4450 // Now the package only has 20 seconds to run. 4451 final long remainingTimeMs = 20 * SECOND_IN_MILLIS; 4452 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4453 createTimingSession( 4454 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS, 4455 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false); 4456 4457 InOrder inOrder = inOrder(mJobSchedulerService); 4458 4459 // UID starts out inactive. 4460 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 4461 // Start the job. 4462 synchronized (mQuotaController.mLock) { 4463 mQuotaController.prepareForExecutionLocked(jobBg); 4464 } 4465 advanceElapsedClock(remainingTimeMs / 2); 4466 // New job starts after UID is in the foreground. Since the app is now in the foreground, it 4467 // should continue to have remainingTimeMs / 2 time remaining. 4468 setProcessState(ActivityManager.PROCESS_STATE_TOP); 4469 synchronized (mQuotaController.mLock) { 4470 mQuotaController.prepareForExecutionLocked(jobTop); 4471 } 4472 advanceElapsedClock(remainingTimeMs); 4473 4474 // Wait for some extra time to allow for job processing. 4475 inOrder.verify(mJobSchedulerService, 4476 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) 4477 .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); 4478 synchronized (mQuotaController.mLock) { 4479 assertEquals(remainingTimeMs / 2, 4480 mQuotaController.getRemainingExecutionTimeLocked(jobBg)); 4481 assertEquals(remainingTimeMs / 2, 4482 mQuotaController.getRemainingExecutionTimeLocked(jobTop)); 4483 } 4484 // Go to a background state. 4485 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 4486 advanceElapsedClock(remainingTimeMs / 2 + 1); 4487 inOrder.verify(mJobSchedulerService, 4488 timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) 4489 .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); 4490 // Top job should still be allowed to run. 4491 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4492 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4493 4494 // New jobs to run. 4495 JobStatus jobBg2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 3); 4496 JobStatus jobTop2 = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 4); 4497 JobStatus jobFg = createJobStatus("testTracking_OutOfQuota_ForegroundAndBackground", 5); 4498 setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg); 4499 4500 advanceElapsedClock(20 * SECOND_IN_MILLIS); 4501 setProcessState(ActivityManager.PROCESS_STATE_TOP); 4502 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) 4503 .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); 4504 trackJobs(jobFg, jobTop); 4505 synchronized (mQuotaController.mLock) { 4506 mQuotaController.prepareForExecutionLocked(jobTop); 4507 } 4508 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4509 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4510 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4511 4512 // App still in foreground so everything should be in quota. 4513 advanceElapsedClock(20 * SECOND_IN_MILLIS); 4514 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 4515 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4516 assertTrue(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4517 assertTrue(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4518 4519 advanceElapsedClock(20 * SECOND_IN_MILLIS); 4520 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 4521 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) 4522 .onControllerStateChanged(argThat(jobs -> jobs.size() == 2)); 4523 // App is now in background and out of quota. Fg should now change to out of quota since it 4524 // wasn't started. Top should remain in quota since it started when the app was in TOP. 4525 assertTrue(jobTop.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4526 assertFalse(jobFg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4527 assertFalse(jobBg.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4528 trackJobs(jobBg2); 4529 assertFalse(jobBg2.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4530 } 4531 4532 /** 4533 * Tests that a job is properly updated and JobSchedulerService is notified when a job reaches 4534 * its quota. 4535 */ 4536 @Test testTracking_OutOfQuota()4537 public void testTracking_OutOfQuota() { 4538 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); 4539 synchronized (mQuotaController.mLock) { 4540 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 4541 } 4542 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window 4543 setProcessState(ActivityManager.PROCESS_STATE_HOME); 4544 // Now the package only has two seconds to run. 4545 final long remainingTimeMs = 2 * SECOND_IN_MILLIS; 4546 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4547 createTimingSession( 4548 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS, 4549 10 * MINUTE_IN_MILLIS - remainingTimeMs, 1), false); 4550 4551 // Start the job. 4552 synchronized (mQuotaController.mLock) { 4553 mQuotaController.prepareForExecutionLocked(jobStatus); 4554 } 4555 advanceElapsedClock(remainingTimeMs); 4556 4557 // Wait for some extra time to allow for job processing. 4558 verify(mJobSchedulerService, 4559 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(1)) 4560 .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); 4561 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4562 assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(), 4563 jobStatus.getWhenStandbyDeferred()); 4564 } 4565 4566 /** 4567 * Tests that a job is properly handled when it's at the edge of its quota and the old quota is 4568 * being phased out. 4569 */ 4570 @Test testTracking_RollingQuota()4571 public void testTracking_RollingQuota() { 4572 JobStatus jobStatus = createJobStatus("testTracking_OutOfQuota", 1); 4573 synchronized (mQuotaController.mLock) { 4574 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 4575 } 4576 setStandbyBucket(WORKING_INDEX, jobStatus); // 2 hour window 4577 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 4578 Handler handler = mQuotaController.getHandler(); 4579 spyOn(handler); 4580 4581 long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 4582 final long remainingTimeMs = SECOND_IN_MILLIS; 4583 // The package only has one second to run, but this session is at the edge of the rolling 4584 // window, so as the package "reaches its quota" it will have more to keep running. 4585 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4586 createTimingSession(now - 2 * HOUR_IN_MILLIS, 4587 10 * SECOND_IN_MILLIS - remainingTimeMs, 1), false); 4588 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4589 createTimingSession(now - HOUR_IN_MILLIS, 4590 9 * MINUTE_IN_MILLIS + 50 * SECOND_IN_MILLIS, 1), false); 4591 4592 synchronized (mQuotaController.mLock) { 4593 assertEquals(remainingTimeMs, 4594 mQuotaController.getRemainingExecutionTimeLocked(jobStatus)); 4595 4596 // Start the job. 4597 mQuotaController.prepareForExecutionLocked(jobStatus); 4598 } 4599 advanceElapsedClock(remainingTimeMs); 4600 4601 // Wait for some extra time to allow for job processing. 4602 verify(mJobSchedulerService, 4603 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) 4604 .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); 4605 assertTrue(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4606 // The job used up the remaining quota, but in that time, the same amount of time in the 4607 // old TimingSession also fell out of the quota window, so it should still have the same 4608 // amount of remaining time left its quota. 4609 synchronized (mQuotaController.mLock) { 4610 assertEquals(remainingTimeMs, 4611 mQuotaController.getRemainingExecutionTimeLocked( 4612 SOURCE_USER_ID, SOURCE_PACKAGE)); 4613 } 4614 // Handler is told to check when the quota will be consumed, not when the initial 4615 // remaining time is over. 4616 verify(handler, atLeast(1)).sendMessageDelayed( 4617 argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA), 4618 eq(10 * SECOND_IN_MILLIS)); 4619 verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); 4620 4621 // After 10 seconds, the job should finally be out of quota. 4622 advanceElapsedClock(10 * SECOND_IN_MILLIS - remainingTimeMs); 4623 // Wait for some extra time to allow for job processing. 4624 verify(mJobSchedulerService, 4625 timeout(12 * SECOND_IN_MILLIS).times(1)) 4626 .onControllerStateChanged(argThat(jobs -> jobs.size() == 1)); 4627 assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4628 verify(handler, never()).sendMessageDelayed(any(), anyInt()); 4629 } 4630 4631 /** 4632 * Tests that the start alarm is properly scheduled when a job has been throttled due to the job 4633 * count rate limiting. 4634 */ 4635 @Test testStartAlarmScheduled_JobCount_RateLimitingWindow()4636 public void testStartAlarmScheduled_JobCount_RateLimitingWindow() { 4637 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 4638 // because it schedules an alarm too. Prevent it from doing so. 4639 spyOn(mQuotaController); 4640 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 4641 4642 // Essentially disable session throttling. 4643 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, Integer.MAX_VALUE); 4644 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW, 4645 Integer.MAX_VALUE); 4646 4647 final int standbyBucket = WORKING_INDEX; 4648 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 4649 4650 // No sessions saved yet. 4651 synchronized (mQuotaController.mLock) { 4652 mQuotaController.maybeScheduleStartAlarmLocked( 4653 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 4654 } 4655 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 4656 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 4657 4658 // Ran jobs up to the job limit. All of them should be allowed to run. 4659 for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) { 4660 JobStatus job = createJobStatus("testStartAlarmScheduled_JobCount_AllowedTime", i); 4661 setStandbyBucket(WORKING_INDEX, job); 4662 synchronized (mQuotaController.mLock) { 4663 mQuotaController.maybeStartTrackingJobLocked(job, null); 4664 assertTrue(job.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4665 mQuotaController.prepareForExecutionLocked(job); 4666 } 4667 advanceElapsedClock(SECOND_IN_MILLIS); 4668 synchronized (mQuotaController.mLock) { 4669 mQuotaController.maybeStopTrackingJobLocked(job, null); 4670 } 4671 advanceElapsedClock(SECOND_IN_MILLIS); 4672 } 4673 // Start alarm shouldn't have been scheduled since the app was in quota up until this point. 4674 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 4675 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 4676 4677 // The app is now out of job count quota 4678 JobStatus throttledJob = createJobStatus( 4679 "testStartAlarmScheduled_JobCount_AllowedTime", 42); 4680 setStandbyBucket(WORKING_INDEX, throttledJob); 4681 synchronized (mQuotaController.mLock) { 4682 mQuotaController.maybeStartTrackingJobLocked(throttledJob, null); 4683 } 4684 assertFalse(throttledJob.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4685 4686 ExecutionStats stats; 4687 synchronized (mQuotaController.mLock) { 4688 stats = mQuotaController.getExecutionStatsLocked( 4689 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 4690 } 4691 final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed; 4692 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 4693 anyInt(), eq(expectedWorkingAlarmTime), anyLong(), 4694 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 4695 } 4696 4697 /** 4698 * Tests that the start alarm is properly scheduled when a job has been throttled due to the 4699 * session count rate limiting. 4700 */ 4701 @Test testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow()4702 public void testStartAlarmScheduled_TimingSessionCount_RateLimitingWindow() { 4703 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 4704 // because it schedules an alarm too. Prevent it from doing so. 4705 spyOn(mQuotaController); 4706 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 4707 4708 // Essentially disable job count throttling. 4709 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_FREQUENT, Integer.MAX_VALUE); 4710 setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW, 4711 Integer.MAX_VALUE); 4712 // Make sure throttling is because of COUNT_PER_RATE_LIMITING_WINDOW. 4713 setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_FREQUENT, 4714 mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW + 1); 4715 4716 final int standbyBucket = FREQUENT_INDEX; 4717 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 4718 4719 // No sessions saved yet. 4720 synchronized (mQuotaController.mLock) { 4721 mQuotaController.maybeScheduleStartAlarmLocked( 4722 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 4723 } 4724 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 4725 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 4726 4727 // Ran jobs up to the job limit. All of them should be allowed to run. 4728 for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) { 4729 JobStatus job = createJobStatus( 4730 "testStartAlarmScheduled_TimingSessionCount_AllowedTime", i); 4731 setStandbyBucket(FREQUENT_INDEX, job); 4732 synchronized (mQuotaController.mLock) { 4733 mQuotaController.maybeStartTrackingJobLocked(job, null); 4734 assertTrue("Constraint not satisfied for job #" + (i + 1), 4735 job.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4736 mQuotaController.prepareForExecutionLocked(job); 4737 } 4738 advanceElapsedClock(SECOND_IN_MILLIS); 4739 synchronized (mQuotaController.mLock) { 4740 mQuotaController.maybeStopTrackingJobLocked(job, null); 4741 } 4742 advanceElapsedClock(SECOND_IN_MILLIS); 4743 } 4744 // Start alarm shouldn't have been scheduled since the app was in quota up until this point. 4745 verify(mAlarmManager, timeout(1000).times(0)).setWindow( 4746 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 4747 4748 // The app is now out of session count quota 4749 JobStatus throttledJob = createJobStatus( 4750 "testStartAlarmScheduled_TimingSessionCount_AllowedTime", 42); 4751 synchronized (mQuotaController.mLock) { 4752 mQuotaController.maybeStartTrackingJobLocked(throttledJob, null); 4753 } 4754 assertFalse(throttledJob.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); 4755 assertEquals(JobSchedulerService.sElapsedRealtimeClock.millis(), 4756 throttledJob.getWhenStandbyDeferred()); 4757 4758 ExecutionStats stats; 4759 synchronized (mQuotaController.mLock) { 4760 stats = mQuotaController.getExecutionStatsLocked( 4761 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 4762 } 4763 final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed; 4764 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 4765 anyInt(), eq(expectedWorkingAlarmTime), anyLong(), 4766 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 4767 } 4768 4769 @Test testGetRemainingEJExecutionTimeLocked_NoHistory()4770 public void testGetRemainingEJExecutionTimeLocked_NoHistory() { 4771 final long[] limits = mQuotaController.getEJLimitsMs(); 4772 for (int i = 0; i < limits.length; ++i) { 4773 setStandbyBucket(i); 4774 assertEquals("Got wrong remaining EJ execution time for bucket #" + i, 4775 limits[i], 4776 mQuotaController.getRemainingEJExecutionTimeLocked( 4777 SOURCE_USER_ID, SOURCE_PACKAGE)); 4778 } 4779 } 4780 4781 @Test testGetRemainingEJExecutionTimeLocked_AllSessionsWithinWindow()4782 public void testGetRemainingEJExecutionTimeLocked_AllSessionsWithinWindow() { 4783 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 4784 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4785 createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS, MINUTE_IN_MILLIS, 5), 4786 true); 4787 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4788 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4789 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4790 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4791 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4792 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4793 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4794 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4795 4796 final long[] limits = mQuotaController.getEJLimitsMs(); 4797 for (int i = 0; i < limits.length; ++i) { 4798 setStandbyBucket(i); 4799 assertEquals("Got wrong remaining EJ execution time for bucket #" + i, 4800 i == NEVER_INDEX ? 0 : (limits[i] - 5 * MINUTE_IN_MILLIS), 4801 mQuotaController.getRemainingEJExecutionTimeLocked( 4802 SOURCE_USER_ID, SOURCE_PACKAGE)); 4803 } 4804 } 4805 4806 @Test testGetRemainingEJExecutionTimeLocked_Installer()4807 public void testGetRemainingEJExecutionTimeLocked_Installer() { 4808 PackageInfo pi = new PackageInfo(); 4809 pi.packageName = SOURCE_PACKAGE; 4810 pi.requestedPermissions = new String[]{Manifest.permission.INSTALL_PACKAGES}; 4811 pi.requestedPermissionsFlags = new int[]{PackageInfo.REQUESTED_PERMISSION_GRANTED}; 4812 pi.applicationInfo = new ApplicationInfo(); 4813 pi.applicationInfo.uid = mSourceUid; 4814 doReturn(List.of(pi)).when(mPackageManager).getInstalledPackagesAsUser(anyInt(), anyInt()); 4815 doReturn(PackageManager.PERMISSION_GRANTED).when(mContext).checkPermission( 4816 eq(Manifest.permission.INSTALL_PACKAGES), anyInt(), eq(mSourceUid)); 4817 mQuotaController.onSystemServicesReady(); 4818 4819 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 4820 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4821 createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS, MINUTE_IN_MILLIS, 5), 4822 true); 4823 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4824 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4825 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4826 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4827 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4828 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4829 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4830 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4831 4832 final long[] limits = mQuotaController.getEJLimitsMs(); 4833 for (int i = 0; i < limits.length; ++i) { 4834 setStandbyBucket(i); 4835 assertEquals("Got wrong remaining EJ execution time for bucket #" + i, 4836 i == NEVER_INDEX ? 0 4837 : (limits[i] + mQuotaController.getEjLimitAdditionInstallerMs() 4838 - 5 * MINUTE_IN_MILLIS), 4839 mQuotaController.getRemainingEJExecutionTimeLocked( 4840 SOURCE_USER_ID, SOURCE_PACKAGE)); 4841 } 4842 } 4843 4844 @Test testGetRemainingEJExecutionTimeLocked_OneSessionStraddlesEdge()4845 public void testGetRemainingEJExecutionTimeLocked_OneSessionStraddlesEdge() { 4846 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 4847 final long[] limits = mQuotaController.getEJLimitsMs(); 4848 for (int i = 0; i < limits.length; ++i) { 4849 synchronized (mQuotaController.mLock) { 4850 mQuotaController.onUserRemovedLocked(SOURCE_USER_ID); 4851 } 4852 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4853 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + MINUTE_IN_MILLIS), 4854 2 * MINUTE_IN_MILLIS, 5), true); 4855 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4856 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4857 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4858 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4859 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4860 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4861 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4862 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4863 4864 setStandbyBucket(i); 4865 assertEquals("Got wrong remaining EJ execution time for bucket #" + i, 4866 i == NEVER_INDEX ? 0 : (limits[i] - 5 * MINUTE_IN_MILLIS), 4867 mQuotaController.getRemainingEJExecutionTimeLocked( 4868 SOURCE_USER_ID, SOURCE_PACKAGE)); 4869 } 4870 } 4871 4872 @Test testGetRemainingEJExecutionTimeLocked_WithStaleSessions()4873 public void testGetRemainingEJExecutionTimeLocked_WithStaleSessions() { 4874 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 4875 4876 final long[] limits = mQuotaController.getEJLimitsMs(); 4877 for (int i = 0; i < limits.length; ++i) { 4878 synchronized (mQuotaController.mLock) { 4879 mQuotaController.onUserRemovedLocked(SOURCE_USER_ID); 4880 } 4881 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4882 createTimingSession( 4883 now - (mQcConstants.EJ_WINDOW_SIZE_MS + 10 * MINUTE_IN_MILLIS), 4884 2 * MINUTE_IN_MILLIS, 5), true); 4885 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4886 createTimingSession( 4887 now - (mQcConstants.EJ_WINDOW_SIZE_MS + 5 * MINUTE_IN_MILLIS), 4888 MINUTE_IN_MILLIS, 5), true); 4889 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4890 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + MINUTE_IN_MILLIS), 4891 2 * MINUTE_IN_MILLIS, 5), true); 4892 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4893 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4894 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4895 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4896 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4897 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4898 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4899 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4900 4901 setStandbyBucket(i); 4902 assertEquals("Got wrong remaining EJ execution time for bucket #" + i, 4903 i == NEVER_INDEX ? 0 : (limits[i] - 5 * MINUTE_IN_MILLIS), 4904 mQuotaController.getRemainingEJExecutionTimeLocked( 4905 SOURCE_USER_ID, SOURCE_PACKAGE)); 4906 } 4907 } 4908 4909 /** 4910 * Tests that getRemainingEJExecutionTimeLocked returns the correct stats soon after device 4911 * startup. 4912 */ 4913 @Test testGetRemainingEJExecutionTimeLocked_BeginningOfTime()4914 public void testGetRemainingEJExecutionTimeLocked_BeginningOfTime() { 4915 // Set time to 3 minutes after boot. 4916 advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis()); 4917 advanceElapsedClock(3 * MINUTE_IN_MILLIS); 4918 4919 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4920 createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), true); 4921 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4922 createTimingSession(150 * SECOND_IN_MILLIS, 15 * SECOND_IN_MILLIS, 5), true); 4923 4924 final long[] limits = mQuotaController.getEJLimitsMs(); 4925 for (int i = 0; i < limits.length; ++i) { 4926 setStandbyBucket(i); 4927 assertEquals("Got wrong remaining EJ execution time for bucket #" + i, 4928 i == NEVER_INDEX ? 0 : (limits[i] - 75 * SECOND_IN_MILLIS), 4929 mQuotaController.getRemainingEJExecutionTimeLocked( 4930 SOURCE_USER_ID, SOURCE_PACKAGE)); 4931 } 4932 } 4933 4934 @Test testGetRemainingEJExecutionTimeLocked_IncrementalTimingSessions()4935 public void testGetRemainingEJExecutionTimeLocked_IncrementalTimingSessions() { 4936 setDischarging(); 4937 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 4938 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 20 * MINUTE_IN_MILLIS); 4939 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 15 * MINUTE_IN_MILLIS); 4940 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 13 * MINUTE_IN_MILLIS); 4941 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); 4942 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS); 4943 4944 for (int i = 1; i <= 25; ++i) { 4945 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4946 createTimingSession(now - ((60 - i) * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 4947 2), true); 4948 4949 synchronized (mQuotaController.mLock) { 4950 setStandbyBucket(ACTIVE_INDEX); 4951 assertEquals("Active has incorrect remaining EJ time with " + i + " sessions", 4952 (20 - i) * MINUTE_IN_MILLIS, 4953 mQuotaController.getRemainingEJExecutionTimeLocked( 4954 SOURCE_USER_ID, SOURCE_PACKAGE)); 4955 4956 setStandbyBucket(WORKING_INDEX); 4957 assertEquals("Working has incorrect remaining EJ time with " + i + " sessions", 4958 (15 - i) * MINUTE_IN_MILLIS, 4959 mQuotaController.getRemainingEJExecutionTimeLocked( 4960 SOURCE_USER_ID, SOURCE_PACKAGE)); 4961 4962 setStandbyBucket(FREQUENT_INDEX); 4963 assertEquals("Frequent has incorrect remaining EJ time with " + i + " sessions", 4964 (13 - i) * MINUTE_IN_MILLIS, 4965 mQuotaController.getRemainingEJExecutionTimeLocked( 4966 SOURCE_USER_ID, SOURCE_PACKAGE)); 4967 4968 setStandbyBucket(RARE_INDEX); 4969 assertEquals("Rare has incorrect remaining EJ time with " + i + " sessions", 4970 (10 - i) * MINUTE_IN_MILLIS, 4971 mQuotaController.getRemainingEJExecutionTimeLocked( 4972 SOURCE_USER_ID, SOURCE_PACKAGE)); 4973 4974 setStandbyBucket(RESTRICTED_INDEX); 4975 assertEquals("Restricted has incorrect remaining EJ time with " + i + " sessions", 4976 (5 - i) * MINUTE_IN_MILLIS, 4977 mQuotaController.getRemainingEJExecutionTimeLocked( 4978 SOURCE_USER_ID, SOURCE_PACKAGE)); 4979 } 4980 } 4981 } 4982 4983 @Test testGetTimeUntilEJQuotaConsumedLocked_NoHistory()4984 public void testGetTimeUntilEJQuotaConsumedLocked_NoHistory() { 4985 final long[] limits = mQuotaController.getEJLimitsMs(); 4986 for (int i = 0; i < limits.length; ++i) { 4987 setStandbyBucket(i); 4988 assertEquals("Got wrong time until EJ quota consumed for bucket #" + i, 4989 limits[i], mQuotaController.getTimeUntilEJQuotaConsumedLocked( 4990 SOURCE_USER_ID, SOURCE_PACKAGE)); 4991 } 4992 } 4993 4994 @Test testGetTimeUntilEJQuotaConsumedLocked_AllSessionsWithinWindow()4995 public void testGetTimeUntilEJQuotaConsumedLocked_AllSessionsWithinWindow() { 4996 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 4997 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 4998 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 4999 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5000 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 5001 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5002 createTimingSession(now - 20 * MINUTE_IN_MILLIS, 2 * MINUTE_IN_MILLIS, 5), true); 5003 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5004 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 5005 5006 final long[] limits = mQuotaController.getEJLimitsMs(); 5007 for (int i = 0; i < limits.length; ++i) { 5008 setStandbyBucket(i); 5009 assertEquals("Got wrong time until EJ quota consumed for bucket #" + i, 5010 i == NEVER_INDEX ? 0 : (limits[i] - 5 * MINUTE_IN_MILLIS), 5011 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5012 SOURCE_USER_ID, SOURCE_PACKAGE)); 5013 } 5014 } 5015 5016 @Test testGetTimeUntilEJQuotaConsumedLocked_SessionsAtEdgeOfWindow()5017 public void testGetTimeUntilEJQuotaConsumedLocked_SessionsAtEdgeOfWindow() { 5018 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 5019 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5020 createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS, MINUTE_IN_MILLIS, 5), 5021 true); 5022 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5023 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS - 2 * MINUTE_IN_MILLIS), 5024 MINUTE_IN_MILLIS, 5), true); 5025 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5026 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS - 10 * MINUTE_IN_MILLIS), 5027 MINUTE_IN_MILLIS, 5), true); 5028 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5029 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 5030 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5031 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 5032 5033 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS); 5034 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS); 5035 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS); 5036 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); 5037 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS); 5038 5039 setStandbyBucket(ACTIVE_INDEX); 5040 assertEquals("Got wrong time until EJ quota consumed for bucket #" + ACTIVE_INDEX, 5041 28 * MINUTE_IN_MILLIS, 5042 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5043 SOURCE_USER_ID, SOURCE_PACKAGE)); 5044 5045 setStandbyBucket(WORKING_INDEX); 5046 assertEquals("Got wrong time until EJ quota consumed for bucket #" + WORKING_INDEX, 5047 18 * MINUTE_IN_MILLIS, 5048 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5049 SOURCE_USER_ID, SOURCE_PACKAGE)); 5050 5051 setStandbyBucket(FREQUENT_INDEX); 5052 assertEquals("Got wrong time until EJ quota consumed for bucket #" + FREQUENT_INDEX, 5053 13 * MINUTE_IN_MILLIS, 5054 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5055 SOURCE_USER_ID, SOURCE_PACKAGE)); 5056 5057 setStandbyBucket(RARE_INDEX); 5058 assertEquals("Got wrong time until EJ quota consumed for bucket #" + RARE_INDEX, 5059 7 * MINUTE_IN_MILLIS, 5060 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5061 SOURCE_USER_ID, SOURCE_PACKAGE)); 5062 5063 setStandbyBucket(RESTRICTED_INDEX); 5064 assertEquals("Got wrong time until EJ quota consumed for bucket #" + RESTRICTED_INDEX, 5065 MINUTE_IN_MILLIS, 5066 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5067 SOURCE_USER_ID, SOURCE_PACKAGE)); 5068 } 5069 5070 @Test testGetTimeUntilEJQuotaConsumedLocked_OneSessionStraddlesEdge()5071 public void testGetTimeUntilEJQuotaConsumedLocked_OneSessionStraddlesEdge() { 5072 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 5073 5074 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5075 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + MINUTE_IN_MILLIS), 5076 2 * MINUTE_IN_MILLIS, 5), true); 5077 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5078 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 5079 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5080 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 5081 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5082 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 5083 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5084 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5), true); 5085 5086 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS); 5087 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS); 5088 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS); 5089 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); 5090 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS); 5091 5092 setStandbyBucket(ACTIVE_INDEX); 5093 assertEquals("Got wrong time until EJ quota consumed for bucket #" + ACTIVE_INDEX, 5094 26 * MINUTE_IN_MILLIS, 5095 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5096 SOURCE_USER_ID, SOURCE_PACKAGE)); 5097 5098 setStandbyBucket(WORKING_INDEX); 5099 assertEquals("Got wrong time until EJ quota consumed for bucket #" + WORKING_INDEX, 5100 16 * MINUTE_IN_MILLIS, 5101 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5102 SOURCE_USER_ID, SOURCE_PACKAGE)); 5103 5104 setStandbyBucket(FREQUENT_INDEX); 5105 assertEquals("Got wrong time until EJ quota consumed for bucket #" + FREQUENT_INDEX, 5106 11 * MINUTE_IN_MILLIS, 5107 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5108 SOURCE_USER_ID, SOURCE_PACKAGE)); 5109 5110 setStandbyBucket(RARE_INDEX); 5111 assertEquals("Got wrong time until EJ quota consumed for bucket #" + RARE_INDEX, 5112 6 * MINUTE_IN_MILLIS, 5113 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5114 SOURCE_USER_ID, SOURCE_PACKAGE)); 5115 5116 setStandbyBucket(RESTRICTED_INDEX); 5117 assertEquals("Got wrong time until EJ quota consumed for bucket #" + RESTRICTED_INDEX, 5118 MINUTE_IN_MILLIS, 5119 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5120 SOURCE_USER_ID, SOURCE_PACKAGE)); 5121 } 5122 5123 @Test testGetTimeUntilEJQuotaConsumedLocked_WithStaleSessions()5124 public void testGetTimeUntilEJQuotaConsumedLocked_WithStaleSessions() { 5125 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 5126 5127 List<TimingSession> timingSessions = new ArrayList<>(); 5128 timingSessions.add( 5129 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + 10 * MINUTE_IN_MILLIS), 5130 2 * MINUTE_IN_MILLIS, 5)); 5131 timingSessions.add( 5132 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + 5 * MINUTE_IN_MILLIS), 5133 MINUTE_IN_MILLIS, 5)); 5134 timingSessions.add( 5135 createTimingSession(now - (mQcConstants.EJ_WINDOW_SIZE_MS + MINUTE_IN_MILLIS), 5136 2 * MINUTE_IN_MILLIS, 5)); 5137 timingSessions.add( 5138 createTimingSession(now - 40 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5)); 5139 timingSessions.add( 5140 createTimingSession(now - 30 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5)); 5141 timingSessions.add( 5142 createTimingSession(now - 20 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5)); 5143 timingSessions.add( 5144 createTimingSession(now - 10 * MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 5)); 5145 5146 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS); 5147 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS); 5148 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS); 5149 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); 5150 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RESTRICTED_MS, 5 * MINUTE_IN_MILLIS); 5151 5152 runTestGetTimeUntilEJQuotaConsumedLocked( 5153 timingSessions, ACTIVE_INDEX, 26 * MINUTE_IN_MILLIS); 5154 runTestGetTimeUntilEJQuotaConsumedLocked( 5155 timingSessions, WORKING_INDEX, 16 * MINUTE_IN_MILLIS); 5156 runTestGetTimeUntilEJQuotaConsumedLocked( 5157 timingSessions, FREQUENT_INDEX, 11 * MINUTE_IN_MILLIS); 5158 runTestGetTimeUntilEJQuotaConsumedLocked(timingSessions, RARE_INDEX, 6 * MINUTE_IN_MILLIS); 5159 runTestGetTimeUntilEJQuotaConsumedLocked( 5160 timingSessions, RESTRICTED_INDEX, MINUTE_IN_MILLIS); 5161 } 5162 5163 /** 5164 * Tests that getTimeUntilEJQuotaConsumedLocked returns the correct stats soon after device 5165 * startup. 5166 */ 5167 @Test testGetTimeUntilEJQuotaConsumedLocked_BeginningOfTime()5168 public void testGetTimeUntilEJQuotaConsumedLocked_BeginningOfTime() { 5169 // Set time to 3 minutes after boot. 5170 advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis()); 5171 advanceElapsedClock(3 * MINUTE_IN_MILLIS); 5172 5173 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5174 createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), true); 5175 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5176 createTimingSession(150 * SECOND_IN_MILLIS, 15 * SECOND_IN_MILLIS, 5), true); 5177 5178 final long[] limits = mQuotaController.getEJLimitsMs(); 5179 for (int i = 0; i < limits.length; ++i) { 5180 setStandbyBucket(i); 5181 assertEquals("Got wrong time until EJ quota consumed for bucket #" + i, 5182 limits[i], // All existing sessions will phase out 5183 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5184 SOURCE_USER_ID, SOURCE_PACKAGE)); 5185 } 5186 } 5187 runTestGetTimeUntilEJQuotaConsumedLocked( List<TimingSession> timingSessions, int bucketIndex, long expectedValue)5188 private void runTestGetTimeUntilEJQuotaConsumedLocked( 5189 List<TimingSession> timingSessions, int bucketIndex, long expectedValue) { 5190 synchronized (mQuotaController.mLock) { 5191 mQuotaController.onUserRemovedLocked(SOURCE_USER_ID); 5192 } 5193 if (timingSessions != null) { 5194 for (TimingSession session : timingSessions) { 5195 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, session, true); 5196 } 5197 } 5198 5199 setStandbyBucket(bucketIndex); 5200 assertEquals("Got wrong time until EJ quota consumed for bucket #" + bucketIndex, 5201 expectedValue, 5202 mQuotaController.getTimeUntilEJQuotaConsumedLocked( 5203 SOURCE_USER_ID, SOURCE_PACKAGE)); 5204 } 5205 5206 @Test testMaybeScheduleStartAlarmLocked_EJ()5207 public void testMaybeScheduleStartAlarmLocked_EJ() { 5208 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 5209 // because it schedules an alarm too. Prevent it from doing so. 5210 spyOn(mQuotaController); 5211 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 5212 5213 synchronized (mQuotaController.mLock) { 5214 mQuotaController.maybeStartTrackingJobLocked( 5215 createJobStatus("testMaybeScheduleStartAlarmLocked_EJ", 1), null); 5216 } 5217 5218 final int standbyBucket = WORKING_INDEX; 5219 setStandbyBucket(standbyBucket); 5220 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS); 5221 5222 InOrder inOrder = inOrder(mAlarmManager); 5223 5224 synchronized (mQuotaController.mLock) { 5225 // No sessions saved yet. 5226 mQuotaController.maybeScheduleStartAlarmLocked( 5227 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 5228 } 5229 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 5230 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5231 any(Handler.class)); 5232 5233 // Test with timing sessions out of window. 5234 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 5235 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5236 createTimingSession(now - 25 * HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS, 1), true); 5237 synchronized (mQuotaController.mLock) { 5238 mQuotaController.maybeScheduleStartAlarmLocked( 5239 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 5240 } 5241 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 5242 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5243 any(Handler.class)); 5244 5245 // Test with timing sessions in window but still in quota. 5246 final long end = now - (22 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); 5247 final long expectedAlarmTime = now + 2 * HOUR_IN_MILLIS + mQcConstants.IN_QUOTA_BUFFER_MS; 5248 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5249 new TimingSession(now - 22 * HOUR_IN_MILLIS, end, 1), true); 5250 synchronized (mQuotaController.mLock) { 5251 mQuotaController.maybeScheduleStartAlarmLocked( 5252 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 5253 } 5254 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 5255 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5256 any(Handler.class)); 5257 5258 // Add some more sessions, but still in quota. 5259 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5260 createTimingSession(now - HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 1), true); 5261 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5262 createTimingSession(now - (50 * MINUTE_IN_MILLIS), 4 * MINUTE_IN_MILLIS, 1), true); 5263 synchronized (mQuotaController.mLock) { 5264 mQuotaController.maybeScheduleStartAlarmLocked( 5265 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 5266 } 5267 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 5268 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5269 any(Handler.class)); 5270 5271 // Test when out of quota. 5272 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5273 createTimingSession(now - 30 * MINUTE_IN_MILLIS, 6 * MINUTE_IN_MILLIS, 1), true); 5274 synchronized (mQuotaController.mLock) { 5275 mQuotaController.maybeScheduleStartAlarmLocked( 5276 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 5277 } 5278 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 5279 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5280 any(Handler.class)); 5281 5282 // Alarm already scheduled, so make sure it's not scheduled again. 5283 synchronized (mQuotaController.mLock) { 5284 mQuotaController.maybeScheduleStartAlarmLocked( 5285 SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); 5286 } 5287 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 5288 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5289 any(Handler.class)); 5290 } 5291 5292 /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */ 5293 @Test testMaybeScheduleStartAlarmLocked_Ej_BucketChange()5294 public void testMaybeScheduleStartAlarmLocked_Ej_BucketChange() { 5295 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 5296 // because it schedules an alarm too. Prevent it from doing so. 5297 spyOn(mQuotaController); 5298 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 5299 5300 synchronized (mQuotaController.mLock) { 5301 mQuotaController.maybeStartTrackingJobLocked( 5302 createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_BucketChange", 1), null); 5303 } 5304 5305 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_ACTIVE_MS, 30 * MINUTE_IN_MILLIS); 5306 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 20 * MINUTE_IN_MILLIS); 5307 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_FREQUENT_MS, 15 * MINUTE_IN_MILLIS); 5308 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); 5309 5310 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 5311 // Affects active bucket 5312 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5313 createTimingSession(now - 12 * HOUR_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3), true); 5314 // Affects active and working buckets 5315 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5316 createTimingSession(now - 4 * HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 3), true); 5317 // Affects active, working, and frequent buckets 5318 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5319 createTimingSession(now - HOUR_IN_MILLIS, 5 * MINUTE_IN_MILLIS, 10), true); 5320 // Affects all buckets 5321 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5322 createTimingSession(now - 5 * MINUTE_IN_MILLIS, 10 * MINUTE_IN_MILLIS, 3), true); 5323 5324 InOrder inOrder = inOrder(mAlarmManager); 5325 5326 // Start in ACTIVE bucket. 5327 setStandbyBucket(ACTIVE_INDEX); 5328 synchronized (mQuotaController.mLock) { 5329 mQuotaController.maybeScheduleStartAlarmLocked( 5330 SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); 5331 } 5332 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 5333 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5334 any(Handler.class)); 5335 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 5336 .cancel(any(AlarmManager.OnAlarmListener.class)); 5337 5338 // And down from there. 5339 setStandbyBucket(WORKING_INDEX); 5340 final long expectedWorkingAlarmTime = 5341 (now - 4 * HOUR_IN_MILLIS) + (24 * HOUR_IN_MILLIS) 5342 + mQcConstants.IN_QUOTA_BUFFER_MS; 5343 synchronized (mQuotaController.mLock) { 5344 mQuotaController.maybeScheduleStartAlarmLocked( 5345 SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); 5346 } 5347 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 5348 anyInt(), eq(expectedWorkingAlarmTime), anyLong(), 5349 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 5350 5351 setStandbyBucket(FREQUENT_INDEX); 5352 final long expectedFrequentAlarmTime = 5353 (now - HOUR_IN_MILLIS) + (24 * HOUR_IN_MILLIS) + mQcConstants.IN_QUOTA_BUFFER_MS; 5354 synchronized (mQuotaController.mLock) { 5355 mQuotaController.maybeScheduleStartAlarmLocked( 5356 SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX); 5357 } 5358 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 5359 anyInt(), eq(expectedFrequentAlarmTime), anyLong(), 5360 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 5361 5362 setStandbyBucket(RARE_INDEX); 5363 final long expectedRareAlarmTime = 5364 (now - 5 * MINUTE_IN_MILLIS) + (24 * HOUR_IN_MILLIS) 5365 + mQcConstants.IN_QUOTA_BUFFER_MS; 5366 synchronized (mQuotaController.mLock) { 5367 mQuotaController.maybeScheduleStartAlarmLocked( 5368 SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX); 5369 } 5370 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 5371 anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5372 any(Handler.class)); 5373 5374 // And back up again. 5375 setStandbyBucket(FREQUENT_INDEX); 5376 synchronized (mQuotaController.mLock) { 5377 mQuotaController.maybeScheduleStartAlarmLocked( 5378 SOURCE_USER_ID, SOURCE_PACKAGE, FREQUENT_INDEX); 5379 } 5380 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 5381 anyInt(), eq(expectedFrequentAlarmTime), anyLong(), 5382 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 5383 5384 setStandbyBucket(WORKING_INDEX); 5385 synchronized (mQuotaController.mLock) { 5386 mQuotaController.maybeScheduleStartAlarmLocked( 5387 SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); 5388 } 5389 inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow( 5390 anyInt(), eq(expectedWorkingAlarmTime), anyLong(), 5391 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 5392 5393 setStandbyBucket(ACTIVE_INDEX); 5394 synchronized (mQuotaController.mLock) { 5395 mQuotaController.maybeScheduleStartAlarmLocked( 5396 SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX); 5397 } 5398 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 5399 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5400 any(Handler.class)); 5401 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 5402 .cancel(any(AlarmManager.OnAlarmListener.class)); 5403 } 5404 5405 /** 5406 * Tests that the start alarm is properly rescheduled if the earliest session that contributes 5407 * to the app being out of quota contributes less than the quota buffer time. 5408 */ 5409 @Test testMaybeScheduleStartAlarmLocked_Ej_SmallRollingQuota()5410 public void testMaybeScheduleStartAlarmLocked_Ej_SmallRollingQuota() { 5411 // saveTimingSession calls maybeScheduleCleanupAlarmLocked which interferes with these tests 5412 // because it schedules an alarm too. Prevent it from doing so. 5413 spyOn(mQuotaController); 5414 doNothing().when(mQuotaController).maybeScheduleCleanupAlarmLocked(); 5415 5416 synchronized (mQuotaController.mLock) { 5417 mQuotaController.maybeStartTrackingJobLocked( 5418 createJobStatus("testMaybeScheduleStartAlarmLocked_Ej_SRQ", 1), null); 5419 } 5420 5421 final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 5422 setStandbyBucket(WORKING_INDEX); 5423 final long contributionMs = mQcConstants.IN_QUOTA_BUFFER_MS / 2; 5424 final long remainingTimeMs = mQcConstants.EJ_LIMIT_WORKING_MS - contributionMs; 5425 5426 // Session straddles edge of bucket window. Only the contribution should be counted towards 5427 // the quota. 5428 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5429 createTimingSession(now - (24 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS), 5430 3 * MINUTE_IN_MILLIS + contributionMs, 3), true); 5431 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 5432 createTimingSession(now - 23 * HOUR_IN_MILLIS, remainingTimeMs, 2), true); 5433 // Expected alarm time should be when the app will have QUOTA_BUFFER_MS time of quota, which 5434 // is 24 hours + (QUOTA_BUFFER_MS - contributionMs) after the start of the second session. 5435 final long expectedAlarmTime = 5436 now + HOUR_IN_MILLIS + (mQcConstants.IN_QUOTA_BUFFER_MS - contributionMs); 5437 synchronized (mQuotaController.mLock) { 5438 mQuotaController.maybeScheduleStartAlarmLocked( 5439 SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); 5440 } 5441 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 5442 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 5443 any(Handler.class)); 5444 } 5445 5446 /** Tests that TimingSessions aren't saved when the device is charging. */ 5447 @Test testEJTimerTracking_Charging()5448 public void testEJTimerTracking_Charging() { 5449 setCharging(); 5450 5451 JobStatus jobStatus = createExpeditedJobStatus("testEJTimerTracking_Charging", 1); 5452 synchronized (mQuotaController.mLock) { 5453 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 5454 } 5455 5456 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5457 5458 synchronized (mQuotaController.mLock) { 5459 mQuotaController.prepareForExecutionLocked(jobStatus); 5460 } 5461 advanceElapsedClock(5 * SECOND_IN_MILLIS); 5462 synchronized (mQuotaController.mLock) { 5463 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 5464 } 5465 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5466 } 5467 5468 /** Tests that TimingSessions are saved properly when the device is discharging. */ 5469 @Test testEJTimerTracking_Discharging()5470 public void testEJTimerTracking_Discharging() { 5471 setDischarging(); 5472 setProcessState(ActivityManager.PROCESS_STATE_BACKUP); 5473 5474 JobStatus jobStatus = createExpeditedJobStatus("testEJTimerTracking_Discharging", 1); 5475 synchronized (mQuotaController.mLock) { 5476 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 5477 } 5478 5479 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5480 5481 List<TimingSession> expected = new ArrayList<>(); 5482 5483 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5484 synchronized (mQuotaController.mLock) { 5485 mQuotaController.prepareForExecutionLocked(jobStatus); 5486 } 5487 advanceElapsedClock(5 * SECOND_IN_MILLIS); 5488 synchronized (mQuotaController.mLock) { 5489 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 5490 } 5491 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); 5492 assertEquals(expected, 5493 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5494 5495 // Test overlapping jobs. 5496 JobStatus jobStatus2 = createExpeditedJobStatus("testEJTimerTracking_Discharging", 2); 5497 synchronized (mQuotaController.mLock) { 5498 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 5499 } 5500 5501 JobStatus jobStatus3 = createExpeditedJobStatus("testEJTimerTracking_Discharging", 3); 5502 synchronized (mQuotaController.mLock) { 5503 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); 5504 } 5505 5506 advanceElapsedClock(SECOND_IN_MILLIS); 5507 5508 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5509 synchronized (mQuotaController.mLock) { 5510 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 5511 mQuotaController.prepareForExecutionLocked(jobStatus); 5512 } 5513 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5514 synchronized (mQuotaController.mLock) { 5515 mQuotaController.prepareForExecutionLocked(jobStatus2); 5516 } 5517 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5518 synchronized (mQuotaController.mLock) { 5519 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 5520 } 5521 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5522 synchronized (mQuotaController.mLock) { 5523 mQuotaController.prepareForExecutionLocked(jobStatus3); 5524 } 5525 advanceElapsedClock(20 * SECOND_IN_MILLIS); 5526 synchronized (mQuotaController.mLock) { 5527 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); 5528 } 5529 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5530 synchronized (mQuotaController.mLock) { 5531 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); 5532 } 5533 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); 5534 assertEquals(expected, 5535 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5536 } 5537 5538 /** 5539 * Tests that TimingSessions are saved properly when the device alternates between 5540 * charging and discharging. 5541 */ 5542 @Test testEJTimerTracking_ChargingAndDischarging()5543 public void testEJTimerTracking_ChargingAndDischarging() { 5544 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 5545 5546 JobStatus jobStatus = 5547 createExpeditedJobStatus("testEJTimerTracking_ChargingAndDischarging", 1); 5548 synchronized (mQuotaController.mLock) { 5549 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 5550 } 5551 JobStatus jobStatus2 = 5552 createExpeditedJobStatus("testEJTimerTracking_ChargingAndDischarging", 2); 5553 synchronized (mQuotaController.mLock) { 5554 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 5555 } 5556 JobStatus jobStatus3 = 5557 createExpeditedJobStatus("testEJTimerTracking_ChargingAndDischarging", 3); 5558 synchronized (mQuotaController.mLock) { 5559 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); 5560 } 5561 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5562 List<TimingSession> expected = new ArrayList<>(); 5563 5564 // A job starting while charging. Only the portion that runs during the discharging period 5565 // should be counted. 5566 setCharging(); 5567 5568 synchronized (mQuotaController.mLock) { 5569 mQuotaController.prepareForExecutionLocked(jobStatus); 5570 } 5571 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5572 setDischarging(); 5573 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5574 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5575 synchronized (mQuotaController.mLock) { 5576 mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus); 5577 } 5578 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 5579 assertEquals(expected, 5580 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5581 5582 advanceElapsedClock(SECOND_IN_MILLIS); 5583 5584 // One job starts while discharging, spans a charging session, and ends after the charging 5585 // session. Only the portions during the discharging periods should be counted. This should 5586 // result in two TimingSessions. A second job starts while discharging and ends within the 5587 // charging session. Only the portion during the first discharging portion should be 5588 // counted. A third job starts and ends within the charging session. The third job 5589 // shouldn't be included in either job count. 5590 setDischarging(); 5591 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5592 synchronized (mQuotaController.mLock) { 5593 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 5594 mQuotaController.prepareForExecutionLocked(jobStatus); 5595 } 5596 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5597 synchronized (mQuotaController.mLock) { 5598 mQuotaController.prepareForExecutionLocked(jobStatus2); 5599 } 5600 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5601 setCharging(); 5602 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); 5603 synchronized (mQuotaController.mLock) { 5604 mQuotaController.prepareForExecutionLocked(jobStatus3); 5605 } 5606 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5607 synchronized (mQuotaController.mLock) { 5608 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); 5609 } 5610 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5611 synchronized (mQuotaController.mLock) { 5612 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); 5613 } 5614 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5615 setDischarging(); 5616 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5617 advanceElapsedClock(20 * SECOND_IN_MILLIS); 5618 synchronized (mQuotaController.mLock) { 5619 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 5620 } 5621 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1)); 5622 assertEquals(expected, 5623 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5624 5625 // A job starting while discharging and ending while charging. Only the portion that runs 5626 // during the discharging period should be counted. 5627 setDischarging(); 5628 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5629 synchronized (mQuotaController.mLock) { 5630 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 5631 mQuotaController.prepareForExecutionLocked(jobStatus2); 5632 } 5633 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5634 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 5635 setCharging(); 5636 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5637 synchronized (mQuotaController.mLock) { 5638 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); 5639 } 5640 assertEquals(expected, 5641 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5642 } 5643 5644 /** Tests that TimingSessions are saved properly when all the jobs are background jobs. */ 5645 @Test testEJTimerTracking_AllBackground()5646 public void testEJTimerTracking_AllBackground() { 5647 setDischarging(); 5648 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 5649 5650 JobStatus jobStatus = createExpeditedJobStatus("testEJTimerTracking_AllBackground", 1); 5651 synchronized (mQuotaController.mLock) { 5652 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 5653 } 5654 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5655 5656 List<TimingSession> expected = new ArrayList<>(); 5657 5658 // Test single job. 5659 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5660 synchronized (mQuotaController.mLock) { 5661 mQuotaController.prepareForExecutionLocked(jobStatus); 5662 } 5663 advanceElapsedClock(5 * SECOND_IN_MILLIS); 5664 synchronized (mQuotaController.mLock) { 5665 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 5666 } 5667 expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1)); 5668 assertEquals(expected, 5669 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5670 5671 // Test overlapping jobs. 5672 JobStatus jobStatus2 = createExpeditedJobStatus("testEJTimerTracking_AllBackground", 2); 5673 synchronized (mQuotaController.mLock) { 5674 mQuotaController.maybeStartTrackingJobLocked(jobStatus2, null); 5675 } 5676 5677 JobStatus jobStatus3 = createExpeditedJobStatus("testEJTimerTracking_AllBackground", 3); 5678 synchronized (mQuotaController.mLock) { 5679 mQuotaController.maybeStartTrackingJobLocked(jobStatus3, null); 5680 } 5681 5682 advanceElapsedClock(SECOND_IN_MILLIS); 5683 5684 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5685 synchronized (mQuotaController.mLock) { 5686 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 5687 mQuotaController.prepareForExecutionLocked(jobStatus); 5688 } 5689 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5690 synchronized (mQuotaController.mLock) { 5691 mQuotaController.prepareForExecutionLocked(jobStatus2); 5692 } 5693 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5694 synchronized (mQuotaController.mLock) { 5695 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 5696 } 5697 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5698 synchronized (mQuotaController.mLock) { 5699 mQuotaController.prepareForExecutionLocked(jobStatus3); 5700 } 5701 advanceElapsedClock(20 * SECOND_IN_MILLIS); 5702 synchronized (mQuotaController.mLock) { 5703 mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null); 5704 } 5705 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5706 synchronized (mQuotaController.mLock) { 5707 mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null); 5708 } 5709 expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3)); 5710 assertEquals(expected, 5711 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5712 } 5713 5714 /** Tests that Timers don't count foreground jobs. */ 5715 @Test testEJTimerTracking_AllForeground()5716 public void testEJTimerTracking_AllForeground() { 5717 setDischarging(); 5718 5719 JobStatus jobStatus = createExpeditedJobStatus("testEJTimerTracking_AllForeground", 1); 5720 setProcessState(ActivityManager.PROCESS_STATE_TOP); 5721 synchronized (mQuotaController.mLock) { 5722 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 5723 } 5724 5725 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5726 5727 synchronized (mQuotaController.mLock) { 5728 mQuotaController.prepareForExecutionLocked(jobStatus); 5729 } 5730 advanceElapsedClock(5 * SECOND_IN_MILLIS); 5731 // Change to a state that should still be considered foreground. 5732 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 5733 advanceElapsedClock(5 * SECOND_IN_MILLIS); 5734 synchronized (mQuotaController.mLock) { 5735 mQuotaController.maybeStopTrackingJobLocked(jobStatus, null); 5736 } 5737 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5738 } 5739 5740 /** 5741 * Tests that Timers properly track sessions when switching between foreground and background 5742 * states. 5743 */ 5744 @Test testEJTimerTracking_ForegroundAndBackground()5745 public void testEJTimerTracking_ForegroundAndBackground() { 5746 setDischarging(); 5747 5748 JobStatus jobBg1 = 5749 createExpeditedJobStatus("testEJTimerTracking_ForegroundAndBackground", 1); 5750 JobStatus jobBg2 = 5751 createExpeditedJobStatus("testEJTimerTracking_ForegroundAndBackground", 2); 5752 JobStatus jobFg3 = 5753 createExpeditedJobStatus("testEJTimerTracking_ForegroundAndBackground", 3); 5754 synchronized (mQuotaController.mLock) { 5755 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 5756 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 5757 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); 5758 } 5759 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5760 List<TimingSession> expected = new ArrayList<>(); 5761 5762 // UID starts out inactive. 5763 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 5764 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5765 synchronized (mQuotaController.mLock) { 5766 mQuotaController.prepareForExecutionLocked(jobBg1); 5767 } 5768 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5769 synchronized (mQuotaController.mLock) { 5770 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); 5771 } 5772 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 5773 assertEquals(expected, 5774 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5775 5776 advanceElapsedClock(SECOND_IN_MILLIS); 5777 5778 // Bg job starts while inactive, spans an entire active session, and ends after the 5779 // active session. 5780 // App switching to foreground state then fg job starts. 5781 // App remains in foreground state after coming to foreground, so there should only be one 5782 // session. 5783 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5784 synchronized (mQuotaController.mLock) { 5785 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 5786 mQuotaController.prepareForExecutionLocked(jobBg2); 5787 } 5788 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5789 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 5790 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 5791 synchronized (mQuotaController.mLock) { 5792 mQuotaController.prepareForExecutionLocked(jobFg3); 5793 } 5794 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5795 synchronized (mQuotaController.mLock) { 5796 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null); 5797 } 5798 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5799 synchronized (mQuotaController.mLock) { 5800 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); 5801 } 5802 assertEquals(expected, 5803 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5804 5805 advanceElapsedClock(SECOND_IN_MILLIS); 5806 5807 // Bg job 1 starts, then fg job starts. Bg job 1 job ends. Shortly after, uid goes 5808 // "inactive" and then bg job 2 starts. Then fg job ends. 5809 // This should result in two TimingSessions: 5810 // * The first should have a count of 1 5811 // * The second should have a count of 2 since it will include both jobs 5812 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5813 synchronized (mQuotaController.mLock) { 5814 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 5815 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 5816 mQuotaController.maybeStartTrackingJobLocked(jobFg3, null); 5817 } 5818 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY); 5819 synchronized (mQuotaController.mLock) { 5820 mQuotaController.prepareForExecutionLocked(jobBg1); 5821 } 5822 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5823 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 5824 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 5825 synchronized (mQuotaController.mLock) { 5826 mQuotaController.prepareForExecutionLocked(jobFg3); 5827 } 5828 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5829 synchronized (mQuotaController.mLock) { 5830 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); 5831 } 5832 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now 5833 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5834 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 5835 synchronized (mQuotaController.mLock) { 5836 mQuotaController.prepareForExecutionLocked(jobBg2); 5837 } 5838 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5839 synchronized (mQuotaController.mLock) { 5840 mQuotaController.maybeStopTrackingJobLocked(jobFg3, null); 5841 } 5842 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5843 synchronized (mQuotaController.mLock) { 5844 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); 5845 } 5846 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); 5847 assertEquals(expected, 5848 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5849 } 5850 5851 /** 5852 * Tests that Timers properly track overlapping top and background jobs. 5853 */ 5854 @Test testEJTimerTracking_TopAndNonTop()5855 public void testEJTimerTracking_TopAndNonTop() { 5856 setDischarging(); 5857 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0); 5858 5859 JobStatus jobBg1 = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 1); 5860 JobStatus jobBg2 = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 2); 5861 JobStatus jobFg1 = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 3); 5862 JobStatus jobTop = createExpeditedJobStatus("testEJTimerTracking_TopAndNonTop", 4); 5863 synchronized (mQuotaController.mLock) { 5864 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 5865 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 5866 mQuotaController.maybeStartTrackingJobLocked(jobFg1, null); 5867 mQuotaController.maybeStartTrackingJobLocked(jobTop, null); 5868 } 5869 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5870 List<TimingSession> expected = new ArrayList<>(); 5871 5872 // UID starts out inactive. 5873 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 5874 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5875 synchronized (mQuotaController.mLock) { 5876 mQuotaController.prepareForExecutionLocked(jobBg1); 5877 } 5878 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5879 synchronized (mQuotaController.mLock) { 5880 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); 5881 } 5882 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 5883 assertEquals(expected, 5884 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5885 5886 advanceElapsedClock(SECOND_IN_MILLIS); 5887 5888 // Bg job starts while inactive, spans an entire active session, and ends after the 5889 // active session. 5890 // App switching to top state then fg job starts. 5891 // App remains in top state after coming to top, so there should only be one 5892 // session. 5893 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5894 synchronized (mQuotaController.mLock) { 5895 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 5896 mQuotaController.prepareForExecutionLocked(jobBg2); 5897 } 5898 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5899 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 5900 setProcessState(ActivityManager.PROCESS_STATE_TOP); 5901 synchronized (mQuotaController.mLock) { 5902 mQuotaController.prepareForExecutionLocked(jobTop); 5903 } 5904 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5905 synchronized (mQuotaController.mLock) { 5906 mQuotaController.maybeStopTrackingJobLocked(jobTop, null); 5907 } 5908 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5909 synchronized (mQuotaController.mLock) { 5910 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); 5911 } 5912 assertEquals(expected, 5913 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5914 5915 advanceElapsedClock(SECOND_IN_MILLIS); 5916 5917 // Bg job 1 starts, then top job starts. Bg job 1 job ends. Then app goes to 5918 // foreground_service and a new job starts. Shortly after, uid goes 5919 // "inactive" and then bg job 2 starts. Then top job ends, followed by bg and fg jobs. 5920 // This should result in two TimingSessions: 5921 // * The first should have a count of 1 5922 // * The second should have a count of 2, which accounts for the bg2 and fg, but not top 5923 // jobs. 5924 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5925 synchronized (mQuotaController.mLock) { 5926 mQuotaController.maybeStartTrackingJobLocked(jobBg1, null); 5927 mQuotaController.maybeStartTrackingJobLocked(jobBg2, null); 5928 mQuotaController.maybeStartTrackingJobLocked(jobTop, null); 5929 } 5930 setProcessState(ActivityManager.PROCESS_STATE_LAST_ACTIVITY); 5931 synchronized (mQuotaController.mLock) { 5932 mQuotaController.prepareForExecutionLocked(jobBg1); 5933 } 5934 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5935 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 5936 setProcessState(ActivityManager.PROCESS_STATE_TOP); 5937 synchronized (mQuotaController.mLock) { 5938 mQuotaController.prepareForExecutionLocked(jobTop); 5939 } 5940 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5941 synchronized (mQuotaController.mLock) { 5942 mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1); 5943 } 5944 advanceElapsedClock(5 * SECOND_IN_MILLIS); 5945 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 5946 synchronized (mQuotaController.mLock) { 5947 mQuotaController.prepareForExecutionLocked(jobFg1); 5948 } 5949 advanceElapsedClock(5 * SECOND_IN_MILLIS); 5950 setProcessState(ActivityManager.PROCESS_STATE_TOP); 5951 advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now 5952 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5953 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 5954 synchronized (mQuotaController.mLock) { 5955 mQuotaController.prepareForExecutionLocked(jobBg2); 5956 } 5957 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5958 synchronized (mQuotaController.mLock) { 5959 mQuotaController.maybeStopTrackingJobLocked(jobTop, null); 5960 } 5961 advanceElapsedClock(10 * SECOND_IN_MILLIS); 5962 synchronized (mQuotaController.mLock) { 5963 mQuotaController.maybeStopTrackingJobLocked(jobBg2, null); 5964 mQuotaController.maybeStopTrackingJobLocked(jobFg1, null); 5965 } 5966 expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2)); 5967 assertEquals(expected, 5968 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5969 } 5970 5971 /** 5972 * Tests that Timers properly track sessions when an app is added and removed from the temp 5973 * allowlist. 5974 */ 5975 @Test testEJTimerTracking_TempAllowlisting()5976 public void testEJTimerTracking_TempAllowlisting() { 5977 setDischarging(); 5978 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 5979 final long gracePeriodMs = 15 * SECOND_IN_MILLIS; 5980 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs); 5981 Handler handler = mQuotaController.getHandler(); 5982 spyOn(handler); 5983 5984 JobStatus job1 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 1); 5985 JobStatus job2 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 2); 5986 JobStatus job3 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 3); 5987 JobStatus job4 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 4); 5988 JobStatus job5 = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting", 5); 5989 synchronized (mQuotaController.mLock) { 5990 mQuotaController.maybeStartTrackingJobLocked(job1, null); 5991 } 5992 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 5993 List<TimingSession> expected = new ArrayList<>(); 5994 5995 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 5996 synchronized (mQuotaController.mLock) { 5997 mQuotaController.prepareForExecutionLocked(job1); 5998 } 5999 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6000 synchronized (mQuotaController.mLock) { 6001 mQuotaController.maybeStopTrackingJobLocked(job1, job1); 6002 } 6003 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6004 assertEquals(expected, 6005 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6006 6007 advanceElapsedClock(SECOND_IN_MILLIS); 6008 6009 // Job starts after app is added to temp allowlist and stops before removal. 6010 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6011 mTempAllowlistListener.onAppAdded(mSourceUid); 6012 synchronized (mQuotaController.mLock) { 6013 mQuotaController.maybeStartTrackingJobLocked(job2, null); 6014 mQuotaController.prepareForExecutionLocked(job2); 6015 } 6016 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6017 synchronized (mQuotaController.mLock) { 6018 mQuotaController.maybeStopTrackingJobLocked(job2, null); 6019 } 6020 assertEquals(expected, 6021 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6022 6023 // Job starts after app is added to temp allowlist and stops after removal, 6024 // before grace period ends. 6025 mTempAllowlistListener.onAppAdded(mSourceUid); 6026 synchronized (mQuotaController.mLock) { 6027 mQuotaController.maybeStartTrackingJobLocked(job3, null); 6028 mQuotaController.prepareForExecutionLocked(job3); 6029 } 6030 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6031 mTempAllowlistListener.onAppRemoved(mSourceUid); 6032 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6033 long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS; 6034 advanceElapsedClock(elapsedGracePeriodMs); 6035 synchronized (mQuotaController.mLock) { 6036 mQuotaController.maybeStopTrackingJobLocked(job3, null); 6037 } 6038 assertEquals(expected, 6039 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6040 6041 advanceElapsedClock(SECOND_IN_MILLIS); 6042 elapsedGracePeriodMs += SECOND_IN_MILLIS; 6043 6044 // Job starts during grace period and ends after grace period ends 6045 synchronized (mQuotaController.mLock) { 6046 mQuotaController.maybeStartTrackingJobLocked(job4, null); 6047 mQuotaController.prepareForExecutionLocked(job4); 6048 } 6049 final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs; 6050 start = JobSchedulerService.sElapsedRealtimeClock.millis() + remainingGracePeriod; 6051 advanceElapsedClock(remainingGracePeriod); 6052 // Wait for handler to update Timer 6053 // Can't directly evaluate the message because for some reason, the captured message returns 6054 // the wrong 'what' even though the correct message goes to the handler and the correct 6055 // path executes. 6056 verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any()); 6057 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6058 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6059 synchronized (mQuotaController.mLock) { 6060 mQuotaController.maybeStopTrackingJobLocked(job4, job4); 6061 } 6062 assertEquals(expected, 6063 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6064 6065 // Job starts and runs completely after temp allowlist grace period. 6066 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6067 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6068 synchronized (mQuotaController.mLock) { 6069 mQuotaController.maybeStartTrackingJobLocked(job5, null); 6070 mQuotaController.prepareForExecutionLocked(job5); 6071 } 6072 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6073 synchronized (mQuotaController.mLock) { 6074 mQuotaController.maybeStopTrackingJobLocked(job5, job5); 6075 } 6076 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6077 assertEquals(expected, 6078 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6079 } 6080 6081 @Test testEJTimerTracking_TempAllowlisting_Restricted()6082 public void testEJTimerTracking_TempAllowlisting_Restricted() { 6083 setDischarging(); 6084 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 6085 final long gracePeriodMs = 15 * SECOND_IN_MILLIS; 6086 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs); 6087 Handler handler = mQuotaController.getHandler(); 6088 spyOn(handler); 6089 6090 JobStatus job = createExpeditedJobStatus("testEJTimerTracking_TempAllowlisting_Restricted", 1); 6091 setStandbyBucket(RESTRICTED_INDEX, job); 6092 synchronized (mQuotaController.mLock) { 6093 mQuotaController.maybeStartTrackingJobLocked(job, null); 6094 } 6095 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6096 List<TimingSession> expected = new ArrayList<>(); 6097 6098 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6099 synchronized (mQuotaController.mLock) { 6100 mQuotaController.prepareForExecutionLocked(job); 6101 } 6102 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6103 synchronized (mQuotaController.mLock) { 6104 mQuotaController.maybeStopTrackingJobLocked(job, job); 6105 } 6106 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6107 assertEquals(expected, 6108 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6109 6110 advanceElapsedClock(SECOND_IN_MILLIS); 6111 6112 // Job starts after app is added to temp allowlist and stops before removal. 6113 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6114 mTempAllowlistListener.onAppAdded(mSourceUid); 6115 synchronized (mQuotaController.mLock) { 6116 mQuotaController.maybeStartTrackingJobLocked(job, null); 6117 mQuotaController.prepareForExecutionLocked(job); 6118 } 6119 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6120 synchronized (mQuotaController.mLock) { 6121 mQuotaController.maybeStopTrackingJobLocked(job, null); 6122 } 6123 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6124 assertEquals(expected, 6125 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6126 6127 // Job starts after app is added to temp allowlist and stops after removal, 6128 // before grace period ends. 6129 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6130 mTempAllowlistListener.onAppAdded(mSourceUid); 6131 synchronized (mQuotaController.mLock) { 6132 mQuotaController.maybeStartTrackingJobLocked(job, null); 6133 mQuotaController.prepareForExecutionLocked(job); 6134 } 6135 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6136 mTempAllowlistListener.onAppRemoved(mSourceUid); 6137 long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS; 6138 advanceElapsedClock(elapsedGracePeriodMs); 6139 synchronized (mQuotaController.mLock) { 6140 mQuotaController.maybeStopTrackingJobLocked(job, null); 6141 } 6142 expected.add(createTimingSession(start, 12 * SECOND_IN_MILLIS, 1)); 6143 assertEquals(expected, 6144 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6145 6146 advanceElapsedClock(SECOND_IN_MILLIS); 6147 elapsedGracePeriodMs += SECOND_IN_MILLIS; 6148 6149 // Job starts during grace period and ends after grace period ends 6150 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6151 synchronized (mQuotaController.mLock) { 6152 mQuotaController.maybeStartTrackingJobLocked(job, null); 6153 mQuotaController.prepareForExecutionLocked(job); 6154 } 6155 final long remainingGracePeriod = gracePeriodMs - elapsedGracePeriodMs; 6156 advanceElapsedClock(remainingGracePeriod); 6157 // Wait for handler to update Timer 6158 // Can't directly evaluate the message because for some reason, the captured message returns 6159 // the wrong 'what' even though the correct message goes to the handler and the correct 6160 // path executes. 6161 verify(handler, timeout(gracePeriodMs + 5 * SECOND_IN_MILLIS)).handleMessage(any()); 6162 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6163 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + remainingGracePeriod, 1)); 6164 synchronized (mQuotaController.mLock) { 6165 mQuotaController.maybeStopTrackingJobLocked(job, job); 6166 } 6167 assertEquals(expected, 6168 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6169 6170 // Job starts and runs completely after temp allowlist grace period. 6171 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6172 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6173 synchronized (mQuotaController.mLock) { 6174 mQuotaController.maybeStartTrackingJobLocked(job, null); 6175 mQuotaController.prepareForExecutionLocked(job); 6176 } 6177 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6178 synchronized (mQuotaController.mLock) { 6179 mQuotaController.maybeStopTrackingJobLocked(job, job); 6180 } 6181 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6182 assertEquals(expected, 6183 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6184 } 6185 6186 /** 6187 * Tests that Timers properly track sessions when TOP state and temp allowlisting overlaps. 6188 */ 6189 @Test 6190 @LargeTest testEJTimerTracking_TopAndTempAllowlisting()6191 public void testEJTimerTracking_TopAndTempAllowlisting() throws Exception { 6192 setDischarging(); 6193 setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); 6194 final long gracePeriodMs = 5 * SECOND_IN_MILLIS; 6195 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, gracePeriodMs); 6196 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, gracePeriodMs); 6197 Handler handler = mQuotaController.getHandler(); 6198 spyOn(handler); 6199 6200 JobStatus job1 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 1); 6201 JobStatus job2 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 2); 6202 JobStatus job3 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 3); 6203 JobStatus job4 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 4); 6204 JobStatus job5 = createExpeditedJobStatus("testEJTimerTracking_TopAndTempAllowlisting", 5); 6205 synchronized (mQuotaController.mLock) { 6206 mQuotaController.maybeStartTrackingJobLocked(job1, null); 6207 } 6208 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6209 List<TimingSession> expected = new ArrayList<>(); 6210 6211 // Case 1: job starts in TA grace period then app becomes TOP 6212 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6213 mTempAllowlistListener.onAppAdded(mSourceUid); 6214 mTempAllowlistListener.onAppRemoved(mSourceUid); 6215 advanceElapsedClock(gracePeriodMs / 2); 6216 synchronized (mQuotaController.mLock) { 6217 mQuotaController.prepareForExecutionLocked(job1); 6218 } 6219 setProcessState(ActivityManager.PROCESS_STATE_TOP); 6220 advanceElapsedClock(gracePeriodMs); 6221 // Wait for the grace period to expire so the handler can process the message. 6222 Thread.sleep(gracePeriodMs); 6223 synchronized (mQuotaController.mLock) { 6224 mQuotaController.maybeStopTrackingJobLocked(job1, job1); 6225 } 6226 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6227 6228 advanceElapsedClock(gracePeriodMs); 6229 6230 // Case 2: job starts in TOP grace period then is TAed 6231 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6232 setProcessState(ActivityManager.PROCESS_STATE_TOP); 6233 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 6234 advanceElapsedClock(gracePeriodMs / 2); 6235 synchronized (mQuotaController.mLock) { 6236 mQuotaController.maybeStartTrackingJobLocked(job2, null); 6237 mQuotaController.prepareForExecutionLocked(job2); 6238 } 6239 mTempAllowlistListener.onAppAdded(mSourceUid); 6240 advanceElapsedClock(gracePeriodMs); 6241 // Wait for the grace period to expire so the handler can process the message. 6242 Thread.sleep(gracePeriodMs); 6243 synchronized (mQuotaController.mLock) { 6244 mQuotaController.maybeStopTrackingJobLocked(job2, null); 6245 } 6246 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6247 6248 advanceElapsedClock(gracePeriodMs); 6249 6250 // Case 3: job starts in TA grace period then app becomes TOP; job ends after TOP grace 6251 mTempAllowlistListener.onAppAdded(mSourceUid); 6252 mTempAllowlistListener.onAppRemoved(mSourceUid); 6253 advanceElapsedClock(gracePeriodMs / 2); 6254 synchronized (mQuotaController.mLock) { 6255 mQuotaController.maybeStartTrackingJobLocked(job3, null); 6256 mQuotaController.prepareForExecutionLocked(job3); 6257 } 6258 advanceElapsedClock(SECOND_IN_MILLIS); 6259 setProcessState(ActivityManager.PROCESS_STATE_TOP); 6260 advanceElapsedClock(gracePeriodMs); 6261 // Wait for the grace period to expire so the handler can process the message. 6262 Thread.sleep(gracePeriodMs); 6263 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 6264 advanceElapsedClock(gracePeriodMs); 6265 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6266 // Wait for the grace period to expire so the handler can process the message. 6267 Thread.sleep(2 * gracePeriodMs); 6268 advanceElapsedClock(gracePeriodMs); 6269 synchronized (mQuotaController.mLock) { 6270 mQuotaController.maybeStopTrackingJobLocked(job3, job3); 6271 } 6272 expected.add(createTimingSession(start, gracePeriodMs, 1)); 6273 assertEquals(expected, 6274 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6275 6276 advanceElapsedClock(gracePeriodMs); 6277 6278 // Case 4: job starts in TOP grace period then app becomes TAed; job ends after TA grace 6279 setProcessState(ActivityManager.PROCESS_STATE_TOP); 6280 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 6281 advanceElapsedClock(gracePeriodMs / 2); 6282 synchronized (mQuotaController.mLock) { 6283 mQuotaController.maybeStartTrackingJobLocked(job4, null); 6284 mQuotaController.prepareForExecutionLocked(job4); 6285 } 6286 advanceElapsedClock(SECOND_IN_MILLIS); 6287 mTempAllowlistListener.onAppAdded(mSourceUid); 6288 advanceElapsedClock(gracePeriodMs); 6289 // Wait for the grace period to expire so the handler can process the message. 6290 Thread.sleep(gracePeriodMs); 6291 mTempAllowlistListener.onAppRemoved(mSourceUid); 6292 advanceElapsedClock(gracePeriodMs); 6293 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6294 // Wait for the grace period to expire so the handler can process the message. 6295 Thread.sleep(2 * gracePeriodMs); 6296 advanceElapsedClock(gracePeriodMs); 6297 synchronized (mQuotaController.mLock) { 6298 mQuotaController.maybeStopTrackingJobLocked(job4, job4); 6299 } 6300 expected.add(createTimingSession(start, gracePeriodMs, 1)); 6301 assertEquals(expected, 6302 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6303 6304 advanceElapsedClock(gracePeriodMs); 6305 6306 // Case 5: job starts during overlapping grace period 6307 setProcessState(ActivityManager.PROCESS_STATE_TOP); 6308 advanceElapsedClock(SECOND_IN_MILLIS); 6309 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 6310 advanceElapsedClock(SECOND_IN_MILLIS); 6311 mTempAllowlistListener.onAppAdded(mSourceUid); 6312 advanceElapsedClock(SECOND_IN_MILLIS); 6313 mTempAllowlistListener.onAppRemoved(mSourceUid); 6314 advanceElapsedClock(gracePeriodMs - SECOND_IN_MILLIS); 6315 synchronized (mQuotaController.mLock) { 6316 mQuotaController.maybeStartTrackingJobLocked(job5, null); 6317 mQuotaController.prepareForExecutionLocked(job5); 6318 } 6319 advanceElapsedClock(SECOND_IN_MILLIS); 6320 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6321 // Wait for the grace period to expire so the handler can process the message. 6322 Thread.sleep(2 * gracePeriodMs); 6323 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6324 synchronized (mQuotaController.mLock) { 6325 mQuotaController.maybeStopTrackingJobLocked(job5, job5); 6326 } 6327 expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6328 assertEquals(expected, 6329 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6330 } 6331 6332 /** 6333 * Tests that expedited jobs aren't stopped when an app runs out of quota. 6334 */ 6335 @Test testEJTracking_OutOfQuota_ForegroundAndBackground()6336 public void testEJTracking_OutOfQuota_ForegroundAndBackground() { 6337 setDischarging(); 6338 setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 0); 6339 6340 JobStatus jobBg = 6341 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 1); 6342 JobStatus jobTop = 6343 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 2); 6344 JobStatus jobUnstarted = 6345 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 3); 6346 trackJobs(jobBg, jobTop, jobUnstarted); 6347 setStandbyBucket(WORKING_INDEX, jobTop, jobBg, jobUnstarted); 6348 // Now the package only has 20 seconds to run. 6349 final long remainingTimeMs = 20 * SECOND_IN_MILLIS; 6350 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 6351 createTimingSession( 6352 JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS, 6353 mQcConstants.EJ_LIMIT_WORKING_MS - remainingTimeMs, 1), true); 6354 6355 InOrder inOrder = inOrder(mJobSchedulerService); 6356 6357 // UID starts out inactive. 6358 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 6359 // Start the job. 6360 synchronized (mQuotaController.mLock) { 6361 mQuotaController.prepareForExecutionLocked(jobBg); 6362 } 6363 advanceElapsedClock(remainingTimeMs / 2); 6364 // New job starts after UID is in the foreground. Since the app is now in the foreground, it 6365 // should continue to have remainingTimeMs / 2 time remaining. 6366 setProcessState(ActivityManager.PROCESS_STATE_TOP); 6367 synchronized (mQuotaController.mLock) { 6368 mQuotaController.prepareForExecutionLocked(jobTop); 6369 } 6370 advanceElapsedClock(remainingTimeMs); 6371 6372 // Wait for some extra time to allow for job processing. 6373 inOrder.verify(mJobSchedulerService, 6374 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) 6375 .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); 6376 synchronized (mQuotaController.mLock) { 6377 assertEquals(remainingTimeMs / 2, 6378 mQuotaController.getRemainingEJExecutionTimeLocked( 6379 SOURCE_USER_ID, SOURCE_PACKAGE)); 6380 } 6381 // Go to a background state. 6382 setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING); 6383 advanceElapsedClock(remainingTimeMs / 2 + 1); 6384 inOrder.verify(mJobSchedulerService, 6385 timeout(remainingTimeMs / 2 + 2 * SECOND_IN_MILLIS).times(1)) 6386 .onControllerStateChanged(argThat(jobs -> jobs.size() == 2)); 6387 // Top should still be "in quota" since it started before the app ran on top out of quota. 6388 assertFalse(jobBg.isExpeditedQuotaApproved()); 6389 assertTrue(jobTop.isExpeditedQuotaApproved()); 6390 assertFalse(jobUnstarted.isExpeditedQuotaApproved()); 6391 synchronized (mQuotaController.mLock) { 6392 assertTrue( 6393 0 >= mQuotaController 6394 .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6395 } 6396 6397 // New jobs to run. 6398 JobStatus jobBg2 = 6399 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 4); 6400 JobStatus jobTop2 = 6401 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 5); 6402 JobStatus jobFg = 6403 createExpeditedJobStatus("testEJTracking_OutOfQuota_ForegroundAndBackground", 6); 6404 setStandbyBucket(WORKING_INDEX, jobBg2, jobTop2, jobFg); 6405 6406 advanceElapsedClock(20 * SECOND_IN_MILLIS); 6407 setProcessState(ActivityManager.PROCESS_STATE_TOP); 6408 // Confirm QC recognizes that jobUnstarted has changed from out-of-quota to in-quota. 6409 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) 6410 .onControllerStateChanged(argThat(jobs -> jobs.size() == 2)); 6411 trackJobs(jobTop2, jobFg); 6412 synchronized (mQuotaController.mLock) { 6413 mQuotaController.prepareForExecutionLocked(jobTop2); 6414 } 6415 assertTrue(jobTop2.isExpeditedQuotaApproved()); 6416 assertTrue(jobFg.isExpeditedQuotaApproved()); 6417 assertTrue(jobBg.isExpeditedQuotaApproved()); 6418 assertTrue(jobUnstarted.isExpeditedQuotaApproved()); 6419 6420 // App still in foreground so everything should be in quota. 6421 advanceElapsedClock(20 * SECOND_IN_MILLIS); 6422 setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); 6423 assertTrue(jobTop2.isExpeditedQuotaApproved()); 6424 assertTrue(jobFg.isExpeditedQuotaApproved()); 6425 assertTrue(jobBg.isExpeditedQuotaApproved()); 6426 assertTrue(jobUnstarted.isExpeditedQuotaApproved()); 6427 6428 advanceElapsedClock(20 * SECOND_IN_MILLIS); 6429 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 6430 inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) 6431 .onControllerStateChanged(argThat(jobs -> jobs.size() == 3)); 6432 // App is now in background and out of quota. Fg should now change to out of quota since it 6433 // wasn't started. Top should remain in quota since it started when the app was in TOP. 6434 assertTrue(jobTop2.isExpeditedQuotaApproved()); 6435 assertFalse(jobFg.isExpeditedQuotaApproved()); 6436 assertFalse(jobBg.isExpeditedQuotaApproved()); 6437 trackJobs(jobBg2); 6438 assertFalse(jobBg2.isExpeditedQuotaApproved()); 6439 assertFalse(jobUnstarted.isExpeditedQuotaApproved()); 6440 synchronized (mQuotaController.mLock) { 6441 assertTrue( 6442 0 >= mQuotaController 6443 .getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6444 } 6445 } 6446 6447 /** 6448 * Tests that Timers properly track overlapping top and background jobs. 6449 */ 6450 @Test testEJTimerTrackingSeparateFromRegularTracking()6451 public void testEJTimerTrackingSeparateFromRegularTracking() { 6452 setDischarging(); 6453 setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); 6454 6455 JobStatus jobReg1 = createJobStatus("testEJTimerTrackingSeparateFromRegularTracking", 1); 6456 JobStatus jobEJ1 = 6457 createExpeditedJobStatus("testEJTimerTrackingSeparateFromRegularTracking", 2); 6458 JobStatus jobReg2 = createJobStatus("testEJTimerTrackingSeparateFromRegularTracking", 3); 6459 JobStatus jobEJ2 = 6460 createExpeditedJobStatus("testEJTimerTrackingSeparateFromRegularTracking", 4); 6461 synchronized (mQuotaController.mLock) { 6462 mQuotaController.maybeStartTrackingJobLocked(jobReg1, null); 6463 mQuotaController.maybeStartTrackingJobLocked(jobEJ1, null); 6464 mQuotaController.maybeStartTrackingJobLocked(jobReg2, null); 6465 mQuotaController.maybeStartTrackingJobLocked(jobEJ2, null); 6466 } 6467 assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6468 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6469 List<TimingSession> expectedRegular = new ArrayList<>(); 6470 List<TimingSession> expectedEJ = new ArrayList<>(); 6471 6472 // First, regular job runs by itself. 6473 long start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6474 synchronized (mQuotaController.mLock) { 6475 mQuotaController.prepareForExecutionLocked(jobReg1); 6476 } 6477 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6478 synchronized (mQuotaController.mLock) { 6479 mQuotaController.maybeStopTrackingJobLocked(jobReg1, jobReg1); 6480 } 6481 expectedRegular.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6482 assertEquals(expectedRegular, 6483 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6484 assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6485 6486 advanceElapsedClock(SECOND_IN_MILLIS); 6487 6488 // Next, EJ runs by itself. 6489 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6490 synchronized (mQuotaController.mLock) { 6491 mQuotaController.prepareForExecutionLocked(jobEJ1); 6492 } 6493 advanceElapsedClock(10 * SECOND_IN_MILLIS); 6494 synchronized (mQuotaController.mLock) { 6495 mQuotaController.maybeStopTrackingJobLocked(jobEJ1, null); 6496 } 6497 expectedEJ.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6498 assertEquals(expectedRegular, 6499 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6500 assertEquals(expectedEJ, 6501 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6502 6503 advanceElapsedClock(SECOND_IN_MILLIS); 6504 6505 // Finally, a regular job and EJ happen to overlap runs. 6506 start = JobSchedulerService.sElapsedRealtimeClock.millis(); 6507 synchronized (mQuotaController.mLock) { 6508 mQuotaController.prepareForExecutionLocked(jobEJ2); 6509 } 6510 advanceElapsedClock(5 * SECOND_IN_MILLIS); 6511 synchronized (mQuotaController.mLock) { 6512 mQuotaController.prepareForExecutionLocked(jobReg2); 6513 } 6514 advanceElapsedClock(5 * SECOND_IN_MILLIS); 6515 synchronized (mQuotaController.mLock) { 6516 mQuotaController.maybeStopTrackingJobLocked(jobEJ2, null); 6517 } 6518 expectedEJ.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1)); 6519 advanceElapsedClock(5 * SECOND_IN_MILLIS); 6520 synchronized (mQuotaController.mLock) { 6521 mQuotaController.maybeStopTrackingJobLocked(jobReg2, null); 6522 } 6523 expectedRegular.add( 6524 createTimingSession(start + 5 * SECOND_IN_MILLIS, 10 * SECOND_IN_MILLIS, 1)); 6525 assertEquals(expectedRegular, 6526 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6527 assertEquals(expectedEJ, 6528 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE)); 6529 } 6530 6531 /** 6532 * Tests that a job is properly handled when it's at the edge of its quota and the old quota is 6533 * being phased out. 6534 */ 6535 @Test testEJTracking_RollingQuota()6536 public void testEJTracking_RollingQuota() { 6537 JobStatus jobStatus = createExpeditedJobStatus("testEJTracking_RollingQuota", 1); 6538 synchronized (mQuotaController.mLock) { 6539 mQuotaController.maybeStartTrackingJobLocked(jobStatus, null); 6540 } 6541 setStandbyBucket(WORKING_INDEX, jobStatus); 6542 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 6543 Handler handler = mQuotaController.getHandler(); 6544 spyOn(handler); 6545 6546 long now = JobSchedulerService.sElapsedRealtimeClock.millis(); 6547 final long remainingTimeMs = SECOND_IN_MILLIS; 6548 // The package only has one second to run, but this session is at the edge of the rolling 6549 // window, so as the package "reaches its quota" it will have more to keep running. 6550 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 6551 createTimingSession(now - mQcConstants.EJ_WINDOW_SIZE_MS, 6552 10 * SECOND_IN_MILLIS - remainingTimeMs, 1), true); 6553 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, 6554 createTimingSession(now - HOUR_IN_MILLIS, 6555 mQcConstants.EJ_LIMIT_WORKING_MS - 10 * SECOND_IN_MILLIS, 1), true); 6556 6557 synchronized (mQuotaController.mLock) { 6558 assertEquals(remainingTimeMs, 6559 mQuotaController.getRemainingEJExecutionTimeLocked( 6560 SOURCE_USER_ID, SOURCE_PACKAGE)); 6561 6562 // Start the job. 6563 mQuotaController.prepareForExecutionLocked(jobStatus); 6564 } 6565 advanceElapsedClock(remainingTimeMs); 6566 6567 // Wait for some extra time to allow for job processing. 6568 verify(mJobSchedulerService, 6569 timeout(remainingTimeMs + 2 * SECOND_IN_MILLIS).times(0)) 6570 .onControllerStateChanged(argThat(jobs -> jobs.size() > 0)); 6571 assertTrue(jobStatus.isExpeditedQuotaApproved()); 6572 // The job used up the remaining quota, but in that time, the same amount of time in the 6573 // old TimingSession also fell out of the quota window, so it should still have the same 6574 // amount of remaining time left its quota. 6575 synchronized (mQuotaController.mLock) { 6576 assertEquals(remainingTimeMs, 6577 mQuotaController.getRemainingEJExecutionTimeLocked( 6578 SOURCE_USER_ID, SOURCE_PACKAGE)); 6579 } 6580 // Handler is told to check when the quota will be consumed, not when the initial 6581 // remaining time is over. 6582 verify(handler, atLeast(1)).sendMessageDelayed( 6583 argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA), 6584 eq(10 * SECOND_IN_MILLIS)); 6585 verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); 6586 } 6587 6588 @Test testEJDebitTallying()6589 public void testEJDebitTallying() { 6590 setStandbyBucket(RARE_INDEX); 6591 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 6592 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); 6593 // 15 seconds for each 30 second chunk. 6594 setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 30 * SECOND_IN_MILLIS); 6595 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 15 * SECOND_IN_MILLIS); 6596 6597 // No history. Debits should be 0. 6598 ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); 6599 assertEquals(0, debit.getTallyLocked()); 6600 assertEquals(10 * MINUTE_IN_MILLIS, 6601 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6602 6603 // Regular job shouldn't affect EJ tally. 6604 JobStatus regJob = createJobStatus("testEJDebitTallying", 1); 6605 synchronized (mQuotaController.mLock) { 6606 mQuotaController.maybeStartTrackingJobLocked(regJob, null); 6607 mQuotaController.prepareForExecutionLocked(regJob); 6608 } 6609 advanceElapsedClock(5000); 6610 synchronized (mQuotaController.mLock) { 6611 mQuotaController.maybeStopTrackingJobLocked(regJob, null); 6612 } 6613 assertEquals(0, debit.getTallyLocked()); 6614 assertEquals(10 * MINUTE_IN_MILLIS, 6615 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6616 6617 // EJ job should affect EJ tally. 6618 JobStatus eJob = createExpeditedJobStatus("testEJDebitTallying", 2); 6619 synchronized (mQuotaController.mLock) { 6620 mQuotaController.maybeStartTrackingJobLocked(eJob, null); 6621 mQuotaController.prepareForExecutionLocked(eJob); 6622 } 6623 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 6624 synchronized (mQuotaController.mLock) { 6625 mQuotaController.maybeStopTrackingJobLocked(eJob, null); 6626 } 6627 assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked()); 6628 assertEquals(5 * MINUTE_IN_MILLIS, 6629 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6630 6631 // Instantaneous event for a different user shouldn't affect tally. 6632 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 6633 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS); 6634 6635 UsageEvents.Event event = 6636 new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); 6637 event.mPackage = SOURCE_PACKAGE; 6638 mUsageEventListener.onUsageEvent(SOURCE_USER_ID + 10, event); 6639 assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked()); 6640 6641 // Instantaneous event for correct user should reduce tally. 6642 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 6643 6644 mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); 6645 waitForNonDelayedMessagesProcessed(); 6646 assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked()); 6647 assertEquals(6 * MINUTE_IN_MILLIS, 6648 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6649 6650 // Activity start shouldn't reduce tally, but duration with activity started should affect 6651 // remaining EJ time. 6652 advanceElapsedClock(5 * MINUTE_IN_MILLIS); 6653 event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_RESUMED, sSystemClock.millis()); 6654 event.mPackage = SOURCE_PACKAGE; 6655 mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); 6656 waitForNonDelayedMessagesProcessed(); 6657 advanceElapsedClock(30 * SECOND_IN_MILLIS); 6658 assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked()); 6659 assertEquals(6 * MINUTE_IN_MILLIS + 15 * SECOND_IN_MILLIS, 6660 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6661 advanceElapsedClock(30 * SECOND_IN_MILLIS); 6662 assertEquals(4 * MINUTE_IN_MILLIS, debit.getTallyLocked()); 6663 assertEquals(6 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, 6664 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6665 6666 // With activity pausing/stopping/destroying, tally should be updated. 6667 advanceElapsedClock(MINUTE_IN_MILLIS); 6668 event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_DESTROYED, sSystemClock.millis()); 6669 event.mPackage = SOURCE_PACKAGE; 6670 mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); 6671 waitForNonDelayedMessagesProcessed(); 6672 assertEquals(3 * MINUTE_IN_MILLIS, debit.getTallyLocked()); 6673 assertEquals(7 * MINUTE_IN_MILLIS, 6674 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6675 } 6676 6677 @Test testEJDebitTallying_StaleSession()6678 public void testEJDebitTallying_StaleSession() { 6679 setStandbyBucket(RARE_INDEX); 6680 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_RARE_MS, 10 * MINUTE_IN_MILLIS); 6681 6682 final long nowElapsed = sElapsedRealtimeClock.millis(); 6683 TimingSession ts = new TimingSession(nowElapsed, nowElapsed + 10 * MINUTE_IN_MILLIS, 5); 6684 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true); 6685 6686 // Make the session stale. 6687 advanceElapsedClock(12 * MINUTE_IN_MILLIS + mQcConstants.EJ_WINDOW_SIZE_MS); 6688 6689 // With lazy deletion, we don't update the tally until getRemainingEJExecutionTimeLocked() 6690 // is called, so call that first. 6691 assertEquals(10 * MINUTE_IN_MILLIS, 6692 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6693 ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); 6694 assertEquals(0, debit.getTallyLocked()); 6695 } 6696 6697 /** 6698 * Tests that rewards are properly accounted when there's no EJ running and the rewards exceed 6699 * the accumulated debits. 6700 */ 6701 @Test testEJDebitTallying_RewardExceedDebits_NoActiveSession()6702 public void testEJDebitTallying_RewardExceedDebits_NoActiveSession() { 6703 setStandbyBucket(WORKING_INDEX); 6704 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 6705 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 30 * MINUTE_IN_MILLIS); 6706 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS); 6707 6708 final long nowElapsed = sElapsedRealtimeClock.millis(); 6709 TimingSession ts = new TimingSession(nowElapsed - 5 * MINUTE_IN_MILLIS, 6710 nowElapsed - 4 * MINUTE_IN_MILLIS, 2); 6711 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true); 6712 6713 ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); 6714 assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked()); 6715 assertEquals(29 * MINUTE_IN_MILLIS, 6716 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6717 6718 advanceElapsedClock(30 * SECOND_IN_MILLIS); 6719 UsageEvents.Event event = 6720 new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); 6721 event.mPackage = SOURCE_PACKAGE; 6722 mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); 6723 waitForNonDelayedMessagesProcessed(); 6724 assertEquals(0, debit.getTallyLocked()); 6725 assertEquals(30 * MINUTE_IN_MILLIS, 6726 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6727 6728 advanceElapsedClock(MINUTE_IN_MILLIS); 6729 assertEquals(0, debit.getTallyLocked()); 6730 assertEquals(30 * MINUTE_IN_MILLIS, 6731 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6732 6733 // Excessive rewards don't increase maximum quota. 6734 event = new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); 6735 event.mPackage = SOURCE_PACKAGE; 6736 mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); 6737 waitForNonDelayedMessagesProcessed(); 6738 assertEquals(0, debit.getTallyLocked()); 6739 assertEquals(30 * MINUTE_IN_MILLIS, 6740 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6741 } 6742 6743 /** 6744 * Tests that rewards are properly accounted when there's an active EJ running and the rewards 6745 * exceed the accumulated debits. 6746 */ 6747 @Test testEJDebitTallying_RewardExceedDebits_ActiveSession()6748 public void testEJDebitTallying_RewardExceedDebits_ActiveSession() { 6749 setStandbyBucket(WORKING_INDEX); 6750 setProcessState(ActivityManager.PROCESS_STATE_SERVICE); 6751 setDeviceConfigLong(QcConstants.KEY_EJ_LIMIT_WORKING_MS, 30 * MINUTE_IN_MILLIS); 6752 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_INTERACTION_MS, MINUTE_IN_MILLIS); 6753 // 15 seconds for each 30 second chunk. 6754 setDeviceConfigLong(QcConstants.KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS, 30 * SECOND_IN_MILLIS); 6755 setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_TOP_APP_MS, 15 * SECOND_IN_MILLIS); 6756 6757 final long nowElapsed = sElapsedRealtimeClock.millis(); 6758 TimingSession ts = new TimingSession(nowElapsed - 5 * MINUTE_IN_MILLIS, 6759 nowElapsed - 4 * MINUTE_IN_MILLIS, 2); 6760 mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, ts, true); 6761 6762 ShrinkableDebits debit = mQuotaController.getEJDebitsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); 6763 assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked()); 6764 assertEquals(29 * MINUTE_IN_MILLIS, 6765 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6766 6767 // With rewards coming in while an EJ is running, the remaining execution time should be 6768 // adjusted accordingly (decrease due to EJ running + increase from reward). 6769 JobStatus eJob = 6770 createExpeditedJobStatus("testEJDebitTallying_RewardExceedDebits_ActiveSession", 1); 6771 synchronized (mQuotaController.mLock) { 6772 mQuotaController.maybeStartTrackingJobLocked(eJob, null); 6773 mQuotaController.prepareForExecutionLocked(eJob); 6774 } 6775 advanceElapsedClock(30 * SECOND_IN_MILLIS); 6776 assertEquals(MINUTE_IN_MILLIS, debit.getTallyLocked()); 6777 assertEquals(28 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, 6778 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6779 6780 advanceElapsedClock(30 * SECOND_IN_MILLIS); 6781 UsageEvents.Event event = 6782 new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); 6783 event.mPackage = SOURCE_PACKAGE; 6784 mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); 6785 waitForNonDelayedMessagesProcessed(); 6786 assertEquals(0, debit.getTallyLocked()); 6787 assertEquals(29 * MINUTE_IN_MILLIS, 6788 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6789 6790 advanceElapsedClock(MINUTE_IN_MILLIS); 6791 assertEquals(0, debit.getTallyLocked()); 6792 assertEquals(28 * MINUTE_IN_MILLIS, 6793 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6794 6795 // Activity start shouldn't reduce tally, but duration with activity started should affect 6796 // remaining EJ time. 6797 event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_RESUMED, sSystemClock.millis()); 6798 event.mPackage = SOURCE_PACKAGE; 6799 mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); 6800 waitForNonDelayedMessagesProcessed(); 6801 advanceElapsedClock(30 * SECOND_IN_MILLIS); 6802 assertEquals(0, debit.getTallyLocked()); 6803 // Decrease by 30 seconds for running EJ, increase by 15 seconds due to ongoing activity. 6804 assertEquals(27 * MINUTE_IN_MILLIS + 45 * SECOND_IN_MILLIS, 6805 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6806 advanceElapsedClock(30 * SECOND_IN_MILLIS); 6807 assertEquals(0, debit.getTallyLocked()); 6808 assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, 6809 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6810 6811 advanceElapsedClock(MINUTE_IN_MILLIS); 6812 assertEquals(0, debit.getTallyLocked()); 6813 assertEquals(27 * MINUTE_IN_MILLIS, 6814 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6815 6816 event = new UsageEvents.Event(UsageEvents.Event.USER_INTERACTION, sSystemClock.millis()); 6817 event.mPackage = SOURCE_PACKAGE; 6818 mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); 6819 waitForNonDelayedMessagesProcessed(); 6820 assertEquals(0, debit.getTallyLocked()); 6821 assertEquals(28 * MINUTE_IN_MILLIS, 6822 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6823 6824 advanceElapsedClock(MINUTE_IN_MILLIS); 6825 assertEquals(0, debit.getTallyLocked()); 6826 assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, 6827 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6828 6829 // At this point, with activity pausing/stopping/destroying, since we're giving a reward, 6830 // tally should remain 0, and time remaining shouldn't change since it was accounted for 6831 // at every step. 6832 event = new UsageEvents.Event(UsageEvents.Event.ACTIVITY_DESTROYED, sSystemClock.millis()); 6833 event.mPackage = SOURCE_PACKAGE; 6834 mUsageEventListener.onUsageEvent(SOURCE_USER_ID, event); 6835 waitForNonDelayedMessagesProcessed(); 6836 assertEquals(0, debit.getTallyLocked()); 6837 assertEquals(27 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, 6838 mQuotaController.getRemainingEJExecutionTimeLocked(SOURCE_USER_ID, SOURCE_PACKAGE)); 6839 } 6840 } 6841