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