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