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