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.Slog;
50 import android.util.SparseArrayMap;
51 import android.util.SparseIntArray;
52 import android.util.TimeSparseArray;
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";
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)450     private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
451             StatCombiner<T> combiner) {
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);
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);
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);
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);
536     }
537 
queryEvents(final long beginTime, final long endTime, int flags)538     UsageEvents queryEvents(final long beginTime, final long endTime, int flags) {
539         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
540             return null;
541         }
542         final ArraySet<String> names = new ArraySet<>();
543         List<Event> results = queryStats(INTERVAL_DAILY,
544                 beginTime, endTime, new StatCombiner<Event>() {
545                     @Override
546                     public boolean combine(IntervalStats stats, boolean mutable,
547                             List<Event> accumulatedResult) {
548                         final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
549                         final int size = stats.events.size();
550                         for (int i = startIndex; i < size; i++) {
551                             Event event = stats.events.get(i);
552                             if (event.mTimeStamp >= endTime) {
553                                 return false;
554                             }
555 
556                             final int eventType = event.mEventType;
557                             if (eventType == Event.SHORTCUT_INVOCATION
558                                     && (flags & HIDE_SHORTCUT_EVENTS) == HIDE_SHORTCUT_EVENTS) {
559                                 continue;
560                             }
561                             if (eventType == Event.LOCUS_ID_SET
562                                     && (flags & HIDE_LOCUS_EVENTS) == HIDE_LOCUS_EVENTS) {
563                                 continue;
564                             }
565                             if ((eventType == Event.NOTIFICATION_SEEN
566                                     || eventType == Event.NOTIFICATION_INTERRUPTION)
567                                     && (flags & OBFUSCATE_NOTIFICATION_EVENTS)
568                                     == OBFUSCATE_NOTIFICATION_EVENTS) {
569                                 event = event.getObfuscatedNotificationEvent();
570                             }
571                             if ((flags & OBFUSCATE_INSTANT_APPS) == OBFUSCATE_INSTANT_APPS) {
572                                 event = event.getObfuscatedIfInstantApp();
573                             }
574                             if (event.mPackage != null) {
575                                 names.add(event.mPackage);
576                             }
577                             if (event.mClass != null) {
578                                 names.add(event.mClass);
579                             }
580                             if (event.mTaskRootPackage != null) {
581                                 names.add(event.mTaskRootPackage);
582                             }
583                             if (event.mTaskRootClass != null) {
584                                 names.add(event.mTaskRootClass);
585                             }
586                             accumulatedResult.add(event);
587                         }
588                         return true;
589                     }
590                 });
591 
592         if (results == null || results.isEmpty()) {
593             return null;
594         }
595 
596         String[] table = names.toArray(new String[names.size()]);
597         Arrays.sort(table);
598         return new UsageEvents(results, table, true);
599     }
600 
601     /**
602      * Returns a {@link UsageEvents} object whose events list contains only the earliest event seen
603      * for each app as well as the earliest event of {@code eventType} seen for each app.
604      */
605     @Nullable
queryEarliestAppEvents(final long beginTime, final long endTime, final int eventType)606     UsageEvents queryEarliestAppEvents(final long beginTime, final long endTime,
607             final int eventType) {
608         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
609             return null;
610         }
611         final ArraySet<String> names = new ArraySet<>();
612         final ArraySet<String> eventSuccess = new ArraySet<>();
613         final List<Event> results = queryStats(INTERVAL_DAILY,
614                 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
615                     final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
616                     final int size = stats.events.size();
617                     for (int i = startIndex; i < size; i++) {
618                         final Event event = stats.events.get(i);
619                         if (event.getTimeStamp() >= endTime) {
620                             return false;
621                         }
622                         if (event.getPackageName() == null) {
623                             continue;
624                         }
625                         if (eventSuccess.contains(event.getPackageName())) {
626                             continue;
627                         }
628 
629                         final boolean firstEvent = names.add(event.getPackageName());
630 
631                         if (event.getEventType() == eventType) {
632                             accumulatedResult.add(event);
633                             eventSuccess.add(event.getPackageName());
634                         } else if (firstEvent) {
635                             // Save the earliest found event for the app, even if it doesn't match.
636                             accumulatedResult.add(event);
637                         }
638                     }
639                     return true;
640                 });
641 
642         if (results == null || results.isEmpty()) {
643             return null;
644         }
645         if (DEBUG) {
646             Slog.d(TAG, "Found " + results.size() + " early events for " + names.size() + " apps");
647         }
648 
649         String[] table = names.toArray(new String[names.size()]);
650         Arrays.sort(table);
651         return new UsageEvents(results, table, false);
652     }
653 
654     @Nullable
queryEventsForPackage(final long beginTime, final long endTime, final String packageName, boolean includeTaskRoot)655     UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
656             final String packageName, boolean includeTaskRoot) {
657         if (!validRange(checkAndGetTimeLocked(), beginTime, endTime)) {
658             return null;
659         }
660         final ArraySet<String> names = new ArraySet<>();
661         names.add(packageName);
662         final List<Event> results = queryStats(INTERVAL_DAILY,
663                 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
664                     final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
665                     final int size = stats.events.size();
666                     for (int i = startIndex; i < size; i++) {
667                         final Event event = stats.events.get(i);
668                         if (event.mTimeStamp >= endTime) {
669                             return false;
670                         }
671 
672                         if (!packageName.equals(event.mPackage)) {
673                             continue;
674                         }
675                         if (event.mClass != null) {
676                             names.add(event.mClass);
677                         }
678                         if (includeTaskRoot && event.mTaskRootPackage != null) {
679                             names.add(event.mTaskRootPackage);
680                         }
681                         if (includeTaskRoot && event.mTaskRootClass != null) {
682                             names.add(event.mTaskRootClass);
683                         }
684                         accumulatedResult.add(event);
685                     }
686                     return true;
687                 });
688 
689         if (results == null || results.isEmpty()) {
690             return null;
691         }
692 
693         final String[] table = names.toArray(new String[names.size()]);
694         Arrays.sort(table);
695         return new UsageEvents(results, table, includeTaskRoot);
696     }
697 
698     /**
699      * Returns a {@link UsageEvents} object whose events list contains only the earliest event seen
700      * for the package as well as the earliest event of {@code eventType} seen for the package.
701      */
702     @Nullable
queryEarliestEventsForPackage(long beginTime, final long endTime, @NonNull final String packageName, final int eventType)703     UsageEvents queryEarliestEventsForPackage(long beginTime, final long endTime,
704             @NonNull final String packageName, final int eventType) {
705         final long currentTime = checkAndGetTimeLocked();
706         if (!validRange(currentTime, beginTime, endTime)) {
707             return null;
708         }
709 
710         CachedEarlyEvents cachedEvents = mCachedEarlyEvents.get(eventType, packageName);
711         if (cachedEvents != null) {
712             // We can use this cached event if the previous search time was the exact same
713             // or earlier AND the event we previously found was at this current time or
714             // afterwards. Since no new events will be added before the cached event,
715             // redoing the search will yield the same event.
716             if (cachedEvents.searchBeginTime <= beginTime && beginTime <= cachedEvents.eventTime) {
717                 final int numEvents = cachedEvents.events == null ? 0 : cachedEvents.events.size();
718                 if ((numEvents == 0
719                         || cachedEvents.events.get(numEvents - 1).getEventType() != eventType)
720                         && cachedEvents.eventTime < endTime) {
721                     // We didn't find a match in the earlier range but this new request is allowing
722                     // us to look at events after the previous request's end time, so we may find
723                     // something new.
724                     beginTime = Math.min(currentTime, cachedEvents.eventTime);
725                     // Leave the cachedEvents's searchBeginTime as the earlier begin time to
726                     // cache/show that we searched the entire range (the union of the two queries):
727                     // [previous query's begin time, current query's end time].
728                 } else if (cachedEvents.eventTime <= endTime) {
729                     if (cachedEvents.events == null) {
730                         return null;
731                     }
732                     return new UsageEvents(cachedEvents.events, new String[]{packageName}, false);
733                 } else {
734                     // Any event we previously found is after the end of this query's range, but
735                     // this query starts at the same time (or after) the previous query's begin time
736                     // so there is no event to return.
737                     return null;
738                 }
739             } else {
740                 // The previous query isn't helpful in any way for this query. Reset the event data.
741                 cachedEvents.searchBeginTime = beginTime;
742             }
743         } else {
744             cachedEvents = new CachedEarlyEvents();
745             cachedEvents.searchBeginTime = beginTime;
746             mCachedEarlyEvents.add(eventType, packageName, cachedEvents);
747         }
748 
749         final long finalBeginTime = beginTime;
750         final List<Event> results = queryStats(INTERVAL_DAILY,
751                 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
752                     final int startIndex = stats.events.firstIndexOnOrAfter(finalBeginTime);
753                     final int size = stats.events.size();
754                     for (int i = startIndex; i < size; i++) {
755                         final Event event = stats.events.get(i);
756                         if (event.getTimeStamp() >= endTime) {
757                             return false;
758                         }
759 
760                         if (!packageName.equals(event.getPackageName())) {
761                             continue;
762                         }
763                         if (event.getEventType() == eventType) {
764                             accumulatedResult.add(event);
765                             // We've found the earliest of eventType. No need to keep going.
766                             return false;
767                         } else if (accumulatedResult.size() == 0) {
768                             // Save the earliest found event, even if it doesn't match.
769                             accumulatedResult.add(event);
770                         }
771                     }
772                     return true;
773                 });
774 
775         if (results == null || results.isEmpty()) {
776             // There won't be any new events added earlier than endTime, so we can use endTime to
777             // avoid querying for events earlier than it.
778             cachedEvents.eventTime = Math.min(currentTime, endTime);
779             cachedEvents.events = null;
780             return null;
781         }
782 
783         cachedEvents.eventTime = results.get(results.size() - 1).getTimeStamp();
784         cachedEvents.events = results;
785         return new UsageEvents(results, new String[]{packageName}, false);
786     }
787 
persistActiveStats()788     void persistActiveStats() {
789         if (mStatsChanged) {
790             Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
791             try {
792                 mDatabase.obfuscateCurrentStats(mCurrentStats);
793                 mDatabase.writeMappingsLocked();
794                 for (int i = 0; i < mCurrentStats.length; i++) {
795                     mDatabase.putUsageStats(i, mCurrentStats[i]);
796                 }
797                 mStatsChanged = false;
798             } catch (IOException e) {
799                 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
800             }
801         }
802     }
803 
rolloverStats(final long currentTimeMillis)804     private void rolloverStats(final long currentTimeMillis) {
805         final long startTime = SystemClock.elapsedRealtime();
806         Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
807 
808         // Finish any ongoing events with an END_OF_DAY or ROLLOVER_FOREGROUND_SERVICE event.
809         // Make a note of which components need a new CONTINUE_PREVIOUS_DAY or
810         // CONTINUING_FOREGROUND_SERVICE entry.
811         final Configuration previousConfig =
812                 mCurrentStats[INTERVAL_DAILY].activeConfiguration;
813         ArraySet<String> continuePkgs = new ArraySet<>();
814         ArrayMap<String, SparseIntArray> continueActivity =
815                 new ArrayMap<>();
816         ArrayMap<String, ArrayMap<String, Integer>> continueForegroundService =
817                 new ArrayMap<>();
818         for (IntervalStats stat : mCurrentStats) {
819             final int pkgCount = stat.packageStats.size();
820             for (int i = 0; i < pkgCount; i++) {
821                 final UsageStats pkgStats = stat.packageStats.valueAt(i);
822                 if (pkgStats.mActivities.size() > 0
823                         || !pkgStats.mForegroundServices.isEmpty()) {
824                     if (pkgStats.mActivities.size() > 0) {
825                         continueActivity.put(pkgStats.mPackageName,
826                                 pkgStats.mActivities);
827                         stat.update(pkgStats.mPackageName, null,
828                                 mDailyExpiryDate.getTimeInMillis() - 1,
829                                 Event.END_OF_DAY, 0);
830                     }
831                     if (!pkgStats.mForegroundServices.isEmpty()) {
832                         continueForegroundService.put(pkgStats.mPackageName,
833                                 pkgStats.mForegroundServices);
834                         stat.update(pkgStats.mPackageName, null,
835                                 mDailyExpiryDate.getTimeInMillis() - 1,
836                                 Event.ROLLOVER_FOREGROUND_SERVICE, 0);
837                     }
838                     continuePkgs.add(pkgStats.mPackageName);
839                     notifyStatsChanged();
840                 }
841             }
842 
843             stat.updateConfigurationStats(null,
844                     mDailyExpiryDate.getTimeInMillis() - 1);
845             stat.commitTime(mDailyExpiryDate.getTimeInMillis() - 1);
846         }
847 
848         persistActiveStats();
849         mDatabase.prune(currentTimeMillis);
850         loadActiveStats(currentTimeMillis);
851 
852         final int continueCount = continuePkgs.size();
853         for (int i = 0; i < continueCount; i++) {
854             String pkgName = continuePkgs.valueAt(i);
855             final long beginTime = mCurrentStats[INTERVAL_DAILY].beginTime;
856             for (IntervalStats stat : mCurrentStats) {
857                 if (continueActivity.containsKey(pkgName)) {
858                     final SparseIntArray eventMap =
859                             continueActivity.get(pkgName);
860                     final int size = eventMap.size();
861                     for (int j = 0; j < size; j++) {
862                         stat.update(pkgName, null, beginTime,
863                                 eventMap.valueAt(j), eventMap.keyAt(j));
864                     }
865                 }
866                 if (continueForegroundService.containsKey(pkgName)) {
867                     final ArrayMap<String, Integer> eventMap =
868                             continueForegroundService.get(pkgName);
869                     final int size = eventMap.size();
870                     for (int j = 0; j < size; j++) {
871                         stat.update(pkgName, eventMap.keyAt(j), beginTime,
872                                 eventMap.valueAt(j), 0);
873                     }
874                 }
875                 stat.updateConfigurationStats(previousConfig, beginTime);
876                 notifyStatsChanged();
877             }
878         }
879         persistActiveStats();
880 
881         final long totalTime = SystemClock.elapsedRealtime() - startTime;
882         Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
883                 + " milliseconds");
884     }
885 
notifyStatsChanged()886     private void notifyStatsChanged() {
887         if (!mStatsChanged) {
888             mStatsChanged = true;
889             mListener.onStatsUpdated();
890         }
891     }
892 
notifyNewUpdate()893     private void notifyNewUpdate() {
894         mListener.onNewUpdate(mUserId);
895     }
896 
loadActiveStats(final long currentTimeMillis)897     private void loadActiveStats(final long currentTimeMillis) {
898         for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
899             final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
900             if (stats != null
901                     && currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
902                 if (DEBUG) {
903                     Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
904                             sDateFormat.format(stats.beginTime) + "(" + stats.beginTime +
905                             ") for interval " + intervalType);
906                 }
907                 mCurrentStats[intervalType] = stats;
908             } else {
909                 // No good fit remains.
910                 if (DEBUG) {
911                     Slog.d(TAG, "Creating new stats @ " +
912                             sDateFormat.format(currentTimeMillis) + "(" +
913                             currentTimeMillis + ") for interval " + intervalType);
914                 }
915 
916                 mCurrentStats[intervalType] = new IntervalStats();
917                 mCurrentStats[intervalType].beginTime = currentTimeMillis;
918                 mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
919             }
920         }
921 
922         mStatsChanged = false;
923         updateRolloverDeadline();
924 
925         // Tell the listener that the stats reloaded, which may have changed idle states.
926         mListener.onStatsReloaded();
927     }
928 
updateRolloverDeadline()929     private void updateRolloverDeadline() {
930         mDailyExpiryDate.setTimeInMillis(
931                 mCurrentStats[INTERVAL_DAILY].beginTime);
932         mDailyExpiryDate.addDays(1);
933         Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
934                 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
935                 mDailyExpiryDate.getTimeInMillis() + ")");
936     }
937 
938     //
939     // -- DUMP related methods --
940     //
941 
checkin(final IndentingPrintWriter pw)942     void checkin(final IndentingPrintWriter pw) {
943         mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
944             @Override
945             public boolean checkin(IntervalStats stats) {
946                 printIntervalStats(pw, stats, false, false, null);
947                 return true;
948             }
949         });
950     }
951 
dump(IndentingPrintWriter pw, List<String> pkgs)952     void dump(IndentingPrintWriter pw, List<String> pkgs) {
953         dump(pw, pkgs, false);
954     }
955 
dump(IndentingPrintWriter pw, List<String> pkgs, boolean compact)956     void dump(IndentingPrintWriter pw, List<String> pkgs, boolean compact) {
957         printLast24HrEvents(pw, !compact, pkgs);
958         for (int interval = 0; interval < mCurrentStats.length; interval++) {
959             pw.print("In-memory ");
960             pw.print(intervalToString(interval));
961             pw.println(" stats");
962             printIntervalStats(pw, mCurrentStats[interval], !compact, true, pkgs);
963         }
964         if (CollectionUtils.isEmpty(pkgs)) {
965             mDatabase.dump(pw, compact);
966         }
967     }
968 
dumpDatabaseInfo(IndentingPrintWriter ipw)969     void dumpDatabaseInfo(IndentingPrintWriter ipw) {
970         mDatabase.dump(ipw, false);
971     }
972 
dumpMappings(IndentingPrintWriter ipw)973     void dumpMappings(IndentingPrintWriter ipw) {
974         mDatabase.dumpMappings(ipw);
975     }
976 
deleteDataFor(String pkg)977     void deleteDataFor(String pkg) {
978         mDatabase.deleteDataFor(pkg);
979     }
980 
dumpFile(IndentingPrintWriter ipw, String[] args)981     void dumpFile(IndentingPrintWriter ipw, String[] args) {
982         if (args == null || args.length == 0) {
983             // dump all files for every interval for specified user
984             final int numIntervals = mDatabase.mSortedStatFiles.length;
985             for (int interval = 0; interval < numIntervals; interval++) {
986                 ipw.println("interval=" + intervalToString(interval));
987                 ipw.increaseIndent();
988                 dumpFileDetailsForInterval(ipw, interval);
989                 ipw.decreaseIndent();
990             }
991         } else {
992             final int interval;
993             try {
994                 final int intervalValue = stringToInterval(args[0]);
995                 if (intervalValue == -1) {
996                     interval = Integer.valueOf(args[0]);
997                 } else {
998                     interval = intervalValue;
999                 }
1000             } catch (NumberFormatException nfe) {
1001                 ipw.println("invalid interval specified.");
1002                 return;
1003             }
1004             if (interval < 0 || interval >= mDatabase.mSortedStatFiles.length) {
1005                 ipw.println("the specified interval does not exist.");
1006                 return;
1007             }
1008             if (args.length == 1) {
1009                 // dump all files in the specified interval
1010                 dumpFileDetailsForInterval(ipw, interval);
1011             } else {
1012                 // dump details only for the specified filename
1013                 final long filename;
1014                 try {
1015                     filename = Long.valueOf(args[1]);
1016                 } catch (NumberFormatException nfe) {
1017                     ipw.println("invalid filename specified.");
1018                     return;
1019                 }
1020                 final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename);
1021                 if (stats == null) {
1022                     ipw.println("the specified filename does not exist.");
1023                     return;
1024                 }
1025                 dumpFileDetails(ipw, stats, Long.valueOf(args[1]));
1026             }
1027         }
1028     }
1029 
dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval)1030     private void dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval) {
1031         final TimeSparseArray<AtomicFile> files = mDatabase.mSortedStatFiles[interval];
1032         final int numFiles = files.size();
1033         for (int i = 0; i < numFiles; i++) {
1034             final long filename = files.keyAt(i);
1035             final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename);
1036             dumpFileDetails(ipw, stats, filename);
1037             ipw.println();
1038         }
1039     }
1040 
dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename)1041     private void dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename) {
1042         ipw.println("file=" + filename);
1043         ipw.increaseIndent();
1044         printIntervalStats(ipw, stats, false, false, null);
1045         ipw.decreaseIndent();
1046     }
1047 
formatDateTime(long dateTime, boolean pretty)1048     static String formatDateTime(long dateTime, boolean pretty) {
1049         if (pretty) {
1050             return "\"" + sDateFormat.format(dateTime)+ "\"";
1051         }
1052         return Long.toString(dateTime);
1053     }
1054 
formatElapsedTime(long elapsedTime, boolean pretty)1055     private String formatElapsedTime(long elapsedTime, boolean pretty) {
1056         if (pretty) {
1057             return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
1058         }
1059         return Long.toString(elapsedTime);
1060     }
1061 
printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates)1062     void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) {
1063         pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
1064         pw.printPair("type", eventToString(event.mEventType));
1065         pw.printPair("package", event.mPackage);
1066         if (event.mClass != null) {
1067             pw.printPair("class", event.mClass);
1068         }
1069         if (event.mConfiguration != null) {
1070             pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
1071         }
1072         if (event.mShortcutId != null) {
1073             pw.printPair("shortcutId", event.mShortcutId);
1074         }
1075         if (event.mEventType == Event.STANDBY_BUCKET_CHANGED) {
1076             pw.printPair("standbyBucket", event.getAppStandbyBucket());
1077             pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason()));
1078         } else if (event.mEventType == Event.ACTIVITY_RESUMED
1079                 || event.mEventType == Event.ACTIVITY_PAUSED
1080                 || event.mEventType == Event.ACTIVITY_STOPPED) {
1081             pw.printPair("instanceId", event.getInstanceId());
1082         }
1083 
1084         if (event.getTaskRootPackageName() != null) {
1085             pw.printPair("taskRootPackage", event.getTaskRootPackageName());
1086         }
1087 
1088         if (event.getTaskRootClassName() != null) {
1089             pw.printPair("taskRootClass", event.getTaskRootClassName());
1090         }
1091 
1092         if (event.mNotificationChannelId != null) {
1093             pw.printPair("channelId", event.mNotificationChannelId);
1094         }
1095         pw.printHexPair("flags", event.mFlags);
1096         pw.println();
1097     }
1098 
printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, final List<String> pkgs)1099     void printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates,
1100             final List<String> pkgs) {
1101         final long endTime = System.currentTimeMillis();
1102         UnixCalendar yesterday = new UnixCalendar(endTime);
1103         yesterday.addDays(-1);
1104 
1105         final long beginTime = yesterday.getTimeInMillis();
1106 
1107         List<Event> events = queryStats(INTERVAL_DAILY,
1108                 beginTime, endTime, new StatCombiner<Event>() {
1109                     @Override
1110                     public boolean combine(IntervalStats stats, boolean mutable,
1111                             List<Event> accumulatedResult) {
1112                         final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
1113                         final int size = stats.events.size();
1114                         for (int i = startIndex; i < size; i++) {
1115                             if (stats.events.get(i).mTimeStamp >= endTime) {
1116                                 return false;
1117                             }
1118 
1119                             Event event = stats.events.get(i);
1120                             if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(event.mPackage)) {
1121                                 continue;
1122                             }
1123                             accumulatedResult.add(event);
1124                         }
1125                         return true;
1126                     }
1127                 });
1128 
1129         pw.print("Last 24 hour events (");
1130         if (prettyDates) {
1131             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
1132                     beginTime, endTime, sDateFormatFlags) + "\"");
1133         } else {
1134             pw.printPair("beginTime", beginTime);
1135             pw.printPair("endTime", endTime);
1136         }
1137         pw.println(")");
1138         if (events != null) {
1139             pw.increaseIndent();
1140             for (Event event : events) {
1141                 printEvent(pw, event, prettyDates);
1142             }
1143             pw.decreaseIndent();
1144         }
1145     }
1146 
printEventAggregation(IndentingPrintWriter pw, String label, IntervalStats.EventTracker tracker, boolean prettyDates)1147     void printEventAggregation(IndentingPrintWriter pw, String label,
1148             IntervalStats.EventTracker tracker, boolean prettyDates) {
1149         if (tracker.count != 0 || tracker.duration != 0) {
1150             pw.print(label);
1151             pw.print(": ");
1152             pw.print(tracker.count);
1153             pw.print("x for ");
1154             pw.print(formatElapsedTime(tracker.duration, prettyDates));
1155             if (tracker.curStartTime != 0) {
1156                 pw.print(" (now running, started at ");
1157                 formatDateTime(tracker.curStartTime, prettyDates);
1158                 pw.print(")");
1159             }
1160             pw.println();
1161         }
1162     }
1163 
printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates, boolean skipEvents, List<String> pkgs)1164     void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
1165             boolean prettyDates, boolean skipEvents, List<String> pkgs) {
1166         if (prettyDates) {
1167             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
1168                     stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
1169         } else {
1170             pw.printPair("beginTime", stats.beginTime);
1171             pw.printPair("endTime", stats.endTime);
1172         }
1173         pw.println();
1174         pw.increaseIndent();
1175         pw.println("packages");
1176         pw.increaseIndent();
1177         final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
1178         final int pkgCount = pkgStats.size();
1179         for (int i = 0; i < pkgCount; i++) {
1180             final UsageStats usageStats = pkgStats.valueAt(i);
1181             if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(usageStats.mPackageName)) {
1182                 continue;
1183             }
1184             pw.printPair("package", usageStats.mPackageName);
1185             pw.printPair("totalTimeUsed",
1186                     formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
1187             pw.printPair("lastTimeUsed", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
1188             pw.printPair("totalTimeVisible",
1189                     formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates));
1190             pw.printPair("lastTimeVisible",
1191                     formatDateTime(usageStats.mLastTimeVisible, prettyDates));
1192             pw.printPair("lastTimeComponentUsed",
1193                     formatDateTime(usageStats.mLastTimeComponentUsed, prettyDates));
1194             pw.printPair("totalTimeFS",
1195                     formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates));
1196             pw.printPair("lastTimeFS",
1197                     formatDateTime(usageStats.mLastTimeForegroundServiceUsed, prettyDates));
1198             pw.printPair("appLaunchCount", usageStats.mAppLaunchCount);
1199             pw.println();
1200         }
1201         pw.decreaseIndent();
1202 
1203         pw.println();
1204         pw.println("ChooserCounts");
1205         pw.increaseIndent();
1206         for (UsageStats usageStats : pkgStats.values()) {
1207             if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(usageStats.mPackageName)) {
1208                 continue;
1209             }
1210             pw.printPair("package", usageStats.mPackageName);
1211             if (usageStats.mChooserCounts != null) {
1212                 final int chooserCountSize = usageStats.mChooserCounts.size();
1213                 for (int i = 0; i < chooserCountSize; i++) {
1214                     final String action = usageStats.mChooserCounts.keyAt(i);
1215                     final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i);
1216                     final int annotationSize = counts.size();
1217                     for (int j = 0; j < annotationSize; j++) {
1218                         final String key = counts.keyAt(j);
1219                         final int count = counts.valueAt(j);
1220                         if (count != 0) {
1221                             pw.printPair("ChooserCounts", action + ":" + key + " is " +
1222                                     Integer.toString(count));
1223                             pw.println();
1224                         }
1225                     }
1226                 }
1227             }
1228             pw.println();
1229         }
1230         pw.decreaseIndent();
1231 
1232         if (CollectionUtils.isEmpty(pkgs)) {
1233             pw.println("configurations");
1234             pw.increaseIndent();
1235             final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
1236             final int configCount = configStats.size();
1237             for (int i = 0; i < configCount; i++) {
1238                 final ConfigurationStats config = configStats.valueAt(i);
1239                 pw.printPair("config", Configuration.resourceQualifierString(
1240                         config.mConfiguration));
1241                 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
1242                 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
1243                 pw.printPair("count", config.mActivationCount);
1244                 pw.println();
1245             }
1246             pw.decreaseIndent();
1247             pw.println("event aggregations");
1248             pw.increaseIndent();
1249             printEventAggregation(pw, "screen-interactive", stats.interactiveTracker,
1250                     prettyDates);
1251             printEventAggregation(pw, "screen-non-interactive", stats.nonInteractiveTracker,
1252                     prettyDates);
1253             printEventAggregation(pw, "keyguard-shown", stats.keyguardShownTracker,
1254                     prettyDates);
1255             printEventAggregation(pw, "keyguard-hidden", stats.keyguardHiddenTracker,
1256                     prettyDates);
1257             pw.decreaseIndent();
1258         }
1259 
1260         // The last 24 hours of events is already printed in the non checkin dump
1261         // No need to repeat here.
1262         if (!skipEvents) {
1263             pw.println("events");
1264             pw.increaseIndent();
1265             final EventList events = stats.events;
1266             final int eventCount = events != null ? events.size() : 0;
1267             for (int i = 0; i < eventCount; i++) {
1268                 final Event event = events.get(i);
1269                 if (!CollectionUtils.isEmpty(pkgs) && !pkgs.contains(event.mPackage)) {
1270                     continue;
1271                 }
1272                 printEvent(pw, event, prettyDates);
1273             }
1274             pw.decreaseIndent();
1275         }
1276         pw.decreaseIndent();
1277     }
1278 
intervalToString(int interval)1279     public static String intervalToString(int interval) {
1280         switch (interval) {
1281             case INTERVAL_DAILY:
1282                 return "daily";
1283             case INTERVAL_WEEKLY:
1284                 return "weekly";
1285             case INTERVAL_MONTHLY:
1286                 return "monthly";
1287             case INTERVAL_YEARLY:
1288                 return "yearly";
1289             default:
1290                 return "?";
1291         }
1292     }
1293 
stringToInterval(String interval)1294     private static int stringToInterval(String interval) {
1295         switch (interval.toLowerCase()) {
1296             case "daily":
1297                 return INTERVAL_DAILY;
1298             case "weekly":
1299                 return INTERVAL_WEEKLY;
1300             case "monthly":
1301                 return INTERVAL_MONTHLY;
1302             case "yearly":
1303                 return INTERVAL_YEARLY;
1304             default:
1305                 return -1;
1306         }
1307     }
1308 
eventToString(int eventType)1309     private static String eventToString(int eventType) {
1310         switch (eventType) {
1311             case Event.NONE:
1312                 return "NONE";
1313             case Event.ACTIVITY_PAUSED:
1314                 return "ACTIVITY_PAUSED";
1315             case Event.ACTIVITY_RESUMED:
1316                 return "ACTIVITY_RESUMED";
1317             case Event.FOREGROUND_SERVICE_START:
1318                 return "FOREGROUND_SERVICE_START";
1319             case Event.FOREGROUND_SERVICE_STOP:
1320                 return "FOREGROUND_SERVICE_STOP";
1321             case Event.ACTIVITY_STOPPED:
1322                 return "ACTIVITY_STOPPED";
1323             case Event.END_OF_DAY:
1324                 return "END_OF_DAY";
1325             case Event.ROLLOVER_FOREGROUND_SERVICE:
1326                 return "ROLLOVER_FOREGROUND_SERVICE";
1327             case Event.CONTINUE_PREVIOUS_DAY:
1328                 return "CONTINUE_PREVIOUS_DAY";
1329             case Event.CONTINUING_FOREGROUND_SERVICE:
1330                 return "CONTINUING_FOREGROUND_SERVICE";
1331             case Event.CONFIGURATION_CHANGE:
1332                 return "CONFIGURATION_CHANGE";
1333             case Event.SYSTEM_INTERACTION:
1334                 return "SYSTEM_INTERACTION";
1335             case Event.USER_INTERACTION:
1336                 return "USER_INTERACTION";
1337             case Event.SHORTCUT_INVOCATION:
1338                 return "SHORTCUT_INVOCATION";
1339             case Event.CHOOSER_ACTION:
1340                 return "CHOOSER_ACTION";
1341             case Event.NOTIFICATION_SEEN:
1342                 return "NOTIFICATION_SEEN";
1343             case Event.STANDBY_BUCKET_CHANGED:
1344                 return "STANDBY_BUCKET_CHANGED";
1345             case Event.NOTIFICATION_INTERRUPTION:
1346                 return "NOTIFICATION_INTERRUPTION";
1347             case Event.SLICE_PINNED:
1348                 return "SLICE_PINNED";
1349             case Event.SLICE_PINNED_PRIV:
1350                 return "SLICE_PINNED_PRIV";
1351             case Event.SCREEN_INTERACTIVE:
1352                 return "SCREEN_INTERACTIVE";
1353             case Event.SCREEN_NON_INTERACTIVE:
1354                 return "SCREEN_NON_INTERACTIVE";
1355             case Event.KEYGUARD_SHOWN:
1356                 return "KEYGUARD_SHOWN";
1357             case Event.KEYGUARD_HIDDEN:
1358                 return "KEYGUARD_HIDDEN";
1359             case Event.DEVICE_SHUTDOWN:
1360                 return "DEVICE_SHUTDOWN";
1361             case Event.DEVICE_STARTUP:
1362                 return "DEVICE_STARTUP";
1363             case Event.USER_UNLOCKED:
1364                 return "USER_UNLOCKED";
1365             case Event.USER_STOPPED:
1366                 return "USER_STOPPED";
1367             case Event.LOCUS_ID_SET:
1368                 return "LOCUS_ID_SET";
1369             case Event.APP_COMPONENT_USED:
1370                 return "APP_COMPONENT_USED";
1371             default:
1372                 return "UNKNOWN_TYPE_" + eventType;
1373         }
1374     }
1375 
getBackupPayload(String key)1376     byte[] getBackupPayload(String key){
1377         checkAndGetTimeLocked();
1378         persistActiveStats();
1379         return mDatabase.getBackupPayload(key);
1380     }
1381 
applyRestoredPayload(String key, byte[] payload)1382     Set<String> applyRestoredPayload(String key, byte[] payload) {
1383         checkAndGetTimeLocked();
1384         return mDatabase.applyRestoredPayload(key, payload);
1385     }
1386 }
1387