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