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