• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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