1 /*
2  * Copyright (C) 2021 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.usage;
18 
19 import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
20 import static android.app.usage.UsageEvents.Event.APP_COMPONENT_USED;
21 import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertNotNull;
26 import static org.junit.Assert.assertTrue;
27 import static org.mockito.Mockito.mockitoSession;
28 
29 import android.app.usage.UsageEvents;
30 import android.app.usage.UsageEvents.Event;
31 import android.content.Context;
32 import android.os.SystemClock;
33 import android.text.format.DateUtils;
34 import android.util.Log;
35 
36 import androidx.test.InstrumentationRegistry;
37 import androidx.test.runner.AndroidJUnit4;
38 
39 import com.android.server.usage.UserUsageStatsService.StatsUpdatedListener;
40 
41 import org.junit.After;
42 import org.junit.Before;
43 import org.junit.Test;
44 import org.junit.runner.RunWith;
45 import org.mockito.Mock;
46 import org.mockito.MockitoSession;
47 import org.mockito.quality.Strictness;
48 
49 import java.io.File;
50 import java.util.HashMap;
51 
52 @RunWith(AndroidJUnit4.class)
53 public class UserUsageStatsServiceTest {
54     private static final String TAG = UserUsageStatsServiceTest.class.getSimpleName();
55 
56     private static final int TEST_USER_ID = 0;
57     private static final String TEST_PACKAGE_NAME = "test.package";
58     private static final long TIME_INTERVAL_MILLIS = DateUtils.DAY_IN_MILLIS;
59 
60     private UserUsageStatsService mService;
61     private MockitoSession mMockitoSession;
62 
63     private File mDir;
64 
65     @Mock
66     private Context mContext;
67     @Mock
68     private StatsUpdatedListener mStatsUpdatedListener;
69 
70     @Before
setUp()71     public void setUp() {
72         mMockitoSession = mockitoSession()
73                 .initMocks(this)
74                 .strictness(Strictness.LENIENT)
75                 .startMocking();
76 
77         // Deleting in tearDown() doesn't always work, so adding a unique suffix to each test
78         // directory to ensure sequential test runs don't interfere with each other.
79         mDir = new File(InstrumentationRegistry.getContext().getCacheDir(),
80                 "test_" + System.currentTimeMillis());
81         mService = new UserUsageStatsService(mContext, TEST_USER_ID, mDir, mStatsUpdatedListener);
82 
83         HashMap<String, Long> installedPkgs = new HashMap<>();
84         installedPkgs.put(TEST_PACKAGE_NAME, System.currentTimeMillis());
85 
86         mService.init(System.currentTimeMillis(), installedPkgs, true);
87     }
88 
89     @After
tearDown()90     public void tearDown() {
91         if (mDir != null && mDir.exists() && !mDir.delete()) {
92             Log.d(TAG, "Failed to delete test directory");
93         }
94         if (mMockitoSession != null) {
95             mMockitoSession.finishMocking();
96         }
97     }
98 
99     @Test
testReportEvent_eventAppearsInQueries()100     public void testReportEvent_eventAppearsInQueries() {
101         Event event = new Event(ACTIVITY_RESUMED, SystemClock.elapsedRealtime());
102         event.mPackage = TEST_PACKAGE_NAME;
103         mService.reportEvent(event);
104 
105         // Force persist the event instead of waiting for it to be processed on the handler.
106         mService.persistActiveStats();
107 
108         long now = System.currentTimeMillis();
109         long startTime = now - TIME_INTERVAL_MILLIS;
110         UsageEvents events = mService.queryEventsForPackage(
111                 startTime, now, TEST_PACKAGE_NAME, false /* includeTaskRoot */);
112 
113         boolean hasTestEvent = false;
114         while (events != null && events.hasNextEvent()) {
115             Event outEvent = new Event();
116             events.getNextEvent(outEvent);
117             if (outEvent.mEventType == ACTIVITY_RESUMED) {
118                 hasTestEvent = true;
119             }
120         }
121         assertTrue(hasTestEvent);
122     }
123 
124     @Test
testReportEvent_packageUsedEventNotTracked()125     public void testReportEvent_packageUsedEventNotTracked() {
126         // For APP_COMPONENT_USED event, the time stamp should have been converted to current time
127         // before reported here.
128         Event event = new Event(APP_COMPONENT_USED, System.currentTimeMillis());
129         event.mPackage = TEST_PACKAGE_NAME;
130         mService.reportEvent(event);
131 
132         // Force persist the event instead of waiting for it to be processed on the handler.
133         mService.persistActiveStats();
134 
135         long now = System.currentTimeMillis();
136         long startTime = now - TIME_INTERVAL_MILLIS;
137         UsageEvents events = mService.queryEventsForPackage(
138                 startTime, now, TEST_PACKAGE_NAME, false /* includeTaskRoot */);
139 
140         boolean hasTestEvent = false;
141         while (events != null && events.hasNextEvent()) {
142             Event outEvent = new Event();
143             events.getNextEvent(outEvent);
144             if (outEvent.mEventType == APP_COMPONENT_USED) {
145                 hasTestEvent = true;
146             }
147         }
148         assertFalse(hasTestEvent);
149     }
150 
151     @Test
testQueryEarliestEventsForPackage()152     public void testQueryEarliestEventsForPackage() {
153         Event event1 = new Event(NOTIFICATION_SEEN, SystemClock.elapsedRealtime());
154         event1.mPackage = TEST_PACKAGE_NAME;
155         mService.reportEvent(event1);
156         Event event2 = new Event(ACTIVITY_RESUMED, SystemClock.elapsedRealtime());
157         event2.mPackage = TEST_PACKAGE_NAME;
158         mService.reportEvent(event2);
159 
160         // Force persist the events instead of waiting for them to be processed on the handler.
161         mService.persistActiveStats();
162 
163         long now = System.currentTimeMillis();
164         long startTime = now - TIME_INTERVAL_MILLIS;
165         UsageEvents events = mService.queryEarliestEventsForPackage(
166                 startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
167 
168         assertNotNull(events);
169         boolean hasTestEvent = false;
170         int count = 0;
171         while (events.hasNextEvent()) {
172             count++;
173             Event outEvent = new Event();
174             events.getNextEvent(outEvent);
175             if (outEvent.mEventType == ACTIVITY_RESUMED) {
176                 hasTestEvent = true;
177             }
178         }
179         assertTrue(hasTestEvent);
180         assertEquals(2, count);
181     }
182 
183     /** Tests that the API works as expected even with the caching system. */
184     @Test
testQueryEarliestEventsForPackage_Caching()185     public void testQueryEarliestEventsForPackage_Caching() throws Exception {
186         final long forcedDiff = 5000;
187         Event event1 = new Event(NOTIFICATION_SEEN, SystemClock.elapsedRealtime());
188         event1.mPackage = TEST_PACKAGE_NAME;
189         mService.reportEvent(event1);
190         final long event1ReportTime = System.currentTimeMillis();
191         Thread.sleep(forcedDiff);
192         Event event2 = new Event(ACTIVITY_RESUMED, SystemClock.elapsedRealtime());
193         event2.mPackage = TEST_PACKAGE_NAME;
194         mService.reportEvent(event2);
195         final long event2ReportTime = System.currentTimeMillis();
196 
197         // Force persist the events instead of waiting for them to be processed on the handler.
198         mService.persistActiveStats();
199 
200         long now = System.currentTimeMillis();
201         long startTime = now - forcedDiff * 2;
202         UsageEvents events = mService.queryEarliestEventsForPackage(
203                 startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
204 
205         assertNotNull(events);
206         boolean hasTestEvent = false;
207         int count = 0;
208         while (events.hasNextEvent()) {
209             count++;
210             Event outEvent = new Event();
211             events.getNextEvent(outEvent);
212             if (outEvent.mEventType == ACTIVITY_RESUMED) {
213                 hasTestEvent = true;
214             }
215         }
216         assertTrue(hasTestEvent);
217         assertEquals(2, count);
218 
219         // Query again
220         events = mService.queryEarliestEventsForPackage(
221                 startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
222 
223         assertNotNull(events);
224         hasTestEvent = false;
225         count = 0;
226         while (events.hasNextEvent()) {
227             count++;
228             Event outEvent = new Event();
229             events.getNextEvent(outEvent);
230             if (outEvent.mEventType == ACTIVITY_RESUMED) {
231                 hasTestEvent = true;
232             }
233         }
234         assertTrue(hasTestEvent);
235         assertEquals(2, count);
236 
237         // Query around just the first event
238         now = event1ReportTime;
239         startTime = now - forcedDiff * 2;
240         events = mService.queryEarliestEventsForPackage(
241                 startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
242 
243         assertNotNull(events);
244         hasTestEvent = false;
245         count = 0;
246         while (events.hasNextEvent()) {
247             count++;
248             Event outEvent = new Event();
249             events.getNextEvent(outEvent);
250             if (outEvent.mEventType == ACTIVITY_RESUMED) {
251                 hasTestEvent = true;
252             }
253         }
254         assertFalse(hasTestEvent);
255         assertEquals(1, count);
256 
257         // Shift query around the first event, still exclude the second
258         now = event1ReportTime + forcedDiff / 2;
259         startTime = event1ReportTime - forcedDiff / 2;
260         events = mService.queryEarliestEventsForPackage(
261                 startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
262 
263         assertNotNull(events);
264         hasTestEvent = false;
265         count = 0;
266         while (events.hasNextEvent()) {
267             count++;
268             Event outEvent = new Event();
269             events.getNextEvent(outEvent);
270             if (outEvent.mEventType == ACTIVITY_RESUMED) {
271                 hasTestEvent = true;
272             }
273         }
274         assertFalse(hasTestEvent);
275         assertEquals(1, count);
276 
277         // Shift query around the second event only
278         now = event2ReportTime + 1;
279         startTime = event1ReportTime + forcedDiff / 4;
280         events = mService.queryEarliestEventsForPackage(
281                 startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
282 
283         assertNotNull(events);
284         hasTestEvent = false;
285         count = 0;
286         while (events.hasNextEvent()) {
287             count++;
288             Event outEvent = new Event();
289             events.getNextEvent(outEvent);
290             if (outEvent.mEventType == ACTIVITY_RESUMED) {
291                 hasTestEvent = true;
292             }
293         }
294         assertTrue(hasTestEvent);
295         assertEquals(1, count);
296 
297         // Shift query around both events
298         now = event2ReportTime + 1;
299         startTime = now - forcedDiff * 2;
300         events = mService.queryEarliestEventsForPackage(
301                 startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
302 
303         assertNotNull(events);
304         hasTestEvent = false;
305         count = 0;
306         while (events.hasNextEvent()) {
307             count++;
308             Event outEvent = new Event();
309             events.getNextEvent(outEvent);
310             if (outEvent.mEventType == ACTIVITY_RESUMED) {
311                 hasTestEvent = true;
312             }
313         }
314         assertTrue(hasTestEvent);
315         assertEquals(2, count);
316 
317         // Query around just the first event and then shift end time to include second event
318         now = event1ReportTime;
319         startTime = now - forcedDiff * 2;
320         events = mService.queryEarliestEventsForPackage(
321                 startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
322 
323         assertNotNull(events);
324         hasTestEvent = false;
325         count = 0;
326         while (events.hasNextEvent()) {
327             count++;
328             Event outEvent = new Event();
329             events.getNextEvent(outEvent);
330             if (outEvent.mEventType == ACTIVITY_RESUMED) {
331                 hasTestEvent = true;
332             }
333         }
334         assertFalse(hasTestEvent);
335         assertEquals(1, count);
336 
337         now = event2ReportTime + 1;
338         events = mService.queryEarliestEventsForPackage(
339                 startTime, now, TEST_PACKAGE_NAME, ACTIVITY_RESUMED);
340 
341         assertNotNull(events);
342         hasTestEvent = false;
343         count = 0;
344         while (events.hasNextEvent()) {
345             count++;
346             Event outEvent = new Event();
347             events.getNextEvent(outEvent);
348             if (outEvent.mEventType == ACTIVITY_RESUMED) {
349                 hasTestEvent = true;
350             }
351         }
352         assertTrue(hasTestEvent);
353         assertEquals(2, count);
354     }
355 }
356