1 /**
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy
6  * 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, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16 package com.android.server.usage;
17 
18 import static android.app.usage.UsageEvents.Event.ACTIVITY_PAUSED;
19 import static android.app.usage.UsageEvents.Event.ACTIVITY_RESUMED;
20 import static android.app.usage.UsageEvents.Event.ACTIVITY_STOPPED;
21 import static android.app.usage.UsageEvents.Event.CONFIGURATION_CHANGE;
22 import static android.app.usage.UsageEvents.Event.CONTINUE_PREVIOUS_DAY;
23 import static android.app.usage.UsageEvents.Event.CONTINUING_FOREGROUND_SERVICE;
24 import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
25 import static android.app.usage.UsageEvents.Event.END_OF_DAY;
26 import static android.app.usage.UsageEvents.Event.FLUSH_TO_DISK;
27 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_START;
28 import static android.app.usage.UsageEvents.Event.FOREGROUND_SERVICE_STOP;
29 import static android.app.usage.UsageEvents.Event.KEYGUARD_HIDDEN;
30 import static android.app.usage.UsageEvents.Event.KEYGUARD_SHOWN;
31 import static android.app.usage.UsageEvents.Event.NOTIFICATION_INTERRUPTION;
32 import static android.app.usage.UsageEvents.Event.ROLLOVER_FOREGROUND_SERVICE;
33 import static android.app.usage.UsageEvents.Event.SCREEN_INTERACTIVE;
34 import static android.app.usage.UsageEvents.Event.SCREEN_NON_INTERACTIVE;
35 import static android.app.usage.UsageEvents.Event.SHORTCUT_INVOCATION;
36 import static android.app.usage.UsageEvents.Event.STANDBY_BUCKET_CHANGED;
37 import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION;
38 
39 import android.app.usage.ConfigurationStats;
40 import android.app.usage.EventList;
41 import android.app.usage.EventStats;
42 import android.app.usage.UsageEvents.Event;
43 import android.app.usage.UsageStats;
44 import android.content.res.Configuration;
45 import android.util.ArrayMap;
46 import android.util.ArraySet;
47 import android.util.proto.ProtoInputStream;
48 
49 import com.android.internal.annotations.VisibleForTesting;
50 
51 import java.io.IOException;
52 import java.util.List;
53 
54 public class IntervalStats {
55     public static final int CURRENT_MAJOR_VERSION = 1;
56     public static final int CURRENT_MINOR_VERSION = 1;
57     public int majorVersion = CURRENT_MAJOR_VERSION;
58     public int minorVersion = CURRENT_MINOR_VERSION;
59     public long beginTime;
60     public long endTime;
61     public long lastTimeSaved;
62     public final EventTracker interactiveTracker = new EventTracker();
63     public final EventTracker nonInteractiveTracker = new EventTracker();
64     public final EventTracker keyguardShownTracker = new EventTracker();
65     public final EventTracker keyguardHiddenTracker = new EventTracker();
66     public final ArrayMap<String, UsageStats> packageStats = new ArrayMap<>();
67     public final ArrayMap<Configuration, ConfigurationStats> configurations = new ArrayMap<>();
68     public Configuration activeConfiguration;
69     public final EventList events = new EventList();
70 
71     // A string cache. This is important as when we're parsing XML files, we don't want to
72     // keep hundreds of strings that have the same contents. We will read the string
73     // and only keep it if it's not in the cache. The GC will take care of the
74     // strings that had identical copies in the cache.
75     public final ArraySet<String> mStringCache = new ArraySet<>();
76 
77     public static final class EventTracker {
78         public long curStartTime;
79         public long lastEventTime;
80         public long duration;
81         public int count;
82 
commitTime(long timeStamp)83         public void commitTime(long timeStamp) {
84             if (curStartTime != 0) {
85                 duration += timeStamp - curStartTime;
86                 curStartTime = 0;
87             }
88         }
89 
update(long timeStamp)90         public void update(long timeStamp) {
91             if (curStartTime == 0) {
92                 // If we aren't already running, time to bump the count.
93                 count++;
94             }
95             commitTime(timeStamp);
96             curStartTime = timeStamp;
97             lastEventTime = timeStamp;
98         }
99 
addToEventStats(List<EventStats> out, int event, long beginTime, long endTime)100         void addToEventStats(List<EventStats> out, int event, long beginTime, long endTime) {
101             if (count != 0 || duration != 0) {
102                 EventStats ev = new EventStats();
103                 ev.mEventType = event;
104                 ev.mCount = count;
105                 ev.mTotalTime = duration;
106                 ev.mLastEventTime = lastEventTime;
107                 ev.mBeginTimeStamp = beginTime;
108                 ev.mEndTimeStamp = endTime;
109                 out.add(ev);
110             }
111         }
112 
113     }
114 
IntervalStats()115     public IntervalStats() {
116     }
117 
118     /**
119      * Gets the UsageStats object for the given package, or creates one and adds it internally.
120      */
getOrCreateUsageStats(String packageName)121     UsageStats getOrCreateUsageStats(String packageName) {
122         UsageStats usageStats = packageStats.get(packageName);
123         if (usageStats == null) {
124             usageStats = new UsageStats();
125             usageStats.mPackageName = getCachedStringRef(packageName);
126             usageStats.mBeginTimeStamp = beginTime;
127             usageStats.mEndTimeStamp = endTime;
128             packageStats.put(usageStats.mPackageName, usageStats);
129         }
130         return usageStats;
131     }
132 
133     /**
134      * Gets the ConfigurationStats object for the given configuration, or creates one and adds it
135      * internally.
136      */
getOrCreateConfigurationStats(Configuration config)137     ConfigurationStats getOrCreateConfigurationStats(Configuration config) {
138         ConfigurationStats configStats = configurations.get(config);
139         if (configStats == null) {
140             configStats = new ConfigurationStats();
141             configStats.mBeginTimeStamp = beginTime;
142             configStats.mEndTimeStamp = endTime;
143             configStats.mConfiguration = config;
144             configurations.put(config, configStats);
145         }
146         return configStats;
147     }
148 
149     /**
150      * Builds a UsageEvents.Event, but does not add it internally.
151      */
buildEvent(String packageName, String className)152     Event buildEvent(String packageName, String className) {
153         Event event = new Event();
154         event.mPackage = getCachedStringRef(packageName);
155         if (className != null) {
156             event.mClass = getCachedStringRef(className);
157         }
158         return event;
159     }
160 
161     /**
162      * Builds a UsageEvents.Event from a proto, but does not add it internally.
163      * Built here to take advantage of the cached String Refs
164      */
buildEvent(ProtoInputStream parser, List<String> stringPool)165     Event buildEvent(ProtoInputStream parser, List<String> stringPool)
166             throws IOException {
167         final Event event = new Event();
168         while (true) {
169             switch (parser.nextField()) {
170                 case (int) IntervalStatsProto.Event.PACKAGE:
171                     event.mPackage = getCachedStringRef(
172                             parser.readString(IntervalStatsProto.Event.PACKAGE));
173                     break;
174                 case (int) IntervalStatsProto.Event.PACKAGE_INDEX:
175                     event.mPackage = getCachedStringRef(stringPool.get(
176                             parser.readInt(IntervalStatsProto.Event.PACKAGE_INDEX) - 1));
177                     break;
178                 case (int) IntervalStatsProto.Event.CLASS:
179                     event.mClass = getCachedStringRef(
180                             parser.readString(IntervalStatsProto.Event.CLASS));
181                     break;
182                 case (int) IntervalStatsProto.Event.CLASS_INDEX:
183                     event.mClass = getCachedStringRef(stringPool.get(
184                             parser.readInt(IntervalStatsProto.Event.CLASS_INDEX) - 1));
185                     break;
186                 case (int) IntervalStatsProto.Event.TIME_MS:
187                     event.mTimeStamp = beginTime + parser.readLong(
188                             IntervalStatsProto.Event.TIME_MS);
189                     break;
190                 case (int) IntervalStatsProto.Event.FLAGS:
191                     event.mFlags = parser.readInt(IntervalStatsProto.Event.FLAGS);
192                     break;
193                 case (int) IntervalStatsProto.Event.TYPE:
194                     event.mEventType = parser.readInt(IntervalStatsProto.Event.TYPE);
195                     break;
196                 case (int) IntervalStatsProto.Event.CONFIG:
197                     event.mConfiguration = new Configuration();
198                     event.mConfiguration.readFromProto(parser, IntervalStatsProto.Event.CONFIG);
199                     break;
200                 case (int) IntervalStatsProto.Event.SHORTCUT_ID:
201                     event.mShortcutId = parser.readString(
202                             IntervalStatsProto.Event.SHORTCUT_ID).intern();
203                     break;
204                 case (int) IntervalStatsProto.Event.STANDBY_BUCKET:
205                     event.mBucketAndReason = parser.readInt(
206                             IntervalStatsProto.Event.STANDBY_BUCKET);
207                     break;
208                 case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL:
209                     event.mNotificationChannelId = parser.readString(
210                             IntervalStatsProto.Event.NOTIFICATION_CHANNEL);
211                     break;
212                 case (int) IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX:
213                     event.mNotificationChannelId = getCachedStringRef(stringPool.get(
214                             parser.readInt(IntervalStatsProto.Event.NOTIFICATION_CHANNEL_INDEX)
215                                     - 1));
216                     break;
217                 case (int) IntervalStatsProto.Event.INSTANCE_ID:
218                     event.mInstanceId = parser.readInt(IntervalStatsProto.Event.INSTANCE_ID);
219                     break;
220                 case (int) IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX:
221                     event.mTaskRootPackage = getCachedStringRef(stringPool.get(
222                             parser.readInt(IntervalStatsProto.Event.TASK_ROOT_PACKAGE_INDEX) - 1));
223                     break;
224                 case (int) IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX:
225                     event.mTaskRootClass = getCachedStringRef(stringPool.get(
226                             parser.readInt(IntervalStatsProto.Event.TASK_ROOT_CLASS_INDEX) - 1));
227                     break;
228                 case ProtoInputStream.NO_MORE_FIELDS:
229                     // Handle default values for certain events types
230                     switch (event.mEventType) {
231                         case CONFIGURATION_CHANGE:
232                             if (event.mConfiguration == null) {
233                                 event.mConfiguration = new Configuration();
234                             }
235                             break;
236                         case SHORTCUT_INVOCATION:
237                             if (event.mShortcutId == null) {
238                                 event.mShortcutId = "";
239                             }
240                             break;
241                         case NOTIFICATION_INTERRUPTION:
242                             if (event.mNotificationChannelId == null) {
243                                 event.mNotificationChannelId = "";
244                             }
245                             break;
246                     }
247                     if (event.mTimeStamp == 0) {
248                         //mTimestamp not set, assume default value 0 plus beginTime
249                         event.mTimeStamp = beginTime;
250                     }
251                     return event;
252             }
253         }
254     }
255 
isStatefulEvent(int eventType)256     private boolean isStatefulEvent(int eventType) {
257         switch (eventType) {
258             case ACTIVITY_RESUMED:
259             case ACTIVITY_PAUSED:
260             case ACTIVITY_STOPPED:
261             case FOREGROUND_SERVICE_START:
262             case FOREGROUND_SERVICE_STOP:
263             case END_OF_DAY:
264             case ROLLOVER_FOREGROUND_SERVICE:
265             case CONTINUE_PREVIOUS_DAY:
266             case CONTINUING_FOREGROUND_SERVICE:
267             case DEVICE_SHUTDOWN:
268                 return true;
269         }
270         return false;
271     }
272 
273     /**
274      * Returns whether the event type is one caused by user visible
275      * interaction. Excludes those that are internally generated.
276      */
isUserVisibleEvent(int eventType)277     private boolean isUserVisibleEvent(int eventType) {
278         return eventType != SYSTEM_INTERACTION
279                 && eventType != STANDBY_BUCKET_CHANGED;
280     }
281 
282     /**
283      * Update the IntervalStats by a activity or foreground service event.
284      * @param packageName package name of this event. Is null if event targets to all packages.
285      * @param className class name of a activity or foreground service, could be null to if this
286      *                  is sent to all activities/services in this package.
287      * @param timeStamp Epoch timestamp in milliseconds.
288      * @param eventType event type as in {@link Event}
289      * @param instanceId if className is an activity, the hashCode of ActivityRecord's appToken.
290      *                 if className is not an activity, instanceId is not used.
291      * @hide
292      */
293     @VisibleForTesting
update(String packageName, String className, long timeStamp, int eventType, int instanceId)294     public void update(String packageName, String className, long timeStamp, int eventType,
295             int instanceId) {
296         if (eventType == DEVICE_SHUTDOWN
297                 || eventType == FLUSH_TO_DISK) {
298             // DEVICE_SHUTDOWN and FLUSH_TO_DISK are sent to all packages.
299             final int size = packageStats.size();
300             for (int i = 0; i < size; i++) {
301                 UsageStats usageStats = packageStats.valueAt(i);
302                 usageStats.update(null, timeStamp, eventType, instanceId);
303             }
304         } else {
305             UsageStats usageStats = getOrCreateUsageStats(packageName);
306             usageStats.update(className, timeStamp, eventType, instanceId);
307         }
308         if (timeStamp > endTime) {
309             endTime = timeStamp;
310         }
311     }
312 
313     /**
314      * @hide
315      */
316     @VisibleForTesting
addEvent(Event event)317     public void addEvent(Event event) {
318         // Cache common use strings
319         event.mPackage = getCachedStringRef(event.mPackage);
320         if (event.mClass != null) {
321             event.mClass = getCachedStringRef(event.mClass);
322         }
323         if (event.mTaskRootPackage != null) {
324             event.mTaskRootPackage = getCachedStringRef(event.mTaskRootPackage);
325         }
326         if (event.mTaskRootClass != null) {
327             event.mTaskRootClass = getCachedStringRef(event.mTaskRootClass);
328         }
329         if (event.mEventType == NOTIFICATION_INTERRUPTION) {
330             event.mNotificationChannelId = getCachedStringRef(event.mNotificationChannelId);
331         }
332         events.insert(event);
333         if (event.mTimeStamp > endTime) {
334             endTime = event.mTimeStamp;
335         }
336     }
337 
updateChooserCounts(String packageName, String category, String action)338     void updateChooserCounts(String packageName, String category, String action) {
339         UsageStats usageStats = getOrCreateUsageStats(packageName);
340         if (usageStats.mChooserCounts == null) {
341             usageStats.mChooserCounts = new ArrayMap<>();
342         }
343         ArrayMap<String, Integer> chooserCounts;
344         final int idx = usageStats.mChooserCounts.indexOfKey(action);
345         if (idx < 0) {
346             chooserCounts = new ArrayMap<>();
347             usageStats.mChooserCounts.put(action, chooserCounts);
348         } else {
349             chooserCounts = usageStats.mChooserCounts.valueAt(idx);
350         }
351         int currentCount = chooserCounts.getOrDefault(category, 0);
352         chooserCounts.put(category, currentCount + 1);
353     }
354 
updateConfigurationStats(Configuration config, long timeStamp)355     void updateConfigurationStats(Configuration config, long timeStamp) {
356         if (activeConfiguration != null) {
357             ConfigurationStats activeStats = configurations.get(activeConfiguration);
358             activeStats.mTotalTimeActive += timeStamp - activeStats.mLastTimeActive;
359             activeStats.mLastTimeActive = timeStamp - 1;
360         }
361 
362         if (config != null) {
363             ConfigurationStats configStats = getOrCreateConfigurationStats(config);
364             configStats.mLastTimeActive = timeStamp;
365             configStats.mActivationCount += 1;
366             activeConfiguration = configStats.mConfiguration;
367         }
368         if (timeStamp > endTime) {
369             endTime = timeStamp;
370         }
371     }
372 
incrementAppLaunchCount(String packageName)373     void incrementAppLaunchCount(String packageName) {
374         UsageStats usageStats = getOrCreateUsageStats(packageName);
375         usageStats.mAppLaunchCount += 1;
376     }
377 
commitTime(long timeStamp)378     void commitTime(long timeStamp) {
379         interactiveTracker.commitTime(timeStamp);
380         nonInteractiveTracker.commitTime(timeStamp);
381         keyguardShownTracker.commitTime(timeStamp);
382         keyguardHiddenTracker.commitTime(timeStamp);
383     }
384 
updateScreenInteractive(long timeStamp)385     void updateScreenInteractive(long timeStamp) {
386         interactiveTracker.update(timeStamp);
387         nonInteractiveTracker.commitTime(timeStamp);
388     }
389 
updateScreenNonInteractive(long timeStamp)390     void updateScreenNonInteractive(long timeStamp) {
391         nonInteractiveTracker.update(timeStamp);
392         interactiveTracker.commitTime(timeStamp);
393     }
394 
updateKeyguardShown(long timeStamp)395     void updateKeyguardShown(long timeStamp) {
396         keyguardShownTracker.update(timeStamp);
397         keyguardHiddenTracker.commitTime(timeStamp);
398     }
399 
updateKeyguardHidden(long timeStamp)400     void updateKeyguardHidden(long timeStamp) {
401         keyguardHiddenTracker.update(timeStamp);
402         keyguardShownTracker.commitTime(timeStamp);
403     }
404 
addEventStatsTo(List<EventStats> out)405     void addEventStatsTo(List<EventStats> out) {
406         interactiveTracker.addToEventStats(out, SCREEN_INTERACTIVE,
407                 beginTime, endTime);
408         nonInteractiveTracker.addToEventStats(out, SCREEN_NON_INTERACTIVE,
409                 beginTime, endTime);
410         keyguardShownTracker.addToEventStats(out, KEYGUARD_SHOWN,
411                 beginTime, endTime);
412         keyguardHiddenTracker.addToEventStats(out, KEYGUARD_HIDDEN,
413                 beginTime, endTime);
414     }
415 
getCachedStringRef(String str)416     private String getCachedStringRef(String str) {
417         final int index = mStringCache.indexOf(str);
418         if (index < 0) {
419             mStringCache.add(str);
420             return str;
421         }
422         return mStringCache.valueAt(index);
423     }
424 
425     /**
426      * When an IntervalStats object is deserialized, if the object's version number
427      * is lower than current version number, optionally perform a upgrade.
428      */
upgradeIfNeeded()429     void upgradeIfNeeded() {
430         // We only uprade on majorVersion change, no need to upgrade on minorVersion change.
431         if (!(majorVersion < CURRENT_MAJOR_VERSION)) {
432             return;
433         }
434         /*
435           Optional upgrade code here.
436         */
437         majorVersion = CURRENT_MAJOR_VERSION;
438     }
439 }
440