1 /*
2  * Copyright (C) 2020 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.people.data;
18 
19 import static com.android.server.people.data.TestUtils.timestamp;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertTrue;
26 import static org.mockito.ArgumentMatchers.anyBoolean;
27 import static org.mockito.ArgumentMatchers.anyInt;
28 import static org.mockito.ArgumentMatchers.anyLong;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.annotation.UserIdInt;
35 import android.app.usage.UsageEvents;
36 import android.app.usage.UsageStats;
37 import android.app.usage.UsageStatsManagerInternal;
38 import android.content.Context;
39 import android.content.LocusId;
40 import android.util.ArrayMap;
41 
42 import androidx.test.InstrumentationRegistry;
43 
44 import com.android.server.LocalServices;
45 
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.junit.runners.JUnit4;
51 import org.mockito.Mock;
52 import org.mockito.MockitoAnnotations;
53 
54 import java.io.File;
55 import java.util.ArrayList;
56 import java.util.Arrays;
57 import java.util.List;
58 import java.util.Map;
59 import java.util.Set;
60 import java.util.concurrent.ScheduledExecutorService;
61 import java.util.function.Predicate;
62 
63 @RunWith(JUnit4.class)
64 public final class UsageStatsQueryHelperTest {
65 
66     private static final int USER_ID_PRIMARY = 0;
67     private static final String PKG_NAME_1 = "pkg_1";
68     private static final String PKG_NAME_2 = "pkg_2";
69     private static final String ACTIVITY_NAME = "TestActivity";
70     private static final String SHORTCUT_ID = "abc";
71     private static final LocusId LOCUS_ID_1 = new LocusId("locus_1");
72     private static final LocusId LOCUS_ID_2 = new LocusId("locus_2");
73 
74     @Mock
75     private UsageStatsManagerInternal mUsageStatsManagerInternal;
76     @Mock
77     private UsageStatsQueryHelper.EventListener mEventListener;
78 
79     private TestPackageData mPackageData;
80     private UsageStatsQueryHelper mHelper;
81 
82     @Before
setUp()83     public void setUp() {
84         MockitoAnnotations.initMocks(this);
85 
86         addLocalServiceMock(UsageStatsManagerInternal.class, mUsageStatsManagerInternal);
87 
88         Context ctx = InstrumentationRegistry.getContext();
89         File testDir = new File(ctx.getCacheDir(), "testdir");
90         ScheduledExecutorService scheduledExecutorService = new MockScheduledExecutorService();
91 
92         mPackageData = new TestPackageData(PKG_NAME_1, USER_ID_PRIMARY, pkg -> false, pkg -> false,
93                 scheduledExecutorService, testDir);
94         mPackageData.mConversationStore.mConversationInfo = new ConversationInfo.Builder()
95                 .setShortcutId(SHORTCUT_ID)
96                 .setLocusId(LOCUS_ID_1)
97                 .build();
98 
99         mHelper = new UsageStatsQueryHelper(USER_ID_PRIMARY, pkg -> mPackageData, mEventListener);
100     }
101 
102     @After
tearDown()103     public void tearDown() {
104         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
105     }
106 
107     @Test
testQueryNoEvents()108     public void testQueryNoEvents() {
109         assertFalse(mHelper.querySince(50L));
110     }
111 
112     @Test
testQueryShortcutInvocationEvent()113     public void testQueryShortcutInvocationEvent() {
114         addUsageEvents(createShortcutInvocationEvent(100L));
115 
116         assertTrue(mHelper.querySince(50L));
117         assertEquals(100L, mHelper.getLastEventTimestamp());
118         Event expectedEvent = new Event(100L, Event.TYPE_SHORTCUT_INVOCATION);
119         List<Event> events = mPackageData.mEventStore.mShortcutEventHistory.queryEvents(
120                 Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
121         assertEquals(1, events.size());
122         assertEquals(expectedEvent, events.get(0));
123     }
124 
125     @Test
testInAppConversationSwitch()126     public void testInAppConversationSwitch() {
127         addUsageEvents(
128                 createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
129                 createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId()));
130 
131         assertTrue(mHelper.querySince(50_000L));
132         assertEquals(110_000L, mHelper.getLastEventTimestamp());
133         List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
134                 Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
135         assertEquals(1, events.size());
136         assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
137         verify(mEventListener).onEvent(
138                 mPackageData, mPackageData.mConversationStore.mConversationInfo, events.get(0));
139     }
140 
141     @Test
testInAppConversationExplicitlyEnd()142     public void testInAppConversationExplicitlyEnd() {
143         addUsageEvents(
144                 createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
145                 createLocusIdSetEvent(110_000L, null));
146 
147         assertTrue(mHelper.querySince(50_000L));
148         assertEquals(110_000L, mHelper.getLastEventTimestamp());
149         List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
150                 Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
151         assertEquals(1, events.size());
152         assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
153         verify(mEventListener).onEvent(
154                 mPackageData, mPackageData.mConversationStore.mConversationInfo, events.get(0));
155     }
156 
157     @Test
testInAppConversationImplicitlyEnd()158     public void testInAppConversationImplicitlyEnd() {
159         addUsageEvents(
160                 createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
161                 createActivityStoppedEvent(110_000L));
162 
163         assertTrue(mHelper.querySince(50_000L));
164         assertEquals(110_000L, mHelper.getLastEventTimestamp());
165         List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
166                 Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
167         assertEquals(1, events.size());
168         assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
169         verify(mEventListener).onEvent(
170                 mPackageData, mPackageData.mConversationStore.mConversationInfo, events.get(0));
171     }
172 
173     @Test
testMultipleInAppConversations()174     public void testMultipleInAppConversations() {
175         addUsageEvents(
176                 createLocusIdSetEvent(100_000L, LOCUS_ID_1.getId()),
177                 createLocusIdSetEvent(110_000L, LOCUS_ID_2.getId()),
178                 createLocusIdSetEvent(130_000L, LOCUS_ID_1.getId()),
179                 createActivityStoppedEvent(160_000L));
180 
181         assertTrue(mHelper.querySince(50_000L));
182         assertEquals(160_000L, mHelper.getLastEventTimestamp());
183         List<Event> events = mPackageData.mEventStore.mLocusEventHistory.queryEvents(
184                 Event.ALL_EVENT_TYPES, 0L, Long.MAX_VALUE);
185         assertEquals(3, events.size());
186         assertEquals(createInAppConversationEvent(100_000L, 10), events.get(0));
187         assertEquals(createInAppConversationEvent(110_000L, 20), events.get(1));
188         assertEquals(createInAppConversationEvent(130_000L, 30), events.get(2));
189     }
190 
191     @Test
testQueryAppMovingToForegroundEvents()192     public void testQueryAppMovingToForegroundEvents() {
193         addUsageEvents(
194                 createShortcutInvocationEvent(100_000L),
195                 createActivityResumedEvent(110_000L),
196                 createActivityStoppedEvent(120_000L),
197                 createActivityResumedEvent(130_000L));
198 
199         List<UsageEvents.Event> events = mHelper.queryAppMovingToForegroundEvents(USER_ID_PRIMARY,
200                 90_000L,
201                 200_000L);
202 
203         assertEquals(2, events.size());
204         assertEquals(UsageEvents.Event.ACTIVITY_RESUMED, events.get(0).getEventType());
205         assertEquals(110_000L, events.get(0).getTimeStamp());
206         assertEquals(UsageEvents.Event.ACTIVITY_RESUMED, events.get(1).getEventType());
207         assertEquals(130_000L, events.get(1).getTimeStamp());
208     }
209 
210     @Test
testQueryAppUsageStats()211     public void testQueryAppUsageStats() {
212         UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2, createDummyChooserCounts());
213         UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3, null);
214         UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1, createDummyChooserCounts());
215         when(mUsageStatsManagerInternal.queryUsageStatsForUser(anyInt(), anyInt(), anyLong(),
216                 anyLong(), anyBoolean())).thenReturn(
217                 List.of(packageStats1, packageStats2, packageStats3));
218 
219         Map<String, AppUsageStatsData> appLaunchChooserCountCounts =
220                 mHelper.queryAppUsageStats(USER_ID_PRIMARY, 90_000L,
221                         200_000L, Set.of(PKG_NAME_1, PKG_NAME_2));
222 
223         assertEquals(2, appLaunchChooserCountCounts.size());
224         assertEquals(4, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getChosenCount());
225         assertEquals(5, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getLaunchCount());
226         assertEquals(4, (long) appLaunchChooserCountCounts.get(PKG_NAME_2).getChosenCount());
227         assertEquals(1, (long) appLaunchChooserCountCounts.get(PKG_NAME_2).getLaunchCount());
228     }
229 
230     @Test
testQueryAppUsageStats_packageNameFiltered()231     public void testQueryAppUsageStats_packageNameFiltered() {
232         UsageStats packageStats1 = createUsageStats(PKG_NAME_1, 2, createDummyChooserCounts());
233         UsageStats packageStats2 = createUsageStats(PKG_NAME_1, 3, createDummyChooserCounts());
234         UsageStats packageStats3 = createUsageStats(PKG_NAME_2, 1, null);
235         when(mUsageStatsManagerInternal.queryUsageStatsForUser(anyInt(), anyInt(), anyLong(),
236                 anyLong(), anyBoolean())).thenReturn(
237                 List.of(packageStats1, packageStats2, packageStats3));
238 
239         Map<String, AppUsageStatsData> appLaunchChooserCountCounts =
240                 mHelper.queryAppUsageStats(USER_ID_PRIMARY, 90_000L,
241                         200_000L,
242                         Set.of(PKG_NAME_1));
243 
244         assertEquals(1, appLaunchChooserCountCounts.size());
245         assertEquals(8, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getChosenCount());
246         assertEquals(5, (long) appLaunchChooserCountCounts.get(PKG_NAME_1).getLaunchCount());
247     }
248 
249     @Test
testQueryAppUsageStats_nullUsageStats()250     public void testQueryAppUsageStats_nullUsageStats() {
251         when(mUsageStatsManagerInternal.queryUsageStatsForUser(anyInt(), anyInt(), anyLong(),
252                 anyLong(), anyBoolean())).thenReturn(null);
253 
254         Map<String, AppUsageStatsData> appLaunchChooserCountCounts =
255                 mHelper.queryAppUsageStats(USER_ID_PRIMARY, 90_000L,
256                         200_000L,
257                         Set.of(PKG_NAME_1));
258 
259         assertThat(appLaunchChooserCountCounts).isEmpty();
260     }
261 
addUsageEvents(UsageEvents.Event... events)262     private void addUsageEvents(UsageEvents.Event... events) {
263         UsageEvents usageEvents = new UsageEvents(Arrays.asList(events), new String[]{});
264         when(mUsageStatsManagerInternal.queryEventsForUser(anyInt(), anyLong(), anyLong(),
265                 anyInt())).thenReturn(usageEvents);
266     }
267 
createUsageStats(String packageName, int launchCount, ArrayMap<String, ArrayMap<String, Integer>> chooserCounts)268     private static UsageStats createUsageStats(String packageName, int launchCount,
269             ArrayMap<String, ArrayMap<String, Integer>> chooserCounts) {
270         UsageStats packageStats = new UsageStats();
271         packageStats.mPackageName = packageName;
272         packageStats.mAppLaunchCount = launchCount;
273         packageStats.mChooserCounts = chooserCounts;
274         return packageStats;
275     }
276 
createDummyChooserCounts()277     private static ArrayMap<String, ArrayMap<String, Integer>> createDummyChooserCounts() {
278         ArrayMap<String, ArrayMap<String, Integer>> chooserCounts = new ArrayMap<>();
279         ArrayMap<String, Integer> counts1 = new ArrayMap<>();
280         counts1.put("text", 2);
281         counts1.put("image", 1);
282         chooserCounts.put("intent1", counts1);
283         ArrayMap<String, Integer> counts2 = new ArrayMap<>();
284         counts2.put("video", 1);
285         chooserCounts.put("intent2", counts2);
286         return chooserCounts;
287     }
288 
addLocalServiceMock(Class<T> clazz, T mock)289     private static <T> void addLocalServiceMock(Class<T> clazz, T mock) {
290         LocalServices.removeServiceForTest(clazz);
291         LocalServices.addService(clazz, mock);
292     }
293 
createShortcutInvocationEvent(long timestamp)294     private static UsageEvents.Event createShortcutInvocationEvent(long timestamp) {
295         UsageEvents.Event e = createUsageEvent(UsageEvents.Event.SHORTCUT_INVOCATION, timestamp);
296         e.mShortcutId = SHORTCUT_ID;
297         return e;
298     }
299 
createLocusIdSetEvent(long timestamp, String locusId)300     private static UsageEvents.Event createLocusIdSetEvent(long timestamp, String locusId) {
301         UsageEvents.Event e = createUsageEvent(UsageEvents.Event.LOCUS_ID_SET, timestamp);
302         e.mClass = ACTIVITY_NAME;
303         e.mLocusId = locusId;
304         return e;
305     }
306 
createActivityStoppedEvent(long timestamp)307     private static UsageEvents.Event createActivityStoppedEvent(long timestamp) {
308         UsageEvents.Event e = createUsageEvent(UsageEvents.Event.ACTIVITY_STOPPED, timestamp);
309         e.mClass = ACTIVITY_NAME;
310         return e;
311     }
312 
createActivityResumedEvent(long timestamp)313     private static UsageEvents.Event createActivityResumedEvent(long timestamp) {
314         UsageEvents.Event e = createUsageEvent(UsageEvents.Event.ACTIVITY_RESUMED, timestamp);
315         e.mClass = ACTIVITY_NAME;
316         return e;
317     }
318 
createUsageEvent(int eventType, long timestamp)319     private static UsageEvents.Event createUsageEvent(int eventType, long timestamp) {
320         UsageEvents.Event e = new UsageEvents.Event(eventType, timestamp);
321         e.mPackage = PKG_NAME_1;
322         return e;
323     }
324 
createInAppConversationEvent(long timestamp, int durationSeconds)325     private static Event createInAppConversationEvent(long timestamp, int durationSeconds) {
326         return new Event.Builder(timestamp, Event.TYPE_IN_APP_CONVERSATION)
327                 .setDurationSeconds(durationSeconds)
328                 .build();
329     }
330 
331     private static class TestConversationStore extends ConversationStore {
332 
333         private ConversationInfo mConversationInfo;
334 
TestConversationStore(File packageDir, ScheduledExecutorService scheduledExecutorService)335         TestConversationStore(File packageDir,
336                 ScheduledExecutorService scheduledExecutorService) {
337             super(packageDir, scheduledExecutorService);
338         }
339 
340         @Override
341         @Nullable
getConversation(@ullable String shortcutId)342         ConversationInfo getConversation(@Nullable String shortcutId) {
343             return mConversationInfo;
344         }
345     }
346 
347     private static class TestPackageData extends PackageData {
348 
349         private final TestConversationStore mConversationStore;
350         private final TestEventStore mEventStore;
351 
TestPackageData(@onNull String packageName, @UserIdInt int userId, @NonNull Predicate<String> isDefaultDialerPredicate, @NonNull Predicate<String> isDefaultSmsAppPredicate, @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir)352         TestPackageData(@NonNull String packageName, @UserIdInt int userId,
353                 @NonNull Predicate<String> isDefaultDialerPredicate,
354                 @NonNull Predicate<String> isDefaultSmsAppPredicate,
355                 @NonNull ScheduledExecutorService scheduledExecutorService, @NonNull File rootDir) {
356             super(packageName, userId, isDefaultDialerPredicate, isDefaultSmsAppPredicate,
357                     scheduledExecutorService, rootDir);
358             mConversationStore = new TestConversationStore(rootDir, scheduledExecutorService);
359             mEventStore = new TestEventStore(rootDir, scheduledExecutorService);
360         }
361 
362         @Override
363         @NonNull
getConversationStore()364         ConversationStore getConversationStore() {
365             return mConversationStore;
366         }
367 
368         @Override
369         @NonNull
getEventStore()370         EventStore getEventStore() {
371             return mEventStore;
372         }
373     }
374 
375     private static class TestEventStore extends EventStore {
376 
377         private static final long CURRENT_TIMESTAMP = timestamp("01-30 18:50");
378         private static final EventIndex.Injector EVENT_INDEX_INJECTOR = new EventIndex.Injector() {
379             @Override
380             long currentTimeMillis() {
381                 return CURRENT_TIMESTAMP;
382             }
383         };
384         private static final EventHistoryImpl.Injector EVENT_HISTORY_INJECTOR =
385                 new EventHistoryImpl.Injector() {
386                     @Override
387                     EventIndex createEventIndex() {
388                         return new EventIndex(EVENT_INDEX_INJECTOR);
389                     }
390                 };
391 
392         private final EventHistoryImpl mShortcutEventHistory;
393         private final EventHistoryImpl mLocusEventHistory;
394 
TestEventStore(File rootDir, ScheduledExecutorService scheduledExecutorService)395         TestEventStore(File rootDir, ScheduledExecutorService scheduledExecutorService) {
396             super(rootDir, scheduledExecutorService);
397             mShortcutEventHistory = new TestEventHistoryImpl(EVENT_HISTORY_INJECTOR, rootDir,
398                     scheduledExecutorService);
399             mLocusEventHistory = new TestEventHistoryImpl(EVENT_HISTORY_INJECTOR, rootDir,
400                     scheduledExecutorService);
401         }
402 
403         @Override
404         @NonNull
getOrCreateEventHistory(@ventCategory int category, String key)405         EventHistoryImpl getOrCreateEventHistory(@EventCategory int category, String key) {
406             if (category == EventStore.CATEGORY_SHORTCUT_BASED) {
407                 return mShortcutEventHistory;
408             } else if (category == EventStore.CATEGORY_LOCUS_ID_BASED) {
409                 return mLocusEventHistory;
410             }
411             throw new UnsupportedOperationException();
412         }
413     }
414 
415     private static class TestEventHistoryImpl extends EventHistoryImpl {
416 
417         private final List<Event> mEvents = new ArrayList<>();
418 
TestEventHistoryImpl(Injector injector, File rootDir, ScheduledExecutorService scheduledExecutorService)419         TestEventHistoryImpl(Injector injector, File rootDir,
420                 ScheduledExecutorService scheduledExecutorService) {
421             super(injector, rootDir, scheduledExecutorService);
422         }
423 
424         @Override
425         @NonNull
queryEvents(Set<Integer> eventTypes, long startTime, long endTime)426         public List<Event> queryEvents(Set<Integer> eventTypes, long startTime, long endTime) {
427             return mEvents;
428         }
429 
430         @Override
addEvent(Event event)431         void addEvent(Event event) {
432             mEvents.add(event);
433         }
434     }
435 }
436