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 
17 package com.android.server.usage;
18 
19 import static android.app.usage.UsageEvents.Event.DEVICE_SHUTDOWN;
20 import static android.app.usage.UsageEvents.Event.DEVICE_STARTUP;
21 import static android.app.usage.UsageEvents.HIDE_LOCUS_EVENTS;
22 import static android.app.usage.UsageEvents.HIDE_SHORTCUT_EVENTS;
23 import static android.app.usage.UsageEvents.OBFUSCATE_INSTANT_APPS;
24 import static android.app.usage.UsageEvents.OBFUSCATE_NOTIFICATION_EVENTS;
25 import static android.app.usage.UsageStatsManager.INTERVAL_BEST;
26 import static android.app.usage.UsageStatsManager.INTERVAL_COUNT;
27 import static android.app.usage.UsageStatsManager.INTERVAL_DAILY;
28 import static android.app.usage.UsageStatsManager.INTERVAL_MONTHLY;
29 import static android.app.usage.UsageStatsManager.INTERVAL_WEEKLY;
30 import static android.app.usage.UsageStatsManager.INTERVAL_YEARLY;
31 
32 import android.annotation.NonNull;
33 import android.annotation.Nullable;
34 import android.app.usage.ConfigurationStats;
35 import android.app.usage.EventList;
36 import android.app.usage.EventStats;
37 import android.app.usage.UsageEvents;
38 import android.app.usage.UsageEvents.Event;
39 import android.app.usage.UsageStats;
40 import android.app.usage.UsageStatsManager;
41 import android.content.Context;
42 import android.content.res.Configuration;
43 import android.os.SystemClock;
44 import android.os.UserHandle;
45 import android.text.format.DateUtils;
46 import android.util.ArrayMap;
47 import android.util.ArraySet;
48 import android.util.AtomicFile;
49 import android.util.LongSparseArray;
50 import android.util.Slog;
51 import android.util.SparseArrayMap;
52 import android.util.SparseIntArray;
53 
54 import com.android.internal.util.ArrayUtils;
55 import com.android.internal.util.CollectionUtils;
56 import com.android.internal.util.IndentingPrintWriter;
57 import com.android.server.usage.UsageStatsDatabase.StatCombiner;
58 
59 import java.io.File;
60 import java.io.IOException;
61 import java.text.SimpleDateFormat;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.HashMap;
65 import java.util.List;
66 import java.util.Set;
67 
68 /**
69  * A per-user UsageStatsService. All methods are meant to be called with the main lock held
70  * in UsageStatsService.
71  */
72 class UserUsageStatsService {
73     private static final String TAG = UsageStatsService.TAG;
74     private static final boolean DEBUG = UsageStatsService.DEBUG;
75     private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
76     private static final int sDateFormatFlags =
77             DateUtils.FORMAT_SHOW_DATE
78             | DateUtils.FORMAT_SHOW_TIME
79             | DateUtils.FORMAT_SHOW_YEAR
80             | DateUtils.FORMAT_NUMERIC_DATE;
81 
82     private final Context mContext;
83     private final UsageStatsDatabase mDatabase;
84     private final IntervalStats[] mCurrentStats;
85     private boolean mStatsChanged = false;
86     private final UnixCalendar mDailyExpiryDate;
87     private final StatsUpdatedListener mListener;
88     private final String mLogPrefix;
89     private String mLastBackgroundedPackage;
90     private final int mUserId;
91     private long mRealTimeSnapshot;
92     private long mSystemTimeSnapshot;
93 
94     private static final long[] INTERVAL_LENGTH = new long[] {
95             UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
96             UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS
97     };
98 
99     interface StatsUpdatedListener {
onStatsUpdated()100         void onStatsUpdated();
onStatsReloaded()101         void onStatsReloaded();
102         /**
103          * Callback that a system update was detected
104          * @param mUserId user that needs to be initialized
105          */
onNewUpdate(int mUserId)106         void onNewUpdate(int mUserId);
107     }
108 
109     private static final class CachedEarlyEvents {
110         public long searchBeginTime;
111 
112         public long eventTime;
113 
114         @Nullable
115         public List<UsageEvents.Event> events;
116     }
117 
118     /**
119      * Mapping of {@link UsageEvents.Event} event value to packageName-cached early usage event.
120      * This is used to reduce how much we need to interact with the underlying database to get the
121      * earliest event for a specific package.
122      */
123     private final SparseArrayMap<String, CachedEarlyEvents> mCachedEarlyEvents =
124             new SparseArrayMap<>();
125 
UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener)126     UserUsageStatsService(Context context, int userId, File usageStatsDir,
127             StatsUpdatedListener listener) {
128         mContext = context;
129         mDailyExpiryDate = new UnixCalendar(0);
130         mDatabase = new UsageStatsDatabase(usageStatsDir);
131         mCurrentStats = new IntervalStats[INTERVAL_COUNT];
132         mListener = listener;
133         mLogPrefix = "User[" + Integer.toString(userId) + "] ";
134         mUserId = userId;
135         mRealTimeSnapshot = SystemClock.elapsedRealtime();
136         mSystemTimeSnapshot = System.currentTimeMillis();
137     }
138 
init(final long currentTimeMillis, HashMap<String, Long> installedPackages, boolean deleteObsoleteData)139     void init(final long currentTimeMillis, HashMap<String, Long> installedPackages,
140             boolean deleteObsoleteData) {
141         readPackageMappingsLocked(installedPackages, deleteObsoleteData);
142         mDatabase.init(currentTimeMillis);
143         if (mDatabase.wasUpgradePerformed()) {
144             mDatabase.prunePackagesDataOnUpgrade(installedPackages);
145         }
146 
147         int nullCount = 0;
148         for (int i = 0; i < mCurrentStats.length; i++) {
149             mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
150             if (mCurrentStats[i] == null) {
151                 // Find out how many intervals we don't have data for.
152                 // Ideally it should be all or none.
153                 nullCount++;
154             }
155         }
156 
157         if (nullCount > 0) {
158             if (nullCount != mCurrentStats.length) {
159                 // This is weird, but we shouldn't fail if something like this
160                 // happens.
161                 Slog.w(TAG, mLogPrefix + "Some stats have no latest available");
162             } else {
163                 // This must be first boot.
164             }
165 
166             // By calling loadActiveStats, we will
167             // generate new stats for each bucket.
168             loadActiveStats(currentTimeMillis);
169         } else {
170             // Set up the expiry date to be one day from the latest daily stat.
171             // This may actually be today and we will rollover on the first event
172             // that is reported.
173             updateRolloverDeadline();
174         }
175 
176         // During system reboot, add a DEVICE_SHUTDOWN event to the end of event list, the timestamp
177         // is last time UsageStatsDatabase is persisted to disk or the last event's time whichever
178         // is higher (because the file system timestamp is round down to integral seconds).
179         // Also add a DEVICE_STARTUP event with current system timestamp.
180         final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
181         if (currentDailyStats != null) {
182             final Event shutdownEvent = new Event(DEVICE_SHUTDOWN,
183                     Math.max(currentDailyStats.lastTimeSaved, currentDailyStats.endTime));
184             shutdownEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
185             currentDailyStats.addEvent(shutdownEvent);
186             final Event startupEvent = new Event(DEVICE_STARTUP, System.currentTimeMillis());
187             startupEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
188             currentDailyStats.addEvent(startupEvent);
189         }
190 
191         if (mDatabase.isNewUpdate()) {
192             notifyNewUpdate();
193         }
194     }
195 
userStopped()196     void userStopped() {
197         // Flush events to disk immediately to guarantee persistence.
198         persistActiveStats();
199         mCachedEarlyEvents.clear();
200     }
201 
onPackageRemoved(String packageName, long timeRemoved)202     int onPackageRemoved(String packageName, long timeRemoved) {
203         for (int i = mCachedEarlyEvents.numMaps() - 1; i >= 0; --i) {
204             final int eventType = mCachedEarlyEvents.keyAt(i);
205             mCachedEarlyEvents.delete(eventType, packageName);
206         }
207         return mDatabase.onPackageRemoved(packageName, timeRemoved);
208     }
209 
readPackageMappingsLocked(HashMap<String, Long> installedPackages, boolean deleteObsoleteData)210     private void readPackageMappingsLocked(HashMap<String, Long> installedPackages,
211             boolean deleteObsoleteData) {
212         mDatabase.readMappingsLocked();
213         // Package mappings for the system user are updated after 24 hours via a job scheduled by
214         // UsageStatsIdleService to ensure restored data is not lost on first boot. Additionally,
215         // this makes user service initialization a little quicker on subsequent boots.
216         if (mUserId != UserHandle.USER_SYSTEM && deleteObsoleteData) {
217             updatePackageMappingsLocked(installedPackages);
218         }
219     }
220 
221     /**
222      * Compares the package mappings on disk with the ones currently installed and removes the
223      * mappings for those packages that have been uninstalled.
224      * This will only happen once per device boot, when the user is unlocked for the first time.
225      * If the user is the system user (user 0), this is delayed to ensure data for packages
226      * that were restored isn't removed before the restore is complete.
227      *
228      * @param installedPackages map of installed packages (package_name:package_install_time)
229      * @return {@code true} on a successful mappings update, {@code false} otherwise.
230      */
updatePackageMappingsLocked(HashMap<String, Long> installedPackages)231     boolean updatePackageMappingsLocked(HashMap<String, Long> installedPackages) {
232         if (ArrayUtils.isEmpty(installedPackages)) {
233             return true;
234         }
235 
236         final long timeNow = System.currentTimeMillis();
237         final ArrayList<String> removedPackages = new ArrayList<>();
238         // populate list of packages that are found in the mappings but not in the installed list
239         for (int i = mDatabase.mPackagesTokenData.packagesToTokensMap.size() - 1; i >= 0; i--) {
240             final String packageName = mDatabase.mPackagesTokenData.packagesToTokensMap.keyAt(i);
241             if (!installedPackages.containsKey(packageName)) {
242                 removedPackages.add(packageName);
243             }
244         }
245         if (removedPackages.isEmpty()) {
246             return true;
247         }
248 
249         // remove packages in the mappings that are no longer installed and persist to disk
250         for (int i = removedPackages.size() - 1; i >= 0; i--) {
251             mDatabase.mPackagesTokenData.removePackage(removedPackages.get(i), timeNow);
252         }
253         try {
254             mDatabase.writeMappingsLocked();
255         } catch (Exception e) {
256             Slog.w(TAG, "Unable to write updated package mappings file on service initialization.");
257             return false;
258         }
259         return true;
260     }
261 
pruneUninstalledPackagesData()262     boolean pruneUninstalledPackagesData() {
263         return mDatabase.pruneUninstalledPackagesData();
264     }
265 
onTimeChanged(long oldTime, long newTime)266     private void onTimeChanged(long oldTime, long newTime) {
267         mCachedEarlyEvents.clear();
268         persistActiveStats();
269         mDatabase.onTimeChanged(newTime - oldTime);
270         loadActiveStats(newTime);
271     }
272 
273     /**
274      * This should be the only way to get the time from the system.
275      */
checkAndGetTimeLocked()276     private long checkAndGetTimeLocked() {
277         final long actualSystemTime = System.currentTimeMillis();
278         if (!UsageStatsService.ENABLE_TIME_CHANGE_CORRECTION) {
279             return actualSystemTime;
280         }
281         final long actualRealtime = SystemClock.elapsedRealtime();
282         final long expectedSystemTime = (actualRealtime - mRealTimeSnapshot) + mSystemTimeSnapshot;
283         final long diffSystemTime = actualSystemTime - expectedSystemTime;
284         if (Math.abs(diffSystemTime) > UsageStatsService.TIME_CHANGE_THRESHOLD_MILLIS) {
285             // The time has changed.
286             Slog.i(TAG, mLogPrefix + "Time changed in by " + (diffSystemTime / 1000) + " seconds");
287             onTimeChanged(expectedSystemTime, actualSystemTime);
288             mRealTimeSnapshot = actualRealtime;
289             mSystemTimeSnapshot = actualSystemTime;
290         }
291         return actualSystemTime;
292     }
293 
294     /**
295      * Assuming the event's timestamp is measured in milliseconds since boot,
296      * convert it to a system wall time.
297      */
convertToSystemTimeLocked(Event event)298     private void convertToSystemTimeLocked(Event event) {
299         event.mTimeStamp = Math.max(0, event.mTimeStamp - mRealTimeSnapshot) + mSystemTimeSnapshot;
300     }
301 
reportEvent(Event event)302     void reportEvent(Event event) {
303         if (DEBUG) {
304             Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
305                     + "[" + event.mTimeStamp + "]: "
306                     + eventToString(event.mEventType));
307         }
308 
309         if (event.mEventType != Event.USER_INTERACTION
310                 && event.mEventType != Event.APP_COMPONENT_USED) {
311             checkAndGetTimeLocked();
312             convertToSystemTimeLocked(event);
313         }
314 
315         if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
316             // Need to rollover
317             rolloverStats(event.mTimeStamp);
318         }
319 
320         final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
321 
322         final Configuration newFullConfig = event.mConfiguration;
323         if (event.mEventType == Event.CONFIGURATION_CHANGE
324                 && currentDailyStats.activeConfiguration != null) {
325             // Make the event configuration a delta.
326             event.mConfiguration = Configuration.generateDelta(
327                     currentDailyStats.activeConfiguration, newFullConfig);
328         }
329 
330         if (event.mEventType != Event.SYSTEM_INTERACTION
331                 // ACTIVITY_DESTROYED is a private event. If there is preceding ACTIVITY_STOPPED
332                 // ACTIVITY_DESTROYED will be dropped. Otherwise it will be converted to
333                 // ACTIVITY_STOPPED.
334                 && event.mEventType != Event.ACTIVITY_DESTROYED
335                 // FLUSH_TO_DISK is a private event.
336                 && event.mEventType != Event.FLUSH_TO_DISK
337                 // DEVICE_SHUTDOWN is added to event list after reboot.
338                 && event.mEventType != Event.DEVICE_SHUTDOWN
339                 // We aren't interested in every instance of the APP_COMPONENT_USED event.
340                 && event.mEventType != Event.APP_COMPONENT_USED) {
341             currentDailyStats.addEvent(event);
342         }
343 
344         boolean incrementAppLaunch = false;
345         if (event.mEventType == Event.ACTIVITY_RESUMED) {
346             if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) {
347                 incrementAppLaunch = true;
348             }
349         } else if (event.mEventType == Event.ACTIVITY_PAUSED) {
350             if (event.mPackage != null) {
351                 mLastBackgroundedPackage = event.mPackage;
352             }
353         }
354 
355         for (IntervalStats stats : mCurrentStats) {
356             switch (event.mEventType) {
357                 case Event.CONFIGURATION_CHANGE: {
358                     stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
359                 } break;
360                 case Event.CHOOSER_ACTION: {
361                     stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction);
362                     String[] annotations = event.mContentAnnotations;
363                     if (annotations != null) {
364                         for (String annotation : annotations) {
365                             stats.updateChooserCounts(event.mPackage, annotation, event.mAction);
366                         }
367                     }
368                 } break;
369                 case Event.SCREEN_INTERACTIVE: {
370                     stats.updateScreenInteractive(event.mTimeStamp);
371                 } break;
372                 case Event.SCREEN_NON_INTERACTIVE: {
373                     stats.updateScreenNonInteractive(event.mTimeStamp);
374                 } break;
375                 case Event.KEYGUARD_SHOWN: {
376                     stats.updateKeyguardShown(event.mTimeStamp);
377                 } break;
378                 case Event.KEYGUARD_HIDDEN: {
379                     stats.updateKeyguardHidden(event.mTimeStamp);
380                 } break;
381                 default: {
382                     stats.update(event.mPackage, event.getClassName(),
383                             event.mTimeStamp, event.mEventType, event.mInstanceId);
384                     if (incrementAppLaunch) {
385                         stats.incrementAppLaunchCount(event.mPackage);
386                     }
387                 } break;
388             }
389         }
390 
391         notifyStatsChanged();
392     }
393 
394     private static final StatCombiner<UsageStats> sUsageStatsCombiner =
395             new StatCombiner<UsageStats>() {
396                 @Override
397                 public boolean combine(IntervalStats stats, boolean mutable,
398                                     List<UsageStats> accResult) {
399                     if (!mutable) {
400                         accResult.addAll(stats.packageStats.values());
401                         return true;
402                     }
403 
404                     final int statCount = stats.packageStats.size();
405                     for (int i = 0; i < statCount; i++) {
406                         accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
407                     }
408                     return true;
409                 }
410             };
411 
412     private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
413             new StatCombiner<ConfigurationStats>() {
414                 @Override
415                 public boolean combine(IntervalStats stats, boolean mutable,
416                                     List<ConfigurationStats> accResult) {
417                     if (!mutable) {
418                         accResult.addAll(stats.configurations.values());
419                         return true;
420                     }
421 
422                     final int configCount = stats.configurations.size();
423                     for (int i = 0; i < configCount; i++) {
424                         accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
425                     }
426                     return true;
427                 }
428             };
429 
430     private static final StatCombiner<EventStats> sEventStatsCombiner =
431             new StatCombiner<EventStats>() {
432                 @Override
433                 public boolean combine(IntervalStats stats, boolean mutable,
434                         List<EventStats> accResult) {
435                     stats.addEventStatsTo(accResult);
436                     return true;
437                 }
438             };
439 
validRange(long currentTime, long beginTime, long endTime)440     private static boolean validRange(long currentTime, long beginTime, long endTime) {
441         return beginTime <= currentTime && beginTime < endTime;
442     }
443 
444     /**
445      * Generic query method that selects the appropriate IntervalStats for the specified time range
446      * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
447      * provided to select the stats to use from the IntervalStats object.
448      */
449     @Nullable
queryStats(int intervalType, final long beginTime, final long endTime, StatCombiner<T> combiner, boolean skipEvents)450     private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
451             StatCombiner<T> combiner, boolean skipEvents) {
452         if (intervalType == INTERVAL_BEST) {
453             intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
454             if (intervalType < 0) {
455                 // Nothing saved to disk yet, so every stat is just as equal (no rollover has
456                 // occurred.
457                 intervalType = INTERVAL_DAILY;
458             }
459         }
460 
461         if (intervalType < 0 || intervalType >= mCurrentStats.length) {
462             if (DEBUG) {
463                 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
464             }
465             return null;
466         }
467 
468         final IntervalStats currentStats = mCurrentStats[intervalType];
469 
470         if (DEBUG) {
471             Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
472                     + beginTime + " AND endTime < " + endTime);
473         }
474 
475         if (beginTime >= currentStats.endTime) {
476             if (DEBUG) {
477                 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
478                         + currentStats.endTime);
479             }
480             // Nothing newer available.
481             return null;
482         }
483 
484         // Truncate the endTime to just before the in-memory stats. Then, we'll append the
485         // in-memory stats to the results (if necessary) so as to avoid writing to disk too
486         // often.
487         final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
488 
489         // Get the stats from disk.
490         List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
491                 truncatedEndTime, combiner, skipEvents);
492         if (DEBUG) {
493             Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
494             Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
495                     " endTime=" + currentStats.endTime);
496         }
497 
498         // Now check if the in-memory stats match the range and add them if they do.
499         if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
500             if (DEBUG) {
501                 Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
502             }
503 
504             if (results == null) {
505                 results = new ArrayList<>();
506             }
507             mDatabase.filterStats(currentStats);
508             combiner.combine(currentStats, true, results);
509         }
510 
511         if (DEBUG) {
512             Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
513         }
514         return results;
515     }
516 
queryUsageStats(int bucketType, long beginTime, long endTime)517     List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
518         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
519             return null;
520         }
521         return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner, true);
522     }
523 
queryConfigurationStats(int bucketType, long beginTime, long endTime)524     List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
525         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
526             return null;
527         }
528         return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner, true);
529     }
530 
queryEventStats(int bucketType, long beginTime, long endTime)531     List<EventStats> queryEventStats(int bucketType, long beginTime, long endTime) {
532         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
533             return null;
534         }
535         return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner, true);
536     }
537 
queryEvents(final long beginTime, final long endTime, int flags, int[] eventTypeFilter, ArraySet<String> pkgNameFilter)538     UsageEvents queryEvents(final long beginTime, final long endTime, int flags,
539             int[] eventTypeFilter, ArraySet<String> pkgNameFilter) {
540         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
541             return null;
542         }
543 
544         // Ensure valid event type filter.
545         final boolean isQueryForAllEvents = ArrayUtils.isEmpty(eventTypeFilter);
546         final boolean isQueryForAllPackages = pkgNameFilter == null || pkgNameFilter.isEmpty();
547         final boolean[] queryEventFilter = new boolean[Event.MAX_EVENT_TYPE + 1];
548         if (!isQueryForAllEvents) {
549             for (int eventType : eventTypeFilter) {
550                 if (eventType < Event.NONE || eventType > Event.MAX_EVENT_TYPE) {
551                     throw new IllegalArgumentException("invalid event type: " + eventType);
552                 }
553                 queryEventFilter[eventType] = true;
554             }
555         }
556         final ArraySet<String> names = new ArraySet<>();
557         List<Event> results = queryStats(INTERVAL_DAILY,
558                 beginTime, endTime, new StatCombiner<Event>() {
559                     @Override
560                     public boolean combine(IntervalStats stats, boolean mutable,
561                             List<Event> accumulatedResult) {
562                         final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
563                         final int size = stats.events.size();
564 
565                         for (int i = startIndex; i < size; i++) {
566                             Event event = stats.events.get(i);
567                             if (event.mTimeStamp >= endTime) {
568                                 return false;
569                             }
570 
571                             final int eventType = event.mEventType;
572                             if (!isQueryForAllEvents && !queryEventFilter[eventType]) {
573                                 continue;
574                             }
575 
576                             if (eventType == Event.SHORTCUT_INVOCATION
577                                     && (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) {
578                                 continue;
579                             }
580                             if (eventType == Event.LOCUS_ID_SET
581                                     && (flags & HIDE_LOCUS_EVENTS) == HIDE_LOCUS_EVENTS) {
582                                 continue;
583                             }
584                             if ((eventType == Event.NOTIFICATION_SEEN
585                                     || eventType == Event.NOTIFICATION_INTERRUPTION)
586                                     && (flags & OBFUSCATE_NOTIFICATION_EVENTS)
587                                     == OBFUSCATE_NOTIFICATION_EVENTS) {
588                                 event = event.getObfuscatedNotificationEvent();
589                             }
590                             if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) {
591                                 event = event.getObfuscatedIfInstantApp();
592                             }
593 
594                             if (!isQueryForAllPackages && !pkgNameFilter.contains(event.mPackage)) {
595                                 continue;
596                             }
597 
598                             if (event.mPackage != null) {
599                                 names.add(event.mPackage);
600                             }
601                             if (event.mClass != null) {
602                                 names.add(event.mClass);
603                             }
604                             if (event.mTaskRootPackage != null) {
605                                 names.add(event.mTaskRootPackage);
606                             }
607                             if (event.mTaskRootClass != null) {
608                                 names.add(event.mTaskRootClass);
609                             }
610                             accumulatedResult.add(event);
611                         }
612                         return true;
613                     }
614                 }, false);
615 
616         if (results == null || results.isEmpty()) {
617             return null;
618         }
619 
620         String[] table = names.toArray(new String[names.size()]);
621         Arrays.sort(table);
622         return new UsageEvents(results, table, true);
623     }
624 
625     /**
626      * Returns a {@link UsageEvents} object whose events list contains only the earliest event seen
627      * for each app as well as the earliest event of {@code eventType} seen for each app.
628      */
629     @Nullable
queryEarliestAppEvents(final long beginTime, final long endTime, final int eventType)630     UsageEvents queryEarliestAppEvents(final long beginTime, final long endTime,
631             final int eventType) {
632         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
633             return null;
634         }
635         final ArraySet<String> names = new ArraySet<>();
636         final ArraySet<String> eventSuccess = new ArraySet<>();
637         final List<Event> results = queryStats(INTERVAL_DAILY,
638                 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
639                     final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
640                     final int size = stats.events.size();
641                     for (int i = startIndex; i < size; i++) {
642                         final Event event = stats.events.get(i);
643                         if (event.getTimeStamp() >= endTime) {
644                             return false;
645                         }
646                         if (event.getPackageName() == null) {
647                             continue;
648                         }
649                         if (eventSuccess.contains(event.getPackageName())) {
650                             continue;
651                         }
652 
653                         final boolean firstEvent = names.add(event.getPackageName());
654 
655                         if (event.getEventType() == eventType) {
656                             accumulatedResult.add(event);
657                             eventSuccess.add(event.getPackageName());
658                         } else if (firstEvent) {
659                             // Save the earliest found event for the app, even if it doesn't match.
660                             accumulatedResult.add(event);
661                         }
662                     }
663                     return true;
664                 }, false);
665 
666         if (results == null || results.isEmpty()) {
667             return null;
668         }
669         if (DEBUG) {
670             Slog.d(TAG, "Found " + results.size() + " early events for " + names.size() + " apps");
671         }
672 
673         String[] table = names.toArray(new String[names.size()]);
674         Arrays.sort(table);
675         return new UsageEvents(results, table, false);
676     }
677 
678     @Nullable
queryEventsForPackage(final long beginTime, final long endTime, final String packageName, boolean includeTaskRoot)679     UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
680             final String packageName, boolean includeTaskRoot) {
681         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
682             return null;
683         }
684         final ArraySet<String> names = new ArraySet<>();
685         names.add(packageName);
686         final List<Event> results = queryStats(INTERVAL_DAILY,
687                 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
688                     final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
689                     final int size = stats.events.size();
690                     for (int i = startIndex; i < size; i++) {
691                         final Event event = stats.events.get(i);
692                         if (event.mTimeStamp >= endTime) {
693                             return false;
694                         }
695 
696                         if (!packageName.equals(event.mPackage)) {
697                             continue;
698                         }
699                         if (event.mClass != null) {
700                             names.add(event.mClass);
701                         }
702                         if (includeTaskRoot && event.mTaskRootPackage != null) {
703                             names.add(event.mTaskRootPackage);
704                         }
705                         if (includeTaskRoot && event.mTaskRootClass != null) {
706                             names.add(event.mTaskRootClass);
707                         }
708                         accumulatedResult.add(event);
709                     }
710                     return true;
711                 }, false);
712 
713         if (results == null || results.isEmpty()) {
714             return null;
715         }
716 
717         final String[] table = names.toArray(new String[names.size()]);
718         Arrays.sort(table);
719         return new UsageEvents(results, table, includeTaskRoot);
720     }
721 
722     /**
723      * Returns a {@link UsageEvents} object whose events list contains only the earliest event seen
724      * for the package as well as the earliest event of {@code eventType} seen for the package.
725      */
726     @Nullable
queryEarliestEventsForPackage(long beginTime, final long endTime, @NonNull final String packageName, final int eventType)727     UsageEvents queryEarliestEventsForPackage(long beginTime, final long endTime,
728             @NonNull final String packageName, final int eventType) {
729         final long currentTime = checkAndGetTimeLocked();
730         if (!validRange(currentTime, beginTime, endTime)) {
731             return null;
732         }
733 
734         CachedEarlyEvents cachedEvents = mCachedEarlyEvents.get(eventType, packageName);
735         if (cachedEvents != null) {
736             // We can use this cached event if the previous search time was the exact same
737             // or earlier AND the event we previously found was at this current time or
738             // afterwards. Since no new events will be added before the cached event,
739             // redoing the search will yield the same event.
740             if (cachedEvents.searchBeginTime <= beginTime && beginTime <= cachedEvents.eventTime) {
741                 final int numEvents = cachedEvents.events == null ? 0 : cachedEvents.events.size();
742                 if ((numEvents == 0
743                         || cachedEvents.events.get(numEvents - 1).getEventType() != eventType)
744                         && cachedEvents.eventTime < endTime) {
745                     // We didn't find a match in the earlier range but this new request is allowing
746                     // us to look at events after the previous request's end time, so we may find
747                     // something new.
748                     beginTime = Math.min(currentTime, cachedEvents.eventTime);
749                     // Leave the cachedEvents's searchBeginTime as the earlier begin time to
750                     // cache/show that we searched the entire range (the union of the two queries):
751                     // [previous query's begin time, current query's end time].
752                 } else if (cachedEvents.eventTime <= endTime) {
753                     if (cachedEvents.events == null) {
754                         return null;
755                     }
756                     return new UsageEvents(cachedEvents.events, new String[]{packageName}, false);
757                 } else {
758                     // Any event we previously found is after the end of this query's range, but
759                     // this query starts at the same time (or after) the previous query's begin time
760                     // so there is no event to return.
761                     return null;
762                 }
763             } else {
764                 // The previous query isn't helpful in any way for this query. Reset the event data.
765                 cachedEvents.searchBeginTime = beginTime;
766             }
767         } else {
768             cachedEvents = new CachedEarlyEvents();
769             cachedEvents.searchBeginTime = beginTime;
770             mCachedEarlyEvents.add(eventType, packageName, cachedEvents);
771         }
772 
773         final long finalBeginTime = beginTime;
774         final List<Event> results = queryStats(INTERVAL_DAILY,
775                 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
776                     final int startIndex = stats.events.firstIndexOnOrAfter(finalBeginTime);
777                     final int size = stats.events.size();
778                     for (int i = startIndex; i < size; i++) {
779                         final Event event = stats.events.get(i);
780                         if (event.getTimeStamp() >= endTime) {
781                             return false;
782                         }
783 
784                         if (!packageName.equals(event.getPackageName())) {
785                             continue;
786                         }
787                         if (event.getEventType() == eventType) {
788                             accumulatedResult.add(event);
789                             // We've found the earliest of eventType. No need to keep going.
790                             return false;
791                         } else if (accumulatedResult.size() == 0) {
792                             // Save the earliest found event, even if it doesn't match.
793                             accumulatedResult.add(event);
794                         }
795                     }
796                     return true;
797                 }, false);
798 
799         if (results == null || results.isEmpty()) {
800             // There won't be any new events added earlier than endTime, so we can use endTime to
801             // avoid querying for events earlier than it.
802             cachedEvents.eventTime = Math.min(currentTime, endTime);
803             cachedEvents.events = null;
804             return null;
805         }
806 
807         cachedEvents.eventTime = results.get(results.size() - 1).getTimeStamp();
808         cachedEvents.events = results;
809         return new UsageEvents(results, new String[]{packageName}, false);
810     }
811 
persistActiveStats()812     void persistActiveStats() {
813         if (mStatsChanged) {
814             Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
815             try {
816                 mDatabase.obfuscateCurrentStats(mCurrentStats);
817                 mDatabase.writeMappingsLocked();
818                 for (int i = 0; i < mCurrentStats.length; i++) {
819                     mDatabase.putUsageStats(i, mCurrentStats[i]);
820                 }
821                 mStatsChanged = false;
822             } catch (IOException e) {
823                 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
824             }
825         }
826     }
827 
rolloverStats(final long currentTimeMillis)828     private void rolloverStats(final long currentTimeMillis) {
829         final long startTime = SystemClock.elapsedRealtime();
830         Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
831 
832         // Finish any ongoing events with an END_OF_DAY or ROLLOVER_FOREGROUND_SERVICE event.
833         // Make a note of which components need a new CONTINUE_PREVIOUS_DAY or
834         // CONTINUING_FOREGROUND_SERVICE entry.
835         final Configuration previousConfig =
836                 mCurrentStats[INTERVAL_DAILY].activeConfiguration;
837         ArraySet<String> continuePkgs = new ArraySet<>();
838         ArrayMap<String, SparseIntArray> continueActivity =
839                 new ArrayMap<>();
840         ArrayMap<String, ArrayMap<String, Integer>> continueForegroundService =
841                 new ArrayMap<>();
842         for (IntervalStats stat : mCurrentStats) {
843             final int pkgCount = stat.packageStats.size();
844             for (int i = 0; i < pkgCount; i++) {
845                 final UsageStats pkgStats = stat.packageStats.valueAt(i);
846                 if (pkgStats.mActivities.size() > 0
847                         || !pkgStats.mForegroundServices.isEmpty()) {
848                     if (pkgStats.mActivities.size() > 0) {
849                         continueActivity.put(pkgStats.mPackageName,
850                                 pkgStats.mActivities);
851                         stat.update(pkgStats.mPackageName, null,
852                                 mDailyExpiryDate.getTimeInMillis() - 1,
853                                 Event.END_OF_DAY, 0);
854                     }
855                     if (!pkgStats.mForegroundServices.isEmpty()) {
856                         continueForegroundService.put(pkgStats.mPackageName,
857                                 pkgStats.mForegroundServices);
858                         stat.update(pkgStats.mPackageName, null,
859                                 mDailyExpiryDate.getTimeInMillis() - 1,
860                                 Event.ROLLOVER_FOREGROUND_SERVICE, 0);
861                     }
862                     continuePkgs.add(pkgStats.mPackageName);
863                     notifyStatsChanged();
864                 }
865             }
866 
867             stat.updateConfigurationStats(null,
868                     mDailyExpiryDate.getTimeInMillis() - 1);
869             stat.commitTime(mDailyExpiryDate.getTimeInMillis() - 1);
870         }
871 
872         persistActiveStats();
873         mDatabase.prune(currentTimeMillis);
874         loadActiveStats(currentTimeMillis);
875 
876         final int continueCount = continuePkgs.size();
877         for (int i = 0; i < continueCount; i++) {
878             String pkgName = continuePkgs.valueAt(i);
879             final long beginTime = mCurrentStats[INTERVAL_DAILY].beginTime;
880             for (IntervalStats stat : mCurrentStats) {
881                 if (continueActivity.containsKey(pkgName)) {
882                     final SparseIntArray eventMap =
883                             continueActivity.get(pkgName);
884                     final int size = eventMap.size();
885                     for (int j = 0; j < size; j++) {
886                         stat.update(pkgName, null, beginTime,
887                                 eventMap.valueAt(j), eventMap.keyAt(j));
888                     }
889                 }
890                 if (continueForegroundService.containsKey(pkgName)) {
891                     final ArrayMap<String, Integer> eventMap =
892                             continueForegroundService.get(pkgName);
893                     final int size = eventMap.size();
894                     for (int j = 0; j < size; j++) {
895                         stat.update(pkgName, eventMap.keyAt(j), beginTime,
896                                 eventMap.valueAt(j), 0);
897                     }
898                 }
899                 stat.updateConfigurationStats(previousConfig, beginTime);
900                 notifyStatsChanged();
901             }
902         }
903         persistActiveStats();
904 
905         final long totalTime = SystemClock.elapsedRealtime() - startTime;
906         Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
907                 + " milliseconds");
908     }
909 
notifyStatsChanged()910     private void notifyStatsChanged() {
911         if (!mStatsChanged) {
912             mStatsChanged = true;
913             mListener.onStatsUpdated();
914         }
915     }
916 
notifyNewUpdate()917     private void notifyNewUpdate() {
918         mListener.onNewUpdate(mUserId);
919     }
920 
loadActiveStats(final long currentTimeMillis)921     private void loadActiveStats(final long currentTimeMillis) {
922         for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
923             final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
924             if (stats != null
925                     && currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
926                 if (DEBUG) {
927                     Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
928                             sDateFormat.format(stats.beginTime) + "(" + stats.beginTime +
929                             ") for interval " + intervalType);
930                 }
931                 mCurrentStats[intervalType] = stats;
932             } else {
933                 // No good fit remains.
934                 if (DEBUG) {
935                     Slog.d(TAG, "Creating new stats @ " +
936                             sDateFormat.format(currentTimeMillis) + "(" +
937                             currentTimeMillis + ") for interval " + intervalType);
938                 }
939 
940                 mCurrentStats[intervalType] = new IntervalStats();
941                 mCurrentStats[intervalType].beginTime = currentTimeMillis;
942                 mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
943             }
944         }
945 
946         mStatsChanged = false;
947         updateRolloverDeadline();
948 
949         // Tell the listener that the stats reloaded, which may have changed idle states.
950         mListener.onStatsReloaded();
951     }
952 
updateRolloverDeadline()953     private void updateRolloverDeadline() {
954         mDailyExpiryDate.setTimeInMillis(
955                 mCurrentStats[INTERVAL_DAILY].beginTime);
956         mDailyExpiryDate.addDays(1);
957         Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
958                 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
959                 mDailyExpiryDate.getTimeInMillis() + ")");
960     }
961 
962     //
963     // -- DUMP related methods --
964     //
965 
checkin(final IndentingPrintWriter pw)966     void checkin(final IndentingPrintWriter pw) {
967         mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
968             @Override
969             public boolean checkin(IntervalStats stats) {
970                 printIntervalStats(pw, stats, false, false, null);
971                 return true;
972             }
973         });
974     }
975 
dump(IndentingPrintWriter pw, List<String> pkgs)976     void dump(IndentingPrintWriter pw, List<String> pkgs) {
977         dump(pw, pkgs, false);
978     }
979 
dump(IndentingPrintWriter pw, List<String> pkgs, boolean compact)980     void dump(IndentingPrintWriter pw, List<String> pkgs, boolean compact) {
981         printLast24HrEvents(pw, !compact, pkgs);
982         for (int interval = 0; interval < mCurrentStats.length; interval++) {
983             pw.print("In-memory ");
984             pw.print(intervalToString(interval));
985             pw.println(" stats");
986             printIntervalStats(pw, mCurrentStats[interval], !compact, true, pkgs);
987         }
988         if (CollectionUtils.isEmpty(pkgs)) {
989             mDatabase.dump(pw, compact);
990         }
991     }
992 
dumpDatabaseInfo(IndentingPrintWriter ipw)993     void dumpDatabaseInfo(IndentingPrintWriter ipw) {
994         mDatabase.dump(ipw, false);
995     }
996 
dumpMappings(IndentingPrintWriter ipw)997     void dumpMappings(IndentingPrintWriter ipw) {
998         mDatabase.dumpMappings(ipw);
999     }
1000 
deleteDataFor(String pkg)1001     void deleteDataFor(String pkg) {
1002         mDatabase.deleteDataFor(pkg);
1003     }
1004 
dumpFile(IndentingPrintWriter ipw, String[] args)1005     void dumpFile(IndentingPrintWriter ipw, String[] args) {
1006         if (args == null || args.length == 0) {
1007             // dump all files for every interval for specified user
1008             final int numIntervals = mDatabase.mSortedStatFiles.length;
1009             for (int interval = 0; interval < numIntervals; interval++) {
1010                 ipw.println("interval=" + intervalToString(interval));
1011                 ipw.increaseIndent();
1012                 dumpFileDetailsForInterval(ipw, interval);
1013                 ipw.decreaseIndent();
1014             }
1015         } else {
1016             final int interval;
1017             try {
1018                 final int intervalValue = stringToInterval(args[0]);
1019                 if (intervalValue == -1) {
1020                     interval = Integer.valueOf(args[0]);
1021                 } else {
1022                     interval = intervalValue;
1023                 }
1024             } catch (NumberFormatException nfe) {
1025                 ipw.println("invalid interval specified.");
1026                 return;
1027             }
1028             if (interval < 0 || interval >= mDatabase.mSortedStatFiles.length) {
1029                 ipw.println("the specified interval does not exist.");
1030                 return;
1031             }
1032             if (args.length == 1) {
1033                 // dump all files in the specified interval
1034                 dumpFileDetailsForInterval(ipw, interval);
1035             } else {
1036                 // dump details only for the specified filename
1037                 final long filename;
1038                 try {
1039                     filename = Long.valueOf(args[1]);
1040                 } catch (NumberFormatException nfe) {
1041                     ipw.println("invalid filename specified.");
1042                     return;
1043                 }
1044                 final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename);
1045                 if (stats == null) {
1046                     ipw.println("the specified filename does not exist.");
1047                     return;
1048                 }
1049                 dumpFileDetails(ipw, stats, Long.valueOf(args[1]));
1050             }
1051         }
1052     }
1053 
dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval)1054     private void dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval) {
1055         final LongSparseArray<AtomicFile> files = mDatabase.mSortedStatFiles[interval];
1056         final int numFiles = files.size();
1057         for (int i = 0; i < numFiles; i++) {
1058             final long filename = files.keyAt(i);
1059             final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename);
1060             dumpFileDetails(ipw, stats, filename);
1061             ipw.println();
1062         }
1063     }
1064 
dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename)1065     private void dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename) {
1066         ipw.println("file=" + filename);
1067         ipw.increaseIndent();
1068         printIntervalStats(ipw, stats, false, false, null);
1069         ipw.decreaseIndent();
1070     }
1071 
formatDateTime(long dateTime, boolean pretty)1072     static String formatDateTime(long dateTime, boolean pretty) {
1073         if (pretty) {
1074             return "\"" + sDateFormat.format(dateTime)+ "\"";
1075         }
1076         return Long.toString(dateTime);
1077     }
1078 
formatElapsedTime(long elapsedTime, boolean pretty)1079     private String formatElapsedTime(long elapsedTime, boolean pretty) {
1080         if (pretty) {
1081             return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
1082         }
1083         return Long.toString(elapsedTime);
1084     }
1085 
printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates)1086     static void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) {
1087         pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
1088         pw.printPair("type", eventToString(event.mEventType));
1089         pw.printPair("package", event.mPackage);
1090         if (event.mClass != null) {
1091             pw.printPair("class", event.mClass);
1092         }
1093         if (event.mConfiguration != null) {
1094             pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
1095         }
1096         if (event.mShortcutId != null) {
1097             pw.printPair("shortcutId", event.mShortcutId);
1098         }
1099         if (event.mEventType == Event.STANDBY_BUCKET_CHANGED) {
1100             pw.printPair("standbyBucket", event.getAppStandbyBucket());
1101             pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason()));
1102         } else if (event.mEventType == Event.ACTIVITY_RESUMED
1103                 || event.mEventType == Event.ACTIVITY_PAUSED
1104                 || event.mEventType == Event.ACTIVITY_STOPPED) {
1105             pw.printPair("instanceId", event.getInstanceId());
1106         }
1107 
1108         if (event.getTaskRootPackageName() != null) {
1109             pw.printPair("taskRootPackage", event.getTaskRootPackageName());
1110         }
1111 
1112         if (event.getTaskRootClassName() != null) {
1113             pw.printPair("taskRootClass", event.getTaskRootClassName());
1114         }
1115 
1116         if (event.mNotificationChannelId != null) {
1117             pw.printPair("channelId", event.mNotificationChannelId);
1118         }
1119 
1120         if ((event.mEventType == Event.USER_INTERACTION) && (event.mExtras != null)) {
1121             pw.print(event.mExtras.toString());
1122         }
1123         pw.printHexPair("flags", event.mFlags);
1124         pw.println();
1125     }
1126 
printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, final List<String> pkgs)1127     void printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates,
1128             final List<String> pkgs) {
1129         final long endTime = System.currentTimeMillis();
1130         UnixCalendar yesterday = new UnixCalendar(endTime);
1131         yesterday.addDays(-1);
1132 
1133         final long beginTime = yesterday.getTimeInMillis();
1134 
1135         List<Event> events = queryStats(INTERVAL_DAILY,
1136                 beginTime, endTime, new StatCombiner<Event>() {
1137                     @Override
1138                     public boolean combine(IntervalStats stats, boolean mutable,
1139                             List<Event> accumulatedResult) {
1140                         final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
1141                         final int size = stats.events.size();
1142                         for (int i = startIndex; i < size; i++) {
1143                             if (stats.events.get(i).mTimeStamp >= endTime) {
1144                                 return false;
1145                             }
1146 
1147                             Event event = stats.events.get(i);
1148                             if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(event.mPackage)) {
1149                                 continue;
1150                             }
1151                             accumulatedResult.add(event);
1152                         }
1153                         return true;
1154                     }
1155                 }, false);
1156 
1157         pw.print("Last 24 hour events (");
1158         if (prettyDates) {
1159             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
1160                     beginTime, endTime, sDateFormatFlags) + "\"");
1161         } else {
1162             pw.printPair("beginTime", beginTime);
1163             pw.printPair("endTime", endTime);
1164         }
1165         pw.println(")");
1166         if (events != null) {
1167             pw.increaseIndent();
1168             for (Event event : events) {
1169                 printEvent(pw, event, prettyDates);
1170             }
1171             pw.decreaseIndent();
1172         }
1173     }
1174 
printEventAggregation(IndentingPrintWriter pw, String label, IntervalStats.EventTracker tracker, boolean prettyDates)1175     void printEventAggregation(IndentingPrintWriter pw, String label,
1176             IntervalStats.EventTracker tracker, boolean prettyDates) {
1177         if (tracker.count != 0 || tracker.duration != 0) {
1178             pw.print(label);
1179             pw.print(": ");
1180             pw.print(tracker.count);
1181             pw.print("x for ");
1182             pw.print(formatElapsedTime(tracker.duration, prettyDates));
1183             if (tracker.curStartTime != 0) {
1184                 pw.print(" (now running, started at ");
1185                 formatDateTime(tracker.curStartTime, prettyDates);
1186                 pw.print(")");
1187             }
1188             pw.println();
1189         }
1190     }
1191 
printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates, boolean skipEvents, List<String> pkgs)1192     void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
1193             boolean prettyDates, boolean skipEvents, List<String> pkgs) {
1194         if (prettyDates) {
1195             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
1196                     stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
1197         } else {
1198             pw.printPair("beginTime", stats.beginTime);
1199             pw.printPair("endTime", stats.endTime);
1200         }
1201         pw.println();
1202         pw.increaseIndent();
1203         pw.println("packages");
1204         pw.increaseIndent();
1205         final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
1206         final int pkgCount = pkgStats.size();
1207         for (int i = 0; i < pkgCount; i++) {
1208             final UsageStats usageStats = pkgStats.valueAt(i);
1209             if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(usageStats.mPackageName)) {
1210                 continue;
1211             }
1212             pw.printPair("package", usageStats.mPackageName);
1213             pw.printPair("totalTimeUsed",
1214                     formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
1215             pw.printPair("lastTimeUsed", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
1216             pw.printPair("totalTimeVisible",
1217                     formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates));
1218             pw.printPair("lastTimeVisible",
1219                     formatDateTime(usageStats.mLastTimeVisible, prettyDates));
1220             pw.printPair("lastTimeComponentUsed",
1221                     formatDateTime(usageStats.mLastTimeComponentUsed, prettyDates));
1222             pw.printPair("totalTimeFS",
1223                     formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates));
1224             pw.printPair("lastTimeFS",
1225                     formatDateTime(usageStats.mLastTimeForegroundServiceUsed, prettyDates));
1226             pw.printPair("appLaunchCount", usageStats.mAppLaunchCount);
1227             pw.println();
1228         }
1229         pw.decreaseIndent();
1230 
1231         pw.println();
1232         pw.println("ChooserCounts");
1233         pw.increaseIndent();
1234         for (UsageStats usageStats : pkgStats.values()) {
1235             if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(usageStats.mPackageName)) {
1236                 continue;
1237             }
1238             pw.printPair("package", usageStats.mPackageName);
1239             if (usageStats.mChooserCounts != null) {
1240                 final int chooserCountSize = usageStats.mChooserCounts.size();
1241                 for (int i = 0; i < chooserCountSize; i++) {
1242                     final String action = usageStats.mChooserCounts.keyAt(i);
1243                     final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i);
1244                     final int annotationSize = counts.size();
1245                     for (int j = 0; j < annotationSize; j++) {
1246                         final String key = counts.keyAt(j);
1247                         final int count = counts.valueAt(j);
1248                         if (count != 0) {
1249                             pw.printPair("ChooserCounts", action + ":" + key + " is " +
1250                                     Integer.toString(count));
1251                             pw.println();
1252                         }
1253                     }
1254                 }
1255             }
1256             pw.println();
1257         }
1258         pw.decreaseIndent();
1259 
1260         if (CollectionUtils.isEmpty(pkgs)) {
1261             pw.println("configurations");
1262             pw.increaseIndent();
1263             final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
1264             final int configCount = configStats.size();
1265             for (int i = 0; i < configCount; i++) {
1266                 final ConfigurationStats config = configStats.valueAt(i);
1267                 pw.printPair("config", Configuration.resourceQualifierString(
1268                         config.mConfiguration));
1269                 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
1270                 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
1271                 pw.printPair("count", config.mActivationCount);
1272                 pw.println();
1273             }
1274             pw.decreaseIndent();
1275             pw.println("event aggregations");
1276             pw.increaseIndent();
1277             printEventAggregation(pw, "screen-interactive", stats.interactiveTracker,
1278                     prettyDates);
1279             printEventAggregation(pw, "screen-non-interactive", stats.nonInteractiveTracker,
1280                     prettyDates);
1281             printEventAggregation(pw, "keyguard-shown", stats.keyguardShownTracker,
1282                     prettyDates);
1283             printEventAggregation(pw, "keyguard-hidden", stats.keyguardHiddenTracker,
1284                     prettyDates);
1285             pw.decreaseIndent();
1286         }
1287 
1288         // The last 24 hours of events is already printed in the non checkin dump
1289         // No need to repeat here.
1290         if (!skipEvents) {
1291             pw.println("events");
1292             pw.increaseIndent();
1293             final EventList events = stats.events;
1294             final int eventCount = events != null ? events.size() : 0;
1295             for (int i = 0; i < eventCount; i++) {
1296                 final Event event = events.get(i);
1297                 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(event.mPackage)) {
1298                     continue;
1299                 }
1300                 printEvent(pw, event, prettyDates);
1301             }
1302             pw.decreaseIndent();
1303         }
1304         pw.decreaseIndent();
1305     }
1306 
intervalToString(int interval)1307     public static String intervalToString(int interval) {
1308         switch (interval) {
1309             case INTERVAL_DAILY:
1310                 return "daily";
1311             case INTERVAL_WEEKLY:
1312                 return "weekly";
1313             case INTERVAL_MONTHLY:
1314                 return "monthly";
1315             case INTERVAL_YEARLY:
1316                 return "yearly";
1317             default:
1318                 return "?";
1319         }
1320     }
1321 
stringToInterval(String interval)1322     private static int stringToInterval(String interval) {
1323         switch (interval.toLowerCase()) {
1324             case "daily":
1325                 return INTERVAL_DAILY;
1326             case "weekly":
1327                 return INTERVAL_WEEKLY;
1328             case "monthly":
1329                 return INTERVAL_MONTHLY;
1330             case "yearly":
1331                 return INTERVAL_YEARLY;
1332             default:
1333                 return -1;
1334         }
1335     }
1336 
eventToString(int eventType)1337     static String eventToString(int eventType) {
1338         switch (eventType) {
1339             case Event.NONE:
1340                 return "NONE";
1341             case Event.ACTIVITY_PAUSED:
1342                 return "ACTIVITY_PAUSED";
1343             case Event.ACTIVITY_RESUMED:
1344                 return "ACTIVITY_RESUMED";
1345             case Event.FOREGROUND_SERVICE_START:
1346                 return "FOREGROUND_SERVICE_START";
1347             case Event.FOREGROUND_SERVICE_STOP:
1348                 return "FOREGROUND_SERVICE_STOP";
1349             case Event.ACTIVITY_STOPPED:
1350                 return "ACTIVITY_STOPPED";
1351             case Event.END_OF_DAY:
1352                 return "END_OF_DAY";
1353             case Event.ROLLOVER_FOREGROUND_SERVICE:
1354                 return "ROLLOVER_FOREGROUND_SERVICE";
1355             case Event.CONTINUE_PREVIOUS_DAY:
1356                 return "CONTINUE_PREVIOUS_DAY";
1357             case Event.CONTINUING_FOREGROUND_SERVICE:
1358                 return "CONTINUING_FOREGROUND_SERVICE";
1359             case Event.CONFIGURATION_CHANGE:
1360                 return "CONFIGURATION_CHANGE";
1361             case Event.SYSTEM_INTERACTION:
1362                 return "SYSTEM_INTERACTION";
1363             case Event.USER_INTERACTION:
1364                 return "USER_INTERACTION";
1365             case Event.SHORTCUT_INVOCATION:
1366                 return "SHORTCUT_INVOCATION";
1367             case Event.CHOOSER_ACTION:
1368                 return "CHOOSER_ACTION";
1369             case Event.NOTIFICATION_SEEN:
1370                 return "NOTIFICATION_SEEN";
1371             case Event.STANDBY_BUCKET_CHANGED:
1372                 return "STANDBY_BUCKET_CHANGED";
1373             case Event.NOTIFICATION_INTERRUPTION:
1374                 return "NOTIFICATION_INTERRUPTION";
1375             case Event.SLICE_PINNED:
1376                 return "SLICE_PINNED";
1377             case Event.SLICE_PINNED_PRIV:
1378                 return "SLICE_PINNED_PRIV";
1379             case Event.SCREEN_INTERACTIVE:
1380                 return "SCREEN_INTERACTIVE";
1381             case Event.SCREEN_NON_INTERACTIVE:
1382                 return "SCREEN_NON_INTERACTIVE";
1383             case Event.KEYGUARD_SHOWN:
1384                 return "KEYGUARD_SHOWN";
1385             case Event.KEYGUARD_HIDDEN:
1386                 return "KEYGUARD_HIDDEN";
1387             case Event.DEVICE_SHUTDOWN:
1388                 return "DEVICE_SHUTDOWN";
1389             case Event.DEVICE_STARTUP:
1390                 return "DEVICE_STARTUP";
1391             case Event.USER_UNLOCKED:
1392                 return "USER_UNLOCKED";
1393             case Event.USER_STOPPED:
1394                 return "USER_STOPPED";
1395             case Event.LOCUS_ID_SET:
1396                 return "LOCUS_ID_SET";
1397             case Event.APP_COMPONENT_USED:
1398                 return "APP_COMPONENT_USED";
1399             default:
1400                 return "UNKNOWN_TYPE_" + eventType;
1401         }
1402     }
1403 
getBackupPayload(String key)1404     byte[] getBackupPayload(String key){
1405         checkAndGetTimeLocked();
1406         persistActiveStats();
1407         return mDatabase.getBackupPayload(key);
1408     }
1409 
applyRestoredPayload(String key, byte[] payload)1410     Set<String> applyRestoredPayload(String key, byte[] payload) {
1411         checkAndGetTimeLocked();
1412         return mDatabase.applyRestoredPayload(key, payload);
1413     }
1414 }
1415