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