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