1 /* 2 * Copyright (C) 2019 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.utils.quota; 18 19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder; 21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; 23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; 24 import static com.android.server.utils.quota.Category.SINGLE_CATEGORY; 25 import static com.android.server.utils.quota.QuotaTracker.MAX_WINDOW_SIZE_MS; 26 import static com.android.server.utils.quota.QuotaTracker.MIN_WINDOW_SIZE_MS; 27 28 import static org.junit.Assert.assertEquals; 29 import static org.junit.Assert.assertFalse; 30 import static org.junit.Assert.assertNull; 31 import static org.junit.Assert.assertTrue; 32 import static org.junit.Assert.fail; 33 import static org.mockito.ArgumentMatchers.any; 34 import static org.mockito.ArgumentMatchers.anyInt; 35 import static org.mockito.ArgumentMatchers.anyLong; 36 import static org.mockito.Mockito.atLeastOnce; 37 import static org.mockito.Mockito.eq; 38 import static org.mockito.Mockito.never; 39 import static org.mockito.Mockito.timeout; 40 import static org.mockito.Mockito.times; 41 import static org.mockito.Mockito.verify; 42 43 import android.annotation.NonNull; 44 import android.annotation.Nullable; 45 import android.app.AlarmManager; 46 import android.content.BroadcastReceiver; 47 import android.content.Context; 48 import android.content.Intent; 49 import android.net.Uri; 50 import android.os.Handler; 51 import android.os.Looper; 52 import android.os.SystemClock; 53 import android.os.UserHandle; 54 import android.util.LongArrayQueue; 55 56 import androidx.test.runner.AndroidJUnit4; 57 58 import com.android.server.LocalServices; 59 import com.android.server.utils.quota.CountQuotaTracker.ExecutionStats; 60 61 import org.junit.After; 62 import org.junit.Before; 63 import org.junit.Test; 64 import org.junit.runner.RunWith; 65 import org.mockito.ArgumentCaptor; 66 import org.mockito.InOrder; 67 import org.mockito.Mock; 68 import org.mockito.MockitoSession; 69 import org.mockito.quality.Strictness; 70 71 /** 72 * Tests for {@link CountQuotaTracker}. 73 */ 74 @RunWith(AndroidJUnit4.class) 75 public class CountQuotaTrackerTest { 76 private static final long SECOND_IN_MILLIS = 1000L; 77 private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS; 78 private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS; 79 private static final String TAG_CLEANUP = "*CountQuotaTracker.cleanup*"; 80 private static final String TAG_QUOTA_CHECK = "*QuotaTracker.quota_check*"; 81 private static final String TEST_PACKAGE = "com.android.frameworks.mockingservicestests"; 82 private static final String TEST_TAG = "testing"; 83 private static final int TEST_UID = 10987; 84 private static final int TEST_USER_ID = 0; 85 86 /** A {@link Category} to represent the ACTIVE standby bucket. */ 87 private static final Category ACTIVE_BUCKET_CATEGORY = new Category("ACTIVE"); 88 89 /** A {@link Category} to represent the WORKING_SET standby bucket. */ 90 private static final Category WORKING_SET_BUCKET_CATEGORY = new Category("WORKING_SET"); 91 92 /** A {@link Category} to represent the FREQUENT standby bucket. */ 93 private static final Category FREQUENT_BUCKET_CATEGORY = new Category("FREQUENT"); 94 95 /** A {@link Category} to represent the RARE standby bucket. */ 96 private static final Category RARE_BUCKET_CATEGORY = new Category("RARE"); 97 98 private CountQuotaTracker mQuotaTracker; 99 private final CategorizerForTest mCategorizer = new CategorizerForTest(); 100 private final InjectorForTest mInjector = new InjectorForTest(); 101 private final TestQuotaChangeListener mQuotaChangeListener = new TestQuotaChangeListener(); 102 private BroadcastReceiver mReceiver; 103 private MockitoSession mMockingSession; 104 @Mock 105 private AlarmManager mAlarmManager; 106 @Mock 107 private Context mContext; 108 109 static class CategorizerForTest implements Categorizer { 110 private Category mCategoryToUse = SINGLE_CATEGORY; 111 112 @Override getCategory(int userId, String packageName, String tag)113 public Category getCategory(int userId, 114 String packageName, String tag) { 115 return mCategoryToUse; 116 } 117 } 118 119 private static class InjectorForTest extends QuotaTracker.Injector { 120 private long mElapsedTime = SystemClock.elapsedRealtime(); 121 122 @Override getElapsedRealtime()123 long getElapsedRealtime() { 124 return mElapsedTime; 125 } 126 127 @Override isAlarmManagerReady()128 boolean isAlarmManagerReady() { 129 return true; 130 } 131 } 132 133 private static class TestQuotaChangeListener implements QuotaChangeListener { 134 135 @Override onQuotaStateChanged(int userId, String packageName, String tag)136 public void onQuotaStateChanged(int userId, String packageName, String tag) { 137 138 } 139 } 140 141 @Before setUp()142 public void setUp() { 143 mMockingSession = mockitoSession() 144 .initMocks(this) 145 .strictness(Strictness.LENIENT) 146 .mockStatic(LocalServices.class) 147 .startMocking(); 148 149 when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper()); 150 when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager); 151 152 // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions 153 // in the past, and QuotaController sometimes floors values at 0, so if the test time 154 // causes sessions with negative timestamps, they will fail. 155 advanceElapsedClock(24 * HOUR_IN_MILLIS); 156 157 // Initialize real objects. 158 // Capture the listeners. 159 ArgumentCaptor<BroadcastReceiver> receiverCaptor = 160 ArgumentCaptor.forClass(BroadcastReceiver.class); 161 mQuotaTracker = new CountQuotaTracker(mContext, mCategorizer, mInjector); 162 mQuotaTracker.setEnabled(true); 163 mQuotaTracker.setQuotaFree(false); 164 mQuotaTracker.registerQuotaChangeListener(mQuotaChangeListener); 165 verify(mContext, atLeastOnce()).registerReceiverAsUser( 166 receiverCaptor.capture(), eq(UserHandle.ALL), any(), any(), any()); 167 mReceiver = receiverCaptor.getValue(); 168 } 169 170 @After tearDown()171 public void tearDown() { 172 if (mMockingSession != null) { 173 mMockingSession.finishMocking(); 174 } 175 } 176 177 /** 178 * Returns true if the two {@link LongArrayQueue}s have the same size and the same elements in 179 * the same order. 180 */ longArrayQueueEquals(LongArrayQueue queue1, LongArrayQueue queue2)181 private static boolean longArrayQueueEquals(LongArrayQueue queue1, LongArrayQueue queue2) { 182 if (queue1 == queue2) { 183 return true; 184 } else if (queue1 == null || queue2 == null) { 185 return false; 186 } 187 if (queue1.size() == queue2.size()) { 188 for (int i = 0; i < queue1.size(); ++i) { 189 if (queue1.get(i) != queue2.get(i)) { 190 return false; 191 } 192 } 193 return true; 194 } 195 return false; 196 } 197 advanceElapsedClock(long incrementMs)198 private void advanceElapsedClock(long incrementMs) { 199 mInjector.mElapsedTime += incrementMs; 200 } 201 logEvents(int count)202 private void logEvents(int count) { 203 logEvents(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, count); 204 } 205 logEvents(int userId, String pkgName, String tag, int count)206 private void logEvents(int userId, String pkgName, String tag, int count) { 207 for (int i = 0; i < count; ++i) { 208 mQuotaTracker.noteEvent(userId, pkgName, tag); 209 } 210 } 211 logEventAt(long timeElapsed)212 private void logEventAt(long timeElapsed) { 213 logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, timeElapsed); 214 } 215 logEventAt(int userId, String pkgName, String tag, long timeElapsed)216 private void logEventAt(int userId, String pkgName, String tag, long timeElapsed) { 217 long now = mInjector.getElapsedRealtime(); 218 mInjector.mElapsedTime = timeElapsed; 219 mQuotaTracker.noteEvent(userId, pkgName, tag); 220 mInjector.mElapsedTime = now; 221 } 222 logEventsAt(int userId, String pkgName, String tag, long timeElapsed, int count)223 private void logEventsAt(int userId, String pkgName, String tag, long timeElapsed, int count) { 224 for (int i = 0; i < count; ++i) { 225 logEventAt(userId, pkgName, tag, timeElapsed); 226 } 227 } 228 getEvents(int userId, String packageName, String tag)229 private LongArrayQueue getEvents(int userId, String packageName, String tag) { 230 synchronized (mQuotaTracker.mLock) { 231 return mQuotaTracker.getEvents(userId, packageName, tag); 232 } 233 } 234 updateExecutionStats(final int userId, @NonNull final String packageName, @Nullable final String tag, @NonNull ExecutionStats stats)235 private void updateExecutionStats(final int userId, @NonNull final String packageName, 236 @Nullable final String tag, @NonNull ExecutionStats stats) { 237 synchronized (mQuotaTracker.mLock) { 238 mQuotaTracker.updateExecutionStatsLocked(userId, packageName, tag, stats); 239 } 240 } 241 getExecutionStats(final int userId, @NonNull final String packageName, @Nullable final String tag)242 private ExecutionStats getExecutionStats(final int userId, @NonNull final String packageName, 243 @Nullable final String tag) { 244 synchronized (mQuotaTracker.mLock) { 245 return mQuotaTracker.getExecutionStatsLocked(userId, packageName, tag); 246 } 247 } 248 maybeScheduleStartAlarm(final int userId, @NonNull final String packageName, @Nullable final String tag)249 private void maybeScheduleStartAlarm(final int userId, @NonNull final String packageName, 250 @Nullable final String tag) { 251 synchronized (mQuotaTracker.mLock) { 252 mQuotaTracker.maybeScheduleStartAlarmLocked(userId, packageName, tag); 253 } 254 } 255 maybeScheduleCleanupAlarm()256 private void maybeScheduleCleanupAlarm() { 257 synchronized (mQuotaTracker.mLock) { 258 mQuotaTracker.maybeScheduleCleanupAlarmLocked(); 259 } 260 } 261 deleteObsoleteEvents()262 private void deleteObsoleteEvents() { 263 synchronized (mQuotaTracker.mLock) { 264 mQuotaTracker.deleteObsoleteEventsLocked(); 265 } 266 } 267 268 @Test testDeleteObsoleteEventsLocked()269 public void testDeleteObsoleteEventsLocked() { 270 // Count window size should only apply to event list. 271 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 7, 2 * HOUR_IN_MILLIS); 272 273 final long now = mInjector.getElapsedRealtime(); 274 275 logEventAt(now - 6 * HOUR_IN_MILLIS); 276 logEventAt(now - 5 * HOUR_IN_MILLIS); 277 logEventAt(now - 4 * HOUR_IN_MILLIS); 278 logEventAt(now - 3 * HOUR_IN_MILLIS); 279 logEventAt(now - 2 * HOUR_IN_MILLIS); 280 logEventAt(now - HOUR_IN_MILLIS); 281 logEventAt(now - 1); 282 283 LongArrayQueue expectedEvents = new LongArrayQueue(); 284 expectedEvents.addLast(now - HOUR_IN_MILLIS); 285 expectedEvents.addLast(now - 1); 286 287 deleteObsoleteEvents(); 288 289 LongArrayQueue remainingEvents = getEvents(TEST_USER_ID, TEST_PACKAGE, 290 TEST_TAG); 291 assertTrue(longArrayQueueEquals(expectedEvents, remainingEvents)); 292 } 293 294 @Test testAppRemoval()295 public void testAppRemoval() { 296 final long now = mInjector.getElapsedRealtime(); 297 logEventAt(TEST_USER_ID, "com.android.test.remove", "tag1", now - (6 * HOUR_IN_MILLIS)); 298 logEventAt(TEST_USER_ID, "com.android.test.remove", "tag2", 299 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS)); 300 logEventAt(TEST_USER_ID, "com.android.test.remove", "tag3", now - (HOUR_IN_MILLIS)); 301 // Test that another app isn't affected. 302 LongArrayQueue expected1 = new LongArrayQueue(); 303 expected1.addLast(now - 10 * MINUTE_IN_MILLIS); 304 LongArrayQueue expected2 = new LongArrayQueue(); 305 expected2.addLast(now - 70 * MINUTE_IN_MILLIS); 306 logEventAt(TEST_USER_ID, "com.android.test.stay", "tag1", now - 10 * MINUTE_IN_MILLIS); 307 logEventAt(TEST_USER_ID, "com.android.test.stay", "tag2", now - 70 * MINUTE_IN_MILLIS); 308 309 Intent removal = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED, 310 Uri.fromParts("package", "com.android.test.remove", null)); 311 removal.putExtra(Intent.EXTRA_UID, TEST_UID); 312 mReceiver.onReceive(mContext, removal); 313 assertNull( 314 getEvents(TEST_USER_ID, "com.android.test.remove", "tag1")); 315 assertNull( 316 getEvents(TEST_USER_ID, "com.android.test.remove", "tag2")); 317 assertNull( 318 getEvents(TEST_USER_ID, "com.android.test.remove", "tag3")); 319 assertTrue(longArrayQueueEquals(expected1, 320 getEvents(TEST_USER_ID, "com.android.test.stay", "tag1"))); 321 assertTrue(longArrayQueueEquals(expected2, 322 getEvents(TEST_USER_ID, "com.android.test.stay", "tag2"))); 323 } 324 325 @Test testUserRemoval()326 public void testUserRemoval() { 327 final long now = mInjector.getElapsedRealtime(); 328 logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag1", now - (6 * HOUR_IN_MILLIS)); 329 logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag2", 330 now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS)); 331 logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag3", now - (HOUR_IN_MILLIS)); 332 // Test that another user isn't affected. 333 LongArrayQueue expected = new LongArrayQueue(); 334 expected.addLast(now - (70 * MINUTE_IN_MILLIS)); 335 expected.addLast(now - (10 * MINUTE_IN_MILLIS)); 336 logEventAt(10, TEST_PACKAGE, "tag4", now - (70 * MINUTE_IN_MILLIS)); 337 logEventAt(10, TEST_PACKAGE, "tag4", now - 10 * MINUTE_IN_MILLIS); 338 339 Intent removal = new Intent(Intent.ACTION_USER_REMOVED); 340 removal.putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID); 341 mReceiver.onReceive(mContext, removal); 342 assertNull(getEvents(TEST_USER_ID, TEST_PACKAGE, "tag1")); 343 assertNull(getEvents(TEST_USER_ID, TEST_PACKAGE, "tag2")); 344 assertNull(getEvents(TEST_USER_ID, TEST_PACKAGE, "tag3")); 345 longArrayQueueEquals(expected, getEvents(10, TEST_PACKAGE, "tag4")); 346 } 347 348 @Test testUpdateExecutionStatsLocked_NoTimer()349 public void testUpdateExecutionStatsLocked_NoTimer() { 350 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 3, 24 * HOUR_IN_MILLIS); 351 final long now = mInjector.getElapsedRealtime(); 352 353 // Added in chronological order. 354 logEventAt(now - 4 * HOUR_IN_MILLIS); 355 logEventAt(now - HOUR_IN_MILLIS); 356 logEventAt(now - 5 * MINUTE_IN_MILLIS); 357 logEventAt(now - MINUTE_IN_MILLIS); 358 359 // Test an app that hasn't had any activity. 360 ExecutionStats expectedStats = new ExecutionStats(); 361 ExecutionStats inputStats = new ExecutionStats(); 362 363 inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS; 364 inputStats.countLimit = expectedStats.countLimit = 3; 365 // Invalid time is now +24 hours since there are no sessions at all for the app. 366 expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS; 367 updateExecutionStats(TEST_USER_ID, "com.android.test.not.run", TEST_TAG, 368 inputStats); 369 assertEquals(expectedStats, inputStats); 370 371 // Now test app that has had activity. 372 373 inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS; 374 // Invalid time is now since there was an event exactly windowSizeMs ago. 375 expectedStats.expirationTimeElapsed = now; 376 expectedStats.countInWindow = 1; 377 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); 378 assertEquals(expectedStats, inputStats); 379 380 inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS; 381 expectedStats.expirationTimeElapsed = now + 2 * MINUTE_IN_MILLIS; 382 expectedStats.countInWindow = 1; 383 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); 384 assertEquals(expectedStats, inputStats); 385 386 inputStats.windowSizeMs = expectedStats.windowSizeMs = 4 * MINUTE_IN_MILLIS; 387 expectedStats.expirationTimeElapsed = now + 3 * MINUTE_IN_MILLIS; 388 expectedStats.countInWindow = 1; 389 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); 390 assertEquals(expectedStats, inputStats); 391 392 inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS; 393 // Invalid time is now +44 minutes since the earliest session in the window is now-5 394 // minutes. 395 expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS; 396 expectedStats.countInWindow = 2; 397 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); 398 assertEquals(expectedStats, inputStats); 399 400 inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS; 401 expectedStats.expirationTimeElapsed = now + 45 * MINUTE_IN_MILLIS; 402 expectedStats.countInWindow = 2; 403 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); 404 assertEquals(expectedStats, inputStats); 405 406 inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS; 407 // Invalid time is now since the event is at the very edge of the window 408 // cutoff time. 409 expectedStats.expirationTimeElapsed = now; 410 expectedStats.countInWindow = 3; 411 // App is at event count limit but the oldest session is at the edge of the window, so 412 // in quota time is now. 413 expectedStats.inQuotaTimeElapsed = now; 414 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); 415 assertEquals(expectedStats, inputStats); 416 417 inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; 418 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 419 expectedStats.countInWindow = 3; 420 expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS; 421 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); 422 assertEquals(expectedStats, inputStats); 423 424 inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * HOUR_IN_MILLIS; 425 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 426 expectedStats.countInWindow = 4; 427 expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS; 428 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); 429 assertEquals(expectedStats, inputStats); 430 431 inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS; 432 expectedStats.expirationTimeElapsed = now + 2 * HOUR_IN_MILLIS; 433 expectedStats.countInWindow = 4; 434 expectedStats.inQuotaTimeElapsed = now + 5 * HOUR_IN_MILLIS; 435 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats); 436 assertEquals(expectedStats, inputStats); 437 } 438 439 /** 440 * Tests that getExecutionStatsLocked returns the correct stats. 441 */ 442 @Test testGetExecutionStatsLocked_Values()443 public void testGetExecutionStatsLocked_Values() { 444 // The handler could cause changes to the cached stats, so prevent it from operating in 445 // this test. 446 Handler handler = mQuotaTracker.getHandler(); 447 spyOn(handler); 448 doNothing().when(handler).handleMessage(any()); 449 450 mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS); 451 mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 8 * HOUR_IN_MILLIS); 452 mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 9, 2 * HOUR_IN_MILLIS); 453 mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS); 454 455 final long now = mInjector.getElapsedRealtime(); 456 457 logEventAt(now - 23 * HOUR_IN_MILLIS); 458 logEventAt(now - 7 * HOUR_IN_MILLIS); 459 logEventAt(now - 5 * HOUR_IN_MILLIS); 460 logEventAt(now - 2 * HOUR_IN_MILLIS); 461 logEventAt(now - 5 * MINUTE_IN_MILLIS); 462 463 ExecutionStats expectedStats = new ExecutionStats(); 464 465 // Active 466 expectedStats.expirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS; 467 expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS; 468 expectedStats.countLimit = 10; 469 expectedStats.countInWindow = 1; 470 mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; 471 assertEquals(expectedStats, 472 getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 473 474 // Working 475 expectedStats.expirationTimeElapsed = now; 476 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; 477 expectedStats.countLimit = 9; 478 expectedStats.countInWindow = 2; 479 mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; 480 assertEquals(expectedStats, 481 getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 482 483 // Frequent 484 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 485 expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS; 486 expectedStats.countLimit = 4; 487 expectedStats.countInWindow = 4; 488 expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS; 489 mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; 490 assertEquals(expectedStats, 491 getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 492 493 // Rare 494 expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; 495 expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS; 496 expectedStats.countLimit = 3; 497 expectedStats.countInWindow = 5; 498 expectedStats.inQuotaTimeElapsed = now + 19 * HOUR_IN_MILLIS; 499 mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY; 500 assertEquals(expectedStats, 501 getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 502 } 503 504 /** 505 * Tests that getExecutionStatsLocked returns the correct stats soon after device startup. 506 */ 507 @Test testGetExecutionStatsLocked_Values_BeginningOfTime()508 public void testGetExecutionStatsLocked_Values_BeginningOfTime() { 509 // Set time to 3 minutes after boot. 510 mInjector.mElapsedTime = 3 * MINUTE_IN_MILLIS; 511 512 logEventAt(30_000); 513 logEventAt(MINUTE_IN_MILLIS); 514 logEventAt(2 * MINUTE_IN_MILLIS); 515 516 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); 517 518 ExecutionStats expectedStats = new ExecutionStats(); 519 520 expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS; 521 expectedStats.countLimit = 10; 522 expectedStats.countInWindow = 3; 523 expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + 30_000; 524 assertEquals(expectedStats, 525 getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 526 } 527 528 @Test testisWithinQuota_GlobalQuotaFree()529 public void testisWithinQuota_GlobalQuotaFree() { 530 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS); 531 mQuotaTracker.setQuotaFree(true); 532 assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null)); 533 assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null)); 534 } 535 536 @Test testisWithinQuota_UptcQuotaFree()537 public void testisWithinQuota_UptcQuotaFree() { 538 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS); 539 mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true); 540 assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null)); 541 assertFalse( 542 mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null)); 543 } 544 545 @Test testisWithinQuota_UnderCount()546 public void testisWithinQuota_UnderCount() { 547 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); 548 logEvents(5); 549 assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 550 } 551 552 @Test testisWithinQuota_OverCount()553 public void testisWithinQuota_OverCount() { 554 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS); 555 logEvents(TEST_USER_ID, "com.android.test.spam", TEST_TAG, 30); 556 assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.test.spam", TEST_TAG)); 557 } 558 559 @Test testisWithinQuota_EqualsCount()560 public void testisWithinQuota_EqualsCount() { 561 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS); 562 logEvents(25); 563 assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 564 } 565 566 @Test testisWithinQuota_DifferentCategories()567 public void testisWithinQuota_DifferentCategories() { 568 mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS); 569 mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 24 * HOUR_IN_MILLIS); 570 mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 5, 24 * HOUR_IN_MILLIS); 571 mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 6, 24 * HOUR_IN_MILLIS); 572 573 for (int i = 0; i < 7; ++i) { 574 logEvents(1); 575 576 mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY; 577 assertEquals("Rare has incorrect quota status with " + (i + 1) + " events", 578 i < 2, 579 mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 580 mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; 581 assertEquals("Frequent has incorrect quota status with " + (i + 1) + " events", 582 i < 3, 583 mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 584 mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; 585 assertEquals("Working has incorrect quota status with " + (i + 1) + " events", 586 i < 4, 587 mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 588 mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; 589 assertEquals("Active has incorrect quota status with " + (i + 1) + " events", 590 i < 5, 591 mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 592 } 593 } 594 595 @Test testMaybeScheduleCleanupAlarmLocked()596 public void testMaybeScheduleCleanupAlarmLocked() { 597 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, 24 * HOUR_IN_MILLIS); 598 599 // No sessions saved yet. 600 maybeScheduleCleanupAlarm(); 601 verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any()); 602 603 // Test with only one timing session saved. 604 final long now = mInjector.getElapsedRealtime(); 605 logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 6 * HOUR_IN_MILLIS); 606 maybeScheduleCleanupAlarm(); 607 verify(mAlarmManager, timeout(1000).times(1)) 608 .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); 609 610 // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again. 611 logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS); 612 logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS); 613 maybeScheduleCleanupAlarm(); 614 verify(mAlarmManager, times(1)) 615 .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any()); 616 } 617 618 /** 619 * Tests that maybeScheduleStartAlarm schedules an alarm for the right time. 620 */ 621 @Test testMaybeScheduleStartAlarmLocked()622 public void testMaybeScheduleStartAlarmLocked() { 623 // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests 624 // because it schedules an alarm too. Prevent it from doing so. 625 spyOn(mQuotaTracker); 626 doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked(); 627 628 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 8 * HOUR_IN_MILLIS); 629 630 // No sessions saved yet. 631 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 632 verify(mAlarmManager, never()).setWindow( 633 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 634 635 // Test with timing sessions out of window. 636 final long now = mInjector.getElapsedRealtime(); 637 logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20); 638 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 639 verify(mAlarmManager, never()).setWindow( 640 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 641 642 // Test with timing sessions in window but still in quota. 643 final long start = now - (6 * HOUR_IN_MILLIS); 644 final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS; 645 logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5); 646 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 647 verify(mAlarmManager, never()).setWindow( 648 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 649 650 // Add some more sessions, but still in quota. 651 logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1); 652 logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3); 653 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 654 verify(mAlarmManager, never()).setWindow( 655 anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 656 657 // Test when out of quota. 658 logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1); 659 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 660 verify(mAlarmManager, timeout(1000).times(1)).setWindow( 661 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 662 any(Handler.class)); 663 664 // Alarm already scheduled, so make sure it's not scheduled again. 665 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 666 verify(mAlarmManager, times(1)).setWindow( 667 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), 668 any(Handler.class)); 669 } 670 671 /** Tests that the start alarm is properly rescheduled if the app's category is changed. */ 672 @Test testMaybeScheduleStartAlarmLocked_CategoryChange()673 public void testMaybeScheduleStartAlarmLocked_CategoryChange() { 674 // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests 675 // because it schedules an alarm too. Prevent it from doing so. 676 spyOn(mQuotaTracker); 677 doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked(); 678 679 mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 10, 24 * HOUR_IN_MILLIS); 680 mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 10, 8 * HOUR_IN_MILLIS); 681 mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 10, 2 * HOUR_IN_MILLIS); 682 mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS); 683 684 final long now = mInjector.getElapsedRealtime(); 685 686 // Affects rare bucket 687 logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 12 * HOUR_IN_MILLIS, 9); 688 // Affects frequent and rare buckets 689 logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 4 * HOUR_IN_MILLIS, 4); 690 // Affects working, frequent, and rare buckets 691 final long outOfQuotaTime = now - HOUR_IN_MILLIS; 692 logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, outOfQuotaTime, 7); 693 // Affects all buckets 694 logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 5 * MINUTE_IN_MILLIS, 3); 695 696 InOrder inOrder = inOrder(mAlarmManager); 697 698 // Start in ACTIVE bucket. 699 mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; 700 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 701 inOrder.verify(mAlarmManager, never()) 702 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 703 any(Handler.class)); 704 inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class)); 705 706 // And down from there. 707 final long expectedWorkingAlarmTime = outOfQuotaTime + (2 * HOUR_IN_MILLIS); 708 mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; 709 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 710 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 711 .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(), 712 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 713 714 final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS); 715 mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; 716 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 717 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 718 .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(), 719 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 720 721 final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS); 722 mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY; 723 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 724 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 725 .setWindow(anyInt(), eq(expectedRareAlarmTime), anyLong(), 726 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 727 728 // And back up again. 729 mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY; 730 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 731 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 732 .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(), 733 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 734 735 mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY; 736 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 737 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 738 .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(), 739 eq(TAG_QUOTA_CHECK), any(), any(Handler.class)); 740 741 mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY; 742 maybeScheduleStartAlarm(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 743 inOrder.verify(mAlarmManager, timeout(1000).times(1)) 744 .cancel(any(AlarmManager.OnAlarmListener.class)); 745 inOrder.verify(mAlarmManager, timeout(1000).times(0)) 746 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), 747 any(Handler.class)); 748 } 749 750 @Test testConstantsUpdating_ValidValues()751 public void testConstantsUpdating_ValidValues() { 752 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 60_000); 753 assertEquals(0, mQuotaTracker.getLimit(SINGLE_CATEGORY)); 754 assertEquals(60_000, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY)); 755 } 756 757 @Test testConstantsUpdating_InvalidValues()758 public void testConstantsUpdating_InvalidValues() { 759 // Test negatives. 760 try { 761 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, -1, 5000); 762 fail("Negative count limit didn't throw an exception"); 763 } catch (IllegalArgumentException e) { 764 // Success 765 } 766 try { 767 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 1, -1); 768 fail("Negative count window size didn't throw an exception"); 769 } catch (IllegalArgumentException e) { 770 // Success 771 } 772 773 // Test window sizes too low. 774 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 1); 775 assertEquals(MIN_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY)); 776 777 // Test window sizes too high. 778 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 365 * 24 * HOUR_IN_MILLIS); 779 assertEquals(MAX_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY)); 780 } 781 782 /** Tests that events aren't counted when global quota is free. */ 783 @Test testLogEvent_GlobalQuotaFree()784 public void testLogEvent_GlobalQuotaFree() { 785 mQuotaTracker.setQuotaFree(true); 786 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); 787 788 ExecutionStats stats = 789 getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 790 assertEquals(0, stats.countInWindow); 791 792 for (int i = 0; i < 10; ++i) { 793 mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 794 advanceElapsedClock(10 * SECOND_IN_MILLIS); 795 796 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); 797 assertEquals(0, stats.countInWindow); 798 } 799 } 800 801 /** 802 * Tests that events are counted when global quota is not free. 803 */ 804 @Test testLogEvent_GlobalQuotaNotFree()805 public void testLogEvent_GlobalQuotaNotFree() { 806 mQuotaTracker.setQuotaFree(false); 807 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); 808 809 ExecutionStats stats = 810 getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 811 assertEquals(0, stats.countInWindow); 812 813 for (int i = 0; i < 10; ++i) { 814 mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 815 advanceElapsedClock(10 * SECOND_IN_MILLIS); 816 817 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); 818 assertEquals(i + 1, stats.countInWindow); 819 } 820 } 821 822 /** Tests that events aren't counted when the uptc quota is free. */ 823 @Test testLogEvent_UptcQuotaFree()824 public void testLogEvent_UptcQuotaFree() { 825 mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true); 826 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); 827 828 ExecutionStats stats = 829 getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 830 assertEquals(0, stats.countInWindow); 831 832 for (int i = 0; i < 10; ++i) { 833 mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 834 advanceElapsedClock(10 * SECOND_IN_MILLIS); 835 836 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); 837 assertEquals(0, stats.countInWindow); 838 } 839 } 840 841 /** 842 * Tests that events are counted when UPTC quota is not free. 843 */ 844 @Test testLogEvent_UptcQuotaNotFree()845 public void testLogEvent_UptcQuotaNotFree() { 846 mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, false); 847 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); 848 849 ExecutionStats stats = 850 getExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 851 assertEquals(0, stats.countInWindow); 852 853 for (int i = 0; i < 10; ++i) { 854 mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 855 advanceElapsedClock(10 * SECOND_IN_MILLIS); 856 857 updateExecutionStats(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats); 858 assertEquals(i + 1, stats.countInWindow); 859 } 860 } 861 862 /** 863 * Tests that QuotaChangeListeners are notified when a UPTC reaches its count quota. 864 */ 865 @Test testTracking_OutOfQuota()866 public void testTracking_OutOfQuota() { 867 spyOn(mQuotaChangeListener); 868 869 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS); 870 logEvents(9); 871 872 mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 873 874 // Wait for some extra time to allow for processing. 875 verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(1)) 876 .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG)); 877 assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 878 } 879 880 /** 881 * Tests that QuotaChangeListeners are not incorrectly notified after a UPTC event is logged 882 * quota times. 883 */ 884 @Test testTracking_InQuota()885 public void testTracking_InQuota() { 886 spyOn(mQuotaChangeListener); 887 888 mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, MINUTE_IN_MILLIS); 889 890 // Log an event once per minute. This is well below the quota, so listeners should not be 891 // notified. 892 for (int i = 0; i < 10; i++) { 893 advanceElapsedClock(MINUTE_IN_MILLIS); 894 mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG); 895 } 896 897 // Wait for some extra time to allow for processing. 898 verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(0)) 899 .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG)); 900 assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG)); 901 } 902 } 903