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 android.annotation.NonNull; 20 import android.annotation.UserIdInt; 21 import android.app.usage.UsageEvents; 22 import android.app.usage.UsageStats; 23 import android.app.usage.UsageStatsManager; 24 import android.app.usage.UsageStatsManagerInternal; 25 import android.content.ComponentName; 26 import android.content.LocusId; 27 import android.text.format.DateUtils; 28 import android.util.ArrayMap; 29 30 import com.android.server.LocalServices; 31 32 import java.util.ArrayList; 33 import java.util.List; 34 import java.util.Map; 35 import java.util.Set; 36 import java.util.function.Function; 37 38 /** A helper class that queries {@link UsageStatsManagerInternal}. */ 39 class UsageStatsQueryHelper { 40 41 private final UsageStatsManagerInternal mUsageStatsManagerInternal; 42 private final int mUserId; 43 private final Function<String, PackageData> mPackageDataGetter; 44 // Activity name -> Conversation start event (LOCUS_ID_SET) 45 private final Map<ComponentName, UsageEvents.Event> mConvoStartEvents = new ArrayMap<>(); 46 private long mLastEventTimestamp; 47 48 /** 49 * @param userId The user whose events are to be queried. 50 * @param packageDataGetter The function to get {@link PackageData} with a package name. 51 */ UsageStatsQueryHelper(@serIdInt int userId, Function<String, PackageData> packageDataGetter)52 UsageStatsQueryHelper(@UserIdInt int userId, 53 Function<String, PackageData> packageDataGetter) { 54 mUsageStatsManagerInternal = getUsageStatsManagerInternal(); 55 mUserId = userId; 56 mPackageDataGetter = packageDataGetter; 57 } 58 59 /** 60 * Queries {@link UsageStatsManagerInternal} for the recent events occurred since {@code 61 * sinceTime} and adds the derived {@link Event}s into the corresponding package's event store, 62 * 63 * @return true if the query runs successfully and at least one event is found. 64 */ querySince(long sinceTime)65 boolean querySince(long sinceTime) { 66 UsageEvents usageEvents = mUsageStatsManagerInternal.queryEventsForUser( 67 mUserId, sinceTime, System.currentTimeMillis(), UsageEvents.SHOW_ALL_EVENT_DATA); 68 if (usageEvents == null) { 69 return false; 70 } 71 boolean hasEvents = false; 72 while (usageEvents.hasNextEvent()) { 73 UsageEvents.Event e = new UsageEvents.Event(); 74 usageEvents.getNextEvent(e); 75 76 hasEvents = true; 77 mLastEventTimestamp = Math.max(mLastEventTimestamp, e.getTimeStamp()); 78 String packageName = e.getPackageName(); 79 PackageData packageData = mPackageDataGetter.apply(packageName); 80 if (packageData == null) { 81 continue; 82 } 83 switch (e.getEventType()) { 84 case UsageEvents.Event.SHORTCUT_INVOCATION: 85 addEventByShortcutId(packageData, e.getShortcutId(), 86 new Event(e.getTimeStamp(), Event.TYPE_SHORTCUT_INVOCATION)); 87 break; 88 case UsageEvents.Event.LOCUS_ID_SET: 89 onInAppConversationEnded(packageData, e); 90 LocusId locusId = e.getLocusId() != null ? new LocusId(e.getLocusId()) : null; 91 if (locusId != null) { 92 if (packageData.getConversationStore().getConversationByLocusId(locusId) 93 != null) { 94 ComponentName activityName = 95 new ComponentName(packageName, e.getClassName()); 96 mConvoStartEvents.put(activityName, e); 97 } 98 } 99 break; 100 case UsageEvents.Event.ACTIVITY_PAUSED: 101 case UsageEvents.Event.ACTIVITY_STOPPED: 102 case UsageEvents.Event.ACTIVITY_DESTROYED: 103 onInAppConversationEnded(packageData, e); 104 break; 105 } 106 } 107 return hasEvents; 108 } 109 getLastEventTimestamp()110 long getLastEventTimestamp() { 111 return mLastEventTimestamp; 112 } 113 114 /** 115 * Queries {@link UsageStatsManagerInternal} events for moving app to foreground between 116 * {@code startTime} and {@code endTime}. 117 * 118 * @return a list containing events moving app to foreground. 119 */ queryAppMovingToForegroundEvents(@serIdInt int userId, long startTime, long endTime)120 static List<UsageEvents.Event> queryAppMovingToForegroundEvents(@UserIdInt int userId, 121 long startTime, long endTime) { 122 List<UsageEvents.Event> res = new ArrayList<>(); 123 UsageEvents usageEvents = getUsageStatsManagerInternal().queryEventsForUser(userId, 124 startTime, endTime, 125 UsageEvents.HIDE_SHORTCUT_EVENTS | UsageEvents.HIDE_LOCUS_EVENTS); 126 if (usageEvents == null) { 127 return res; 128 } 129 while (usageEvents.hasNextEvent()) { 130 UsageEvents.Event e = new UsageEvents.Event(); 131 usageEvents.getNextEvent(e); 132 if (e.getEventType() == UsageEvents.Event.ACTIVITY_RESUMED) { 133 res.add(e); 134 } 135 } 136 return res; 137 } 138 139 /** 140 * Queries {@link UsageStatsManagerInternal} for usage stats of apps within {@code 141 * packageNameFilter} between {@code startTime} and {@code endTime}. 142 * 143 * @return a map which keys are package names and values are {@link AppUsageStatsData}. 144 */ queryAppUsageStats(@serIdInt int userId, long startTime, long endTime, Set<String> packageNameFilter)145 static Map<String, AppUsageStatsData> queryAppUsageStats(@UserIdInt int userId, long startTime, 146 long endTime, Set<String> packageNameFilter) { 147 List<UsageStats> stats = getUsageStatsManagerInternal().queryUsageStatsForUser(userId, 148 UsageStatsManager.INTERVAL_BEST, startTime, endTime, 149 /* obfuscateInstantApps= */ false); 150 Map<String, AppUsageStatsData> aggregatedStats = new ArrayMap<>(); 151 if (stats == null) { 152 return aggregatedStats; 153 } 154 for (UsageStats stat : stats) { 155 String packageName = stat.getPackageName(); 156 if (packageNameFilter.contains(packageName)) { 157 AppUsageStatsData packageStats = aggregatedStats.computeIfAbsent(packageName, 158 (key) -> new AppUsageStatsData()); 159 packageStats.incrementChosenCountBy(sumChooserCounts(stat.mChooserCounts)); 160 packageStats.incrementLaunchCountBy(stat.getAppLaunchCount()); 161 } 162 } 163 return aggregatedStats; 164 } 165 sumChooserCounts(ArrayMap<String, ArrayMap<String, Integer>> chooserCounts)166 private static int sumChooserCounts(ArrayMap<String, ArrayMap<String, Integer>> chooserCounts) { 167 int sum = 0; 168 if (chooserCounts == null) { 169 return sum; 170 } 171 int chooserCountsSize = chooserCounts.size(); 172 for (int i = 0; i < chooserCountsSize; i++) { 173 ArrayMap<String, Integer> counts = chooserCounts.valueAt(i); 174 if (counts == null) { 175 continue; 176 } 177 final int annotationSize = counts.size(); 178 for (int j = 0; j < annotationSize; j++) { 179 sum += counts.valueAt(j); 180 } 181 } 182 return sum; 183 } 184 onInAppConversationEnded(@onNull PackageData packageData, @NonNull UsageEvents.Event endEvent)185 private void onInAppConversationEnded(@NonNull PackageData packageData, 186 @NonNull UsageEvents.Event endEvent) { 187 ComponentName activityName = 188 new ComponentName(endEvent.getPackageName(), endEvent.getClassName()); 189 UsageEvents.Event startEvent = mConvoStartEvents.remove(activityName); 190 if (startEvent == null || startEvent.getTimeStamp() >= endEvent.getTimeStamp()) { 191 return; 192 } 193 long durationMillis = endEvent.getTimeStamp() - startEvent.getTimeStamp(); 194 Event event = new Event.Builder(startEvent.getTimeStamp(), Event.TYPE_IN_APP_CONVERSATION) 195 .setDurationSeconds((int) (durationMillis / DateUtils.SECOND_IN_MILLIS)) 196 .build(); 197 addEventByLocusId(packageData, new LocusId(startEvent.getLocusId()), event); 198 } 199 addEventByShortcutId(PackageData packageData, String shortcutId, Event event)200 private void addEventByShortcutId(PackageData packageData, String shortcutId, Event event) { 201 if (packageData.getConversationStore().getConversation(shortcutId) == null) { 202 return; 203 } 204 EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( 205 EventStore.CATEGORY_SHORTCUT_BASED, shortcutId); 206 eventHistory.addEvent(event); 207 } 208 addEventByLocusId(PackageData packageData, LocusId locusId, Event event)209 private void addEventByLocusId(PackageData packageData, LocusId locusId, Event event) { 210 if (packageData.getConversationStore().getConversationByLocusId(locusId) == null) { 211 return; 212 } 213 EventHistoryImpl eventHistory = packageData.getEventStore().getOrCreateEventHistory( 214 EventStore.CATEGORY_LOCUS_ID_BASED, locusId.getId()); 215 eventHistory.addEvent(event); 216 } 217 getUsageStatsManagerInternal()218 private static UsageStatsManagerInternal getUsageStatsManagerInternal() { 219 return LocalServices.getService(UsageStatsManagerInternal.class); 220 } 221 } 222