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