• Home
  • 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());
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());
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());
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());
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(), any());
621 
622         // Alarm already scheduled, so make sure it's not scheduled again.
623         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
624         verify(mAlarmManager, times(1)).setWindow(
625                 anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
626     }
627 
628     /** Tests that the start alarm is properly rescheduled if the app's category is changed. */
629     @Test
testMaybeScheduleStartAlarmLocked_CategoryChange()630     public void testMaybeScheduleStartAlarmLocked_CategoryChange() {
631         // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests
632         // because it schedules an alarm too. Prevent it from doing so.
633         spyOn(mQuotaTracker);
634         doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked();
635 
636         mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 10, 24 * HOUR_IN_MILLIS);
637         mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 10, 8 * HOUR_IN_MILLIS);
638         mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
639         mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS);
640 
641         final long now = mInjector.getElapsedRealtime();
642 
643         // Affects rare bucket
644         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 12 * HOUR_IN_MILLIS, 9);
645         // Affects frequent and rare buckets
646         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 4 * HOUR_IN_MILLIS, 4);
647         // Affects working, frequent, and rare buckets
648         final long outOfQuotaTime = now - HOUR_IN_MILLIS;
649         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, outOfQuotaTime, 7);
650         // Affects all buckets
651         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 5 * MINUTE_IN_MILLIS, 3);
652 
653         InOrder inOrder = inOrder(mAlarmManager);
654 
655         // Start in ACTIVE bucket.
656         mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
657         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
658         inOrder.verify(mAlarmManager, never())
659                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
660         inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
661 
662         // And down from there.
663         final long expectedWorkingAlarmTime = outOfQuotaTime + (2 * HOUR_IN_MILLIS);
664         mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
665         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
666         inOrder.verify(mAlarmManager, timeout(1000).times(1))
667                 .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
668                         eq(TAG_QUOTA_CHECK), any(), any());
669 
670         final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS);
671         mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
672         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
673         inOrder.verify(mAlarmManager, timeout(1000).times(1))
674                 .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
675                         eq(TAG_QUOTA_CHECK), any(), any());
676 
677         final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS);
678         mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
679         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
680         inOrder.verify(mAlarmManager, timeout(1000).times(1))
681                 .setWindow(anyInt(), eq(expectedRareAlarmTime), anyLong(),
682                         eq(TAG_QUOTA_CHECK), any(), any());
683 
684         // And back up again.
685         mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
686         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
687         inOrder.verify(mAlarmManager, timeout(1000).times(1))
688                 .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
689                         eq(TAG_QUOTA_CHECK), any(), any());
690 
691         mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
692         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
693         inOrder.verify(mAlarmManager, timeout(1000).times(1))
694                 .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
695                         eq(TAG_QUOTA_CHECK), any(), any());
696 
697         mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
698         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
699         inOrder.verify(mAlarmManager, timeout(1000).times(1))
700                 .cancel(any(AlarmManager.OnAlarmListener.class));
701         inOrder.verify(mAlarmManager, timeout(1000).times(0))
702                 .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
703     }
704 
705     @Test
testConstantsUpdating_ValidValues()706     public void testConstantsUpdating_ValidValues() {
707         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 60_000);
708         assertEquals(0, mQuotaTracker.getLimit(SINGLE_CATEGORY));
709         assertEquals(60_000, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
710     }
711 
712     @Test
testConstantsUpdating_InvalidValues()713     public void testConstantsUpdating_InvalidValues() {
714         // Test negatives.
715         try {
716             mQuotaTracker.setCountLimit(SINGLE_CATEGORY, -1, 5000);
717             fail("Negative count limit didn't throw an exception");
718         } catch (IllegalArgumentException e) {
719             // Success
720         }
721         try {
722             mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 1, -1);
723             fail("Negative count window size didn't throw an exception");
724         } catch (IllegalArgumentException e) {
725             // Success
726         }
727 
728         // Test window sizes too low.
729         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 1);
730         assertEquals(MIN_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
731 
732         // Test window sizes too high.
733         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 365 * 24 * HOUR_IN_MILLIS);
734         assertEquals(MAX_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
735     }
736 
737     /** Tests that events aren't counted when global quota is free. */
738     @Test
testLogEvent_GlobalQuotaFree()739     public void testLogEvent_GlobalQuotaFree() {
740         mQuotaTracker.setQuotaFree(true);
741         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
742 
743         ExecutionStats stats =
744                 mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
745         assertEquals(0, stats.countInWindow);
746 
747         for (int i = 0; i < 10; ++i) {
748             mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
749             advanceElapsedClock(10 * SECOND_IN_MILLIS);
750 
751             mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
752             assertEquals(0, stats.countInWindow);
753         }
754     }
755 
756     /**
757      * Tests that events are counted when global quota is not free.
758      */
759     @Test
testLogEvent_GlobalQuotaNotFree()760     public void testLogEvent_GlobalQuotaNotFree() {
761         mQuotaTracker.setQuotaFree(false);
762         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
763 
764         ExecutionStats stats =
765                 mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
766         assertEquals(0, stats.countInWindow);
767 
768         for (int i = 0; i < 10; ++i) {
769             mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
770             advanceElapsedClock(10 * SECOND_IN_MILLIS);
771 
772             mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
773             assertEquals(i + 1, stats.countInWindow);
774         }
775     }
776 
777     /** Tests that events aren't counted when the uptc quota is free. */
778     @Test
testLogEvent_UptcQuotaFree()779     public void testLogEvent_UptcQuotaFree() {
780         mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true);
781         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
782 
783         ExecutionStats stats =
784                 mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
785         assertEquals(0, stats.countInWindow);
786 
787         for (int i = 0; i < 10; ++i) {
788             mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
789             advanceElapsedClock(10 * SECOND_IN_MILLIS);
790 
791             mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
792             assertEquals(0, stats.countInWindow);
793         }
794     }
795 
796     /**
797      * Tests that events are counted when UPTC quota is not free.
798      */
799     @Test
testLogEvent_UptcQuotaNotFree()800     public void testLogEvent_UptcQuotaNotFree() {
801         mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, false);
802         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
803 
804         ExecutionStats stats =
805                 mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
806         assertEquals(0, stats.countInWindow);
807 
808         for (int i = 0; i < 10; ++i) {
809             mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
810             advanceElapsedClock(10 * SECOND_IN_MILLIS);
811 
812             mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
813             assertEquals(i + 1, stats.countInWindow);
814         }
815     }
816 
817     /**
818      * Tests that QuotaChangeListeners are notified when a UPTC reaches its count quota.
819      */
820     @Test
testTracking_OutOfQuota()821     public void testTracking_OutOfQuota() {
822         spyOn(mQuotaChangeListener);
823 
824         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
825         logEvents(9);
826 
827         mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
828 
829         // Wait for some extra time to allow for processing.
830         verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(1))
831                 .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG));
832         assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
833     }
834 
835     /**
836      * Tests that QuotaChangeListeners are not incorrectly notified after a UPTC event is logged
837      * quota times.
838      */
839     @Test
testTracking_InQuota()840     public void testTracking_InQuota() {
841         spyOn(mQuotaChangeListener);
842 
843         mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, MINUTE_IN_MILLIS);
844 
845         // Log an event once per minute. This is well below the quota, so listeners should not be
846         // notified.
847         for (int i = 0; i < 10; i++) {
848             advanceElapsedClock(MINUTE_IN_MILLIS);
849             mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
850         }
851 
852         // Wait for some extra time to allow for processing.
853         verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(0))
854                 .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG));
855         assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
856     }
857 }
858