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.UsageStatsManager.INTERVAL_BEST;
22 import static android.app.usage.UsageStatsManager.INTERVAL_COUNT;
23 import static android.app.usage.UsageStatsManager.INTERVAL_DAILY;
24 import static android.app.usage.UsageStatsManager.INTERVAL_MONTHLY;
25 import static android.app.usage.UsageStatsManager.INTERVAL_WEEKLY;
26 import static android.app.usage.UsageStatsManager.INTERVAL_YEARLY;
27 
28 import android.app.usage.ConfigurationStats;
29 import android.app.usage.EventList;
30 import android.app.usage.EventStats;
31 import android.app.usage.TimeSparseArray;
32 import android.app.usage.UsageEvents;
33 import android.app.usage.UsageEvents.Event;
34 import android.app.usage.UsageStats;
35 import android.app.usage.UsageStatsManager;
36 import android.content.Context;
37 import android.content.res.Configuration;
38 import android.os.SystemClock;
39 import android.text.format.DateUtils;
40 import android.util.ArrayMap;
41 import android.util.ArraySet;
42 import android.util.AtomicFile;
43 import android.util.Slog;
44 import android.util.SparseIntArray;
45 
46 import com.android.internal.util.IndentingPrintWriter;
47 import com.android.server.usage.UsageStatsDatabase.StatCombiner;
48 
49 import java.io.File;
50 import java.io.IOException;
51 import java.text.SimpleDateFormat;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.List;
55 
56 /**
57  * A per-user UsageStatsService. All methods are meant to be called with the main lock held
58  * in UsageStatsService.
59  */
60 class UserUsageStatsService {
61     private static final String TAG = "UsageStatsService";
62     private static final boolean DEBUG = UsageStatsService.DEBUG;
63     private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
64     private static final int sDateFormatFlags =
65             DateUtils.FORMAT_SHOW_DATE
66             | DateUtils.FORMAT_SHOW_TIME
67             | DateUtils.FORMAT_SHOW_YEAR
68             | DateUtils.FORMAT_NUMERIC_DATE;
69 
70     private final Context mContext;
71     private final UsageStatsDatabase mDatabase;
72     private final IntervalStats[] mCurrentStats;
73     private boolean mStatsChanged = false;
74     private final UnixCalendar mDailyExpiryDate;
75     private final StatsUpdatedListener mListener;
76     private final String mLogPrefix;
77     private String mLastBackgroundedPackage;
78     private final int mUserId;
79 
80     private static final long[] INTERVAL_LENGTH = new long[] {
81             UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
82             UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS
83     };
84 
85     interface StatsUpdatedListener {
onStatsUpdated()86         void onStatsUpdated();
onStatsReloaded()87         void onStatsReloaded();
88         /**
89          * Callback that a system update was detected
90          * @param mUserId user that needs to be initialized
91          */
onNewUpdate(int mUserId)92         void onNewUpdate(int mUserId);
93     }
94 
UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener)95     UserUsageStatsService(Context context, int userId, File usageStatsDir,
96             StatsUpdatedListener listener) {
97         mContext = context;
98         mDailyExpiryDate = new UnixCalendar(0);
99         mDatabase = new UsageStatsDatabase(usageStatsDir);
100         mCurrentStats = new IntervalStats[INTERVAL_COUNT];
101         mListener = listener;
102         mLogPrefix = "User[" + Integer.toString(userId) + "] ";
103         mUserId = userId;
104     }
105 
init(final long currentTimeMillis)106     void init(final long currentTimeMillis) {
107         mDatabase.init(currentTimeMillis);
108 
109         int nullCount = 0;
110         for (int i = 0; i < mCurrentStats.length; i++) {
111             mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
112             if (mCurrentStats[i] == null) {
113                 // Find out how many intervals we don't have data for.
114                 // Ideally it should be all or none.
115                 nullCount++;
116             }
117         }
118 
119         if (nullCount > 0) {
120             if (nullCount != mCurrentStats.length) {
121                 // This is weird, but we shouldn't fail if something like this
122                 // happens.
123                 Slog.w(TAG, mLogPrefix + "Some stats have no latest available");
124             } else {
125                 // This must be first boot.
126             }
127 
128             // By calling loadActiveStats, we will
129             // generate new stats for each bucket.
130             loadActiveStats(currentTimeMillis);
131         } else {
132             // Set up the expiry date to be one day from the latest daily stat.
133             // This may actually be today and we will rollover on the first event
134             // that is reported.
135             updateRolloverDeadline();
136         }
137 
138         // During system reboot, add a DEVICE_SHUTDOWN event to the end of event list, the timestamp
139         // is last time UsageStatsDatabase is persisted to disk.
140         // Also add a DEVICE_STARTUP event with current system timestamp.
141         final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
142         if (currentDailyStats != null) {
143             // File system timestamp only has precision of 1 second, add 1000ms to make up
144             // for the loss of round up.
145             final Event shutdownEvent =
146                     new Event(DEVICE_SHUTDOWN, currentDailyStats.lastTimeSaved + 1000);
147             shutdownEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
148             currentDailyStats.addEvent(shutdownEvent);
149             final Event startupEvent = new Event(DEVICE_STARTUP, currentTimeMillis);
150             startupEvent.mPackage = Event.DEVICE_EVENT_PACKAGE_NAME;
151             currentDailyStats.addEvent(startupEvent);
152         }
153 
154         if (mDatabase.isNewUpdate()) {
155             notifyNewUpdate();
156         }
157     }
158 
onTimeChanged(long oldTime, long newTime)159     void onTimeChanged(long oldTime, long newTime) {
160         persistActiveStats();
161         mDatabase.onTimeChanged(newTime - oldTime);
162         loadActiveStats(newTime);
163     }
164 
reportEvent(Event event)165     void reportEvent(Event event) {
166         if (DEBUG) {
167             Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
168                     + "[" + event.mTimeStamp + "]: "
169                     + eventToString(event.mEventType));
170         }
171 
172         if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
173             // Need to rollover
174             rolloverStats(event.mTimeStamp);
175         }
176 
177         final IntervalStats currentDailyStats = mCurrentStats[INTERVAL_DAILY];
178 
179         final Configuration newFullConfig = event.mConfiguration;
180         if (event.mEventType == Event.CONFIGURATION_CHANGE
181                 && currentDailyStats.activeConfiguration != null) {
182             // Make the event configuration a delta.
183             event.mConfiguration = Configuration.generateDelta(
184                     currentDailyStats.activeConfiguration, newFullConfig);
185         }
186 
187         if (event.mEventType != Event.SYSTEM_INTERACTION
188                 // ACTIVITY_DESTROYED is a private event. If there is preceding ACTIVITY_STOPPED
189                 // ACTIVITY_DESTROYED will be dropped. Otherwise it will be converted to
190                 // ACTIVITY_STOPPED.
191                 && event.mEventType != Event.ACTIVITY_DESTROYED
192                 // FLUSH_TO_DISK is a private event.
193                 && event.mEventType != Event.FLUSH_TO_DISK
194                 // DEVICE_SHUTDOWN is added to event list after reboot.
195                 && event.mEventType != Event.DEVICE_SHUTDOWN) {
196             currentDailyStats.addEvent(event);
197         }
198 
199         boolean incrementAppLaunch = false;
200         if (event.mEventType == Event.ACTIVITY_RESUMED) {
201             if (event.mPackage != null && !event.mPackage.equals(mLastBackgroundedPackage)) {
202                 incrementAppLaunch = true;
203             }
204         } else if (event.mEventType == Event.ACTIVITY_PAUSED) {
205             if (event.mPackage != null) {
206                 mLastBackgroundedPackage = event.mPackage;
207             }
208         }
209 
210         for (IntervalStats stats : mCurrentStats) {
211             switch (event.mEventType) {
212                 case Event.CONFIGURATION_CHANGE: {
213                     stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
214                 } break;
215                 case Event.CHOOSER_ACTION: {
216                     stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction);
217                     String[] annotations = event.mContentAnnotations;
218                     if (annotations != null) {
219                         for (String annotation : annotations) {
220                             stats.updateChooserCounts(event.mPackage, annotation, event.mAction);
221                         }
222                     }
223                 } break;
224                 case Event.SCREEN_INTERACTIVE: {
225                     stats.updateScreenInteractive(event.mTimeStamp);
226                 } break;
227                 case Event.SCREEN_NON_INTERACTIVE: {
228                     stats.updateScreenNonInteractive(event.mTimeStamp);
229                 } break;
230                 case Event.KEYGUARD_SHOWN: {
231                     stats.updateKeyguardShown(event.mTimeStamp);
232                 } break;
233                 case Event.KEYGUARD_HIDDEN: {
234                     stats.updateKeyguardHidden(event.mTimeStamp);
235                 } break;
236                 default: {
237                     stats.update(event.mPackage, event.getClassName(),
238                             event.mTimeStamp, event.mEventType, event.mInstanceId);
239                     if (incrementAppLaunch) {
240                         stats.incrementAppLaunchCount(event.mPackage);
241                     }
242                 } break;
243             }
244         }
245 
246         notifyStatsChanged();
247     }
248 
249     private static final StatCombiner<UsageStats> sUsageStatsCombiner =
250             new StatCombiner<UsageStats>() {
251                 @Override
252                 public void combine(IntervalStats stats, boolean mutable,
253                                     List<UsageStats> accResult) {
254                     if (!mutable) {
255                         accResult.addAll(stats.packageStats.values());
256                         return;
257                     }
258 
259                     final int statCount = stats.packageStats.size();
260                     for (int i = 0; i < statCount; i++) {
261                         accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
262                     }
263                 }
264             };
265 
266     private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
267             new StatCombiner<ConfigurationStats>() {
268                 @Override
269                 public void combine(IntervalStats stats, boolean mutable,
270                                     List<ConfigurationStats> accResult) {
271                     if (!mutable) {
272                         accResult.addAll(stats.configurations.values());
273                         return;
274                     }
275 
276                     final int configCount = stats.configurations.size();
277                     for (int i = 0; i < configCount; i++) {
278                         accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
279                     }
280                 }
281             };
282 
283     private static final StatCombiner<EventStats> sEventStatsCombiner =
284             new StatCombiner<EventStats>() {
285                 @Override
286                 public void combine(IntervalStats stats, boolean mutable,
287                         List<EventStats> accResult) {
288                     stats.addEventStatsTo(accResult);
289                 }
290             };
291 
292     /**
293      * Generic query method that selects the appropriate IntervalStats for the specified time range
294      * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
295      * provided to select the stats to use from the IntervalStats object.
296      */
queryStats(int intervalType, final long beginTime, final long endTime, StatCombiner<T> combiner)297     private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
298             StatCombiner<T> combiner) {
299         if (intervalType == INTERVAL_BEST) {
300             intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
301             if (intervalType < 0) {
302                 // Nothing saved to disk yet, so every stat is just as equal (no rollover has
303                 // occurred.
304                 intervalType = INTERVAL_DAILY;
305             }
306         }
307 
308         if (intervalType < 0 || intervalType >= mCurrentStats.length) {
309             if (DEBUG) {
310                 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
311             }
312             return null;
313         }
314 
315         final IntervalStats currentStats = mCurrentStats[intervalType];
316 
317         if (DEBUG) {
318             Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
319                     + beginTime + " AND endTime < " + endTime);
320         }
321 
322         if (beginTime >= currentStats.endTime) {
323             if (DEBUG) {
324                 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
325                         + currentStats.endTime);
326             }
327             // Nothing newer available.
328             return null;
329         }
330 
331         // Truncate the endTime to just before the in-memory stats. Then, we'll append the
332         // in-memory stats to the results (if necessary) so as to avoid writing to disk too
333         // often.
334         final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
335 
336         // Get the stats from disk.
337         List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
338                 truncatedEndTime, combiner);
339         if (DEBUG) {
340             Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
341             Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
342                     " endTime=" + currentStats.endTime);
343         }
344 
345         // Now check if the in-memory stats match the range and add them if they do.
346         if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
347             if (DEBUG) {
348                 Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
349             }
350 
351             if (results == null) {
352                 results = new ArrayList<>();
353             }
354             combiner.combine(currentStats, true, results);
355         }
356 
357         if (DEBUG) {
358             Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
359         }
360         return results;
361     }
362 
queryUsageStats(int bucketType, long beginTime, long endTime)363     List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
364         return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
365     }
366 
queryConfigurationStats(int bucketType, long beginTime, long endTime)367     List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
368         return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
369     }
370 
queryEventStats(int bucketType, long beginTime, long endTime)371     List<EventStats> queryEventStats(int bucketType, long beginTime, long endTime) {
372         return queryStats(bucketType, beginTime, endTime, sEventStatsCombiner);
373     }
374 
queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps)375     UsageEvents queryEvents(final long beginTime, final long endTime,
376                             boolean obfuscateInstantApps) {
377         final ArraySet<String> names = new ArraySet<>();
378         List<Event> results = queryStats(INTERVAL_DAILY,
379                 beginTime, endTime, new StatCombiner<Event>() {
380                     @Override
381                     public void combine(IntervalStats stats, boolean mutable,
382                             List<Event> accumulatedResult) {
383                         final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
384                         final int size = stats.events.size();
385                         for (int i = startIndex; i < size; i++) {
386                             if (stats.events.get(i).mTimeStamp >= endTime) {
387                                 return;
388                             }
389 
390                             Event event = stats.events.get(i);
391                             if (obfuscateInstantApps) {
392                                 event = event.getObfuscatedIfInstantApp();
393                             }
394                             if (event.mPackage != null) {
395                                 names.add(event.mPackage);
396                             }
397                             if (event.mClass != null) {
398                                 names.add(event.mClass);
399                             }
400                             if (event.mTaskRootPackage != null) {
401                                 names.add(event.mTaskRootPackage);
402                             }
403                             if (event.mTaskRootClass != null) {
404                                 names.add(event.mTaskRootClass);
405                             }
406                             accumulatedResult.add(event);
407                         }
408                     }
409                 });
410 
411         if (results == null || results.isEmpty()) {
412             return null;
413         }
414 
415         String[] table = names.toArray(new String[names.size()]);
416         Arrays.sort(table);
417         return new UsageEvents(results, table, true);
418     }
419 
queryEventsForPackage(final long beginTime, final long endTime, final String packageName, boolean includeTaskRoot)420     UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
421             final String packageName, boolean includeTaskRoot) {
422         final ArraySet<String> names = new ArraySet<>();
423         names.add(packageName);
424         final List<Event> results = queryStats(INTERVAL_DAILY,
425                 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
426                     final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
427                     final int size = stats.events.size();
428                     for (int i = startIndex; i < size; i++) {
429                         if (stats.events.get(i).mTimeStamp >= endTime) {
430                             return;
431                         }
432 
433                         final Event event = stats.events.get(i);
434                         if (!packageName.equals(event.mPackage)) {
435                             continue;
436                         }
437                         if (event.mClass != null) {
438                             names.add(event.mClass);
439                         }
440                         if (includeTaskRoot && event.mTaskRootPackage != null) {
441                             names.add(event.mTaskRootPackage);
442                         }
443                         if (includeTaskRoot && event.mTaskRootClass != null) {
444                             names.add(event.mTaskRootClass);
445                         }
446                         accumulatedResult.add(event);
447                     }
448                 });
449 
450         if (results == null || results.isEmpty()) {
451             return null;
452         }
453 
454         final String[] table = names.toArray(new String[names.size()]);
455         Arrays.sort(table);
456         return new UsageEvents(results, table, includeTaskRoot);
457     }
458 
persistActiveStats()459     void persistActiveStats() {
460         if (mStatsChanged) {
461             Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
462             try {
463                 for (int i = 0; i < mCurrentStats.length; i++) {
464                     mDatabase.putUsageStats(i, mCurrentStats[i]);
465                 }
466                 mStatsChanged = false;
467             } catch (IOException e) {
468                 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
469             }
470         }
471     }
472 
rolloverStats(final long currentTimeMillis)473     private void rolloverStats(final long currentTimeMillis) {
474         final long startTime = SystemClock.elapsedRealtime();
475         Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
476 
477         // Finish any ongoing events with an END_OF_DAY or ROLLOVER_FOREGROUND_SERVICE event.
478         // Make a note of which components need a new CONTINUE_PREVIOUS_DAY or
479         // CONTINUING_FOREGROUND_SERVICE entry.
480         final Configuration previousConfig =
481                 mCurrentStats[INTERVAL_DAILY].activeConfiguration;
482         ArraySet<String> continuePkgs = new ArraySet<>();
483         ArrayMap<String, SparseIntArray> continueActivity =
484                 new ArrayMap<>();
485         ArrayMap<String, ArrayMap<String, Integer>> continueForegroundService =
486                 new ArrayMap<>();
487         for (IntervalStats stat : mCurrentStats) {
488             final int pkgCount = stat.packageStats.size();
489             for (int i = 0; i < pkgCount; i++) {
490                 final UsageStats pkgStats = stat.packageStats.valueAt(i);
491                 if (pkgStats.mActivities.size() > 0
492                         || !pkgStats.mForegroundServices.isEmpty()) {
493                     if (pkgStats.mActivities.size() > 0) {
494                         continueActivity.put(pkgStats.mPackageName,
495                                 pkgStats.mActivities);
496                         stat.update(pkgStats.mPackageName, null,
497                                 mDailyExpiryDate.getTimeInMillis() - 1,
498                                 Event.END_OF_DAY, 0);
499                     }
500                     if (!pkgStats.mForegroundServices.isEmpty()) {
501                         continueForegroundService.put(pkgStats.mPackageName,
502                                 pkgStats.mForegroundServices);
503                         stat.update(pkgStats.mPackageName, null,
504                                 mDailyExpiryDate.getTimeInMillis() - 1,
505                                 Event.ROLLOVER_FOREGROUND_SERVICE, 0);
506                     }
507                     continuePkgs.add(pkgStats.mPackageName);
508                     notifyStatsChanged();
509                 }
510             }
511 
512             stat.updateConfigurationStats(null,
513                     mDailyExpiryDate.getTimeInMillis() - 1);
514             stat.commitTime(mDailyExpiryDate.getTimeInMillis() - 1);
515         }
516 
517         persistActiveStats();
518         mDatabase.prune(currentTimeMillis);
519         loadActiveStats(currentTimeMillis);
520 
521         final int continueCount = continuePkgs.size();
522         for (int i = 0; i < continueCount; i++) {
523             String pkgName = continuePkgs.valueAt(i);
524             final long beginTime = mCurrentStats[INTERVAL_DAILY].beginTime;
525             for (IntervalStats stat : mCurrentStats) {
526                 if (continueActivity.containsKey(pkgName)) {
527                     final SparseIntArray eventMap =
528                             continueActivity.get(pkgName);
529                     final int size = eventMap.size();
530                     for (int j = 0; j < size; j++) {
531                         stat.update(pkgName, null, beginTime,
532                                 eventMap.valueAt(j), eventMap.keyAt(j));
533                     }
534                 }
535                 if (continueForegroundService.containsKey(pkgName)) {
536                     final ArrayMap<String, Integer> eventMap =
537                             continueForegroundService.get(pkgName);
538                     final int size = eventMap.size();
539                     for (int j = 0; j < size; j++) {
540                         stat.update(pkgName, eventMap.keyAt(j), beginTime,
541                                 eventMap.valueAt(j), 0);
542                     }
543                 }
544                 stat.updateConfigurationStats(previousConfig, beginTime);
545                 notifyStatsChanged();
546             }
547         }
548         persistActiveStats();
549 
550         final long totalTime = SystemClock.elapsedRealtime() - startTime;
551         Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
552                 + " milliseconds");
553     }
554 
notifyStatsChanged()555     private void notifyStatsChanged() {
556         if (!mStatsChanged) {
557             mStatsChanged = true;
558             mListener.onStatsUpdated();
559         }
560     }
561 
notifyNewUpdate()562     private void notifyNewUpdate() {
563         mListener.onNewUpdate(mUserId);
564     }
565 
loadActiveStats(final long currentTimeMillis)566     private void loadActiveStats(final long currentTimeMillis) {
567         for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
568             final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
569             if (stats != null
570                     && currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
571                 if (DEBUG) {
572                     Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
573                             sDateFormat.format(stats.beginTime) + "(" + stats.beginTime +
574                             ") for interval " + intervalType);
575                 }
576                 mCurrentStats[intervalType] = stats;
577             } else {
578                 // No good fit remains.
579                 if (DEBUG) {
580                     Slog.d(TAG, "Creating new stats @ " +
581                             sDateFormat.format(currentTimeMillis) + "(" +
582                             currentTimeMillis + ") for interval " + intervalType);
583                 }
584 
585                 mCurrentStats[intervalType] = new IntervalStats();
586                 mCurrentStats[intervalType].beginTime = currentTimeMillis;
587                 mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
588             }
589         }
590 
591         mStatsChanged = false;
592         updateRolloverDeadline();
593 
594         // Tell the listener that the stats reloaded, which may have changed idle states.
595         mListener.onStatsReloaded();
596     }
597 
updateRolloverDeadline()598     private void updateRolloverDeadline() {
599         mDailyExpiryDate.setTimeInMillis(
600                 mCurrentStats[INTERVAL_DAILY].beginTime);
601         mDailyExpiryDate.addDays(1);
602         Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
603                 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
604                 mDailyExpiryDate.getTimeInMillis() + ")");
605     }
606 
607     //
608     // -- DUMP related methods --
609     //
610 
checkin(final IndentingPrintWriter pw)611     void checkin(final IndentingPrintWriter pw) {
612         mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
613             @Override
614             public boolean checkin(IntervalStats stats) {
615                 printIntervalStats(pw, stats, false, false, null);
616                 return true;
617             }
618         });
619     }
620 
dump(IndentingPrintWriter pw, String pkg)621     void dump(IndentingPrintWriter pw, String pkg) {
622         dump(pw, pkg, false);
623     }
dump(IndentingPrintWriter pw, String pkg, boolean compact)624     void dump(IndentingPrintWriter pw, String pkg, boolean compact) {
625         printLast24HrEvents(pw, !compact, pkg);
626         for (int interval = 0; interval < mCurrentStats.length; interval++) {
627             pw.print("In-memory ");
628             pw.print(intervalToString(interval));
629             pw.println(" stats");
630             printIntervalStats(pw, mCurrentStats[interval], !compact, true, pkg);
631         }
632         mDatabase.dump(pw, compact);
633     }
634 
dumpDatabaseInfo(IndentingPrintWriter ipw)635     void dumpDatabaseInfo(IndentingPrintWriter ipw) {
636         mDatabase.dump(ipw, false);
637     }
638 
dumpFile(IndentingPrintWriter ipw, String[] args)639     void dumpFile(IndentingPrintWriter ipw, String[] args) {
640         if (args == null || args.length == 0) {
641             // dump all files for every interval for specified user
642             final int numIntervals = mDatabase.mSortedStatFiles.length;
643             for (int interval = 0; interval < numIntervals; interval++) {
644                 ipw.println("interval=" + intervalToString(interval));
645                 ipw.increaseIndent();
646                 dumpFileDetailsForInterval(ipw, interval);
647                 ipw.decreaseIndent();
648             }
649         } else {
650             final int interval;
651             try {
652                 final int intervalValue = stringToInterval(args[0]);
653                 if (intervalValue == -1) {
654                     interval = Integer.valueOf(args[0]);
655                 } else {
656                     interval = intervalValue;
657                 }
658             } catch (NumberFormatException nfe) {
659                 ipw.println("invalid interval specified.");
660                 return;
661             }
662             if (interval < 0 || interval >= mDatabase.mSortedStatFiles.length) {
663                 ipw.println("the specified interval does not exist.");
664                 return;
665             }
666             if (args.length == 1) {
667                 // dump all files in the specified interval
668                 dumpFileDetailsForInterval(ipw, interval);
669             } else {
670                 // dump details only for the specified filename
671                 final long filename;
672                 try {
673                     filename = Long.valueOf(args[1]);
674                 } catch (NumberFormatException nfe) {
675                     ipw.println("invalid filename specified.");
676                     return;
677                 }
678                 final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename);
679                 if (stats == null) {
680                     ipw.println("the specified filename does not exist.");
681                     return;
682                 }
683                 dumpFileDetails(ipw, stats, Long.valueOf(args[1]));
684             }
685         }
686     }
687 
dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval)688     private void dumpFileDetailsForInterval(IndentingPrintWriter ipw, int interval) {
689         final TimeSparseArray<AtomicFile> files = mDatabase.mSortedStatFiles[interval];
690         final int numFiles = files.size();
691         for (int i = 0; i < numFiles; i++) {
692             final long filename = files.keyAt(i);
693             final IntervalStats stats = mDatabase.readIntervalStatsForFile(interval, filename);
694             dumpFileDetails(ipw, stats, filename);
695             ipw.println();
696         }
697     }
698 
dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename)699     private void dumpFileDetails(IndentingPrintWriter ipw, IntervalStats stats, long filename) {
700         ipw.println("file=" + filename);
701         ipw.increaseIndent();
702         printIntervalStats(ipw, stats, false, false, null);
703         ipw.decreaseIndent();
704     }
705 
formatDateTime(long dateTime, boolean pretty)706     static String formatDateTime(long dateTime, boolean pretty) {
707         if (pretty) {
708             return "\"" + sDateFormat.format(dateTime)+ "\"";
709         }
710         return Long.toString(dateTime);
711     }
712 
formatElapsedTime(long elapsedTime, boolean pretty)713     private String formatElapsedTime(long elapsedTime, boolean pretty) {
714         if (pretty) {
715             return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
716         }
717         return Long.toString(elapsedTime);
718     }
719 
720 
printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates)721     void printEvent(IndentingPrintWriter pw, Event event, boolean prettyDates) {
722         pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
723         pw.printPair("type", eventToString(event.mEventType));
724         pw.printPair("package", event.mPackage);
725         if (event.mClass != null) {
726             pw.printPair("class", event.mClass);
727         }
728         if (event.mConfiguration != null) {
729             pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
730         }
731         if (event.mShortcutId != null) {
732             pw.printPair("shortcutId", event.mShortcutId);
733         }
734         if (event.mEventType == Event.STANDBY_BUCKET_CHANGED) {
735             pw.printPair("standbyBucket", event.getStandbyBucket());
736             pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason()));
737         } else if (event.mEventType == Event.ACTIVITY_RESUMED
738                 || event.mEventType == Event.ACTIVITY_PAUSED
739                 || event.mEventType == Event.ACTIVITY_STOPPED) {
740             pw.printPair("instanceId", event.getInstanceId());
741         }
742 
743         if (event.getTaskRootPackageName() != null) {
744             pw.printPair("taskRootPackage", event.getTaskRootPackageName());
745         }
746 
747         if (event.getTaskRootClassName() != null) {
748             pw.printPair("taskRootClass", event.getTaskRootClassName());
749         }
750 
751         if (event.mNotificationChannelId != null) {
752             pw.printPair("channelId", event.mNotificationChannelId);
753         }
754         pw.printHexPair("flags", event.mFlags);
755         pw.println();
756     }
757 
printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, final String pkg)758     void printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, final String pkg) {
759         final long endTime = System.currentTimeMillis();
760         UnixCalendar yesterday = new UnixCalendar(endTime);
761         yesterday.addDays(-1);
762 
763         final long beginTime = yesterday.getTimeInMillis();
764 
765         List<Event> events = queryStats(INTERVAL_DAILY,
766                 beginTime, endTime, new StatCombiner<Event>() {
767                     @Override
768                     public void combine(IntervalStats stats, boolean mutable,
769                             List<Event> accumulatedResult) {
770                         final int startIndex = stats.events.firstIndexOnOrAfter(beginTime);
771                         final int size = stats.events.size();
772                         for (int i = startIndex; i < size; i++) {
773                             if (stats.events.get(i).mTimeStamp >= endTime) {
774                                 return;
775                             }
776 
777                             Event event = stats.events.get(i);
778                             if (pkg != null && !pkg.equals(event.mPackage)) {
779                                 continue;
780                             }
781                             accumulatedResult.add(event);
782                         }
783                     }
784                 });
785 
786         pw.print("Last 24 hour events (");
787         if (prettyDates) {
788             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
789                     beginTime, endTime, sDateFormatFlags) + "\"");
790         } else {
791             pw.printPair("beginTime", beginTime);
792             pw.printPair("endTime", endTime);
793         }
794         pw.println(")");
795         if (events != null) {
796             pw.increaseIndent();
797             for (Event event : events) {
798                 printEvent(pw, event, prettyDates);
799             }
800             pw.decreaseIndent();
801         }
802     }
803 
printEventAggregation(IndentingPrintWriter pw, String label, IntervalStats.EventTracker tracker, boolean prettyDates)804     void printEventAggregation(IndentingPrintWriter pw, String label,
805             IntervalStats.EventTracker tracker, boolean prettyDates) {
806         if (tracker.count != 0 || tracker.duration != 0) {
807             pw.print(label);
808             pw.print(": ");
809             pw.print(tracker.count);
810             pw.print("x for ");
811             pw.print(formatElapsedTime(tracker.duration, prettyDates));
812             if (tracker.curStartTime != 0) {
813                 pw.print(" (now running, started at ");
814                 formatDateTime(tracker.curStartTime, prettyDates);
815                 pw.print(")");
816             }
817             pw.println();
818         }
819     }
820 
printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates, boolean skipEvents, String pkg)821     void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
822             boolean prettyDates, boolean skipEvents, String pkg) {
823         if (prettyDates) {
824             pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
825                     stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
826         } else {
827             pw.printPair("beginTime", stats.beginTime);
828             pw.printPair("endTime", stats.endTime);
829         }
830         pw.println();
831         pw.increaseIndent();
832         pw.println("packages");
833         pw.increaseIndent();
834         final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
835         final int pkgCount = pkgStats.size();
836         for (int i = 0; i < pkgCount; i++) {
837             final UsageStats usageStats = pkgStats.valueAt(i);
838             if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
839                 continue;
840             }
841             pw.printPair("package", usageStats.mPackageName);
842             pw.printPair("totalTimeUsed",
843                     formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
844             pw.printPair("lastTimeUsed", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
845             pw.printPair("totalTimeVisible",
846                     formatElapsedTime(usageStats.mTotalTimeVisible, prettyDates));
847             pw.printPair("lastTimeVisible",
848                     formatDateTime(usageStats.mLastTimeVisible, prettyDates));
849             pw.printPair("totalTimeFS",
850                     formatElapsedTime(usageStats.mTotalTimeForegroundServiceUsed, prettyDates));
851             pw.printPair("lastTimeFS",
852                     formatDateTime(usageStats.mLastTimeForegroundServiceUsed, prettyDates));
853             pw.printPair("appLaunchCount", usageStats.mAppLaunchCount);
854             pw.println();
855         }
856         pw.decreaseIndent();
857 
858         pw.println();
859         pw.println("ChooserCounts");
860         pw.increaseIndent();
861         for (UsageStats usageStats : pkgStats.values()) {
862             if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
863                 continue;
864             }
865             pw.printPair("package", usageStats.mPackageName);
866             if (usageStats.mChooserCounts != null) {
867                 final int chooserCountSize = usageStats.mChooserCounts.size();
868                 for (int i = 0; i < chooserCountSize; i++) {
869                     final String action = usageStats.mChooserCounts.keyAt(i);
870                     final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i);
871                     final int annotationSize = counts.size();
872                     for (int j = 0; j < annotationSize; j++) {
873                         final String key = counts.keyAt(j);
874                         final int count = counts.valueAt(j);
875                         if (count != 0) {
876                             pw.printPair("ChooserCounts", action + ":" + key + " is " +
877                                     Integer.toString(count));
878                             pw.println();
879                         }
880                     }
881                 }
882             }
883             pw.println();
884         }
885         pw.decreaseIndent();
886 
887         if (pkg == null) {
888             pw.println("configurations");
889             pw.increaseIndent();
890             final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
891             final int configCount = configStats.size();
892             for (int i = 0; i < configCount; i++) {
893                 final ConfigurationStats config = configStats.valueAt(i);
894                 pw.printPair("config", Configuration.resourceQualifierString(
895                         config.mConfiguration));
896                 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
897                 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
898                 pw.printPair("count", config.mActivationCount);
899                 pw.println();
900             }
901             pw.decreaseIndent();
902             pw.println("event aggregations");
903             pw.increaseIndent();
904             printEventAggregation(pw, "screen-interactive", stats.interactiveTracker,
905                     prettyDates);
906             printEventAggregation(pw, "screen-non-interactive", stats.nonInteractiveTracker,
907                     prettyDates);
908             printEventAggregation(pw, "keyguard-shown", stats.keyguardShownTracker,
909                     prettyDates);
910             printEventAggregation(pw, "keyguard-hidden", stats.keyguardHiddenTracker,
911                     prettyDates);
912             pw.decreaseIndent();
913         }
914 
915         // The last 24 hours of events is already printed in the non checkin dump
916         // No need to repeat here.
917         if (!skipEvents) {
918             pw.println("events");
919             pw.increaseIndent();
920             final EventList events = stats.events;
921             final int eventCount = events != null ? events.size() : 0;
922             for (int i = 0; i < eventCount; i++) {
923                 final Event event = events.get(i);
924                 if (pkg != null && !pkg.equals(event.mPackage)) {
925                     continue;
926                 }
927                 printEvent(pw, event, prettyDates);
928             }
929             pw.decreaseIndent();
930         }
931         pw.decreaseIndent();
932     }
933 
intervalToString(int interval)934     public static String intervalToString(int interval) {
935         switch (interval) {
936             case INTERVAL_DAILY:
937                 return "daily";
938             case INTERVAL_WEEKLY:
939                 return "weekly";
940             case INTERVAL_MONTHLY:
941                 return "monthly";
942             case INTERVAL_YEARLY:
943                 return "yearly";
944             default:
945                 return "?";
946         }
947     }
948 
stringToInterval(String interval)949     private static int stringToInterval(String interval) {
950         switch (interval.toLowerCase()) {
951             case "daily":
952                 return INTERVAL_DAILY;
953             case "weekly":
954                 return INTERVAL_WEEKLY;
955             case "monthly":
956                 return INTERVAL_MONTHLY;
957             case "yearly":
958                 return INTERVAL_YEARLY;
959             default:
960                 return -1;
961         }
962     }
963 
eventToString(int eventType)964     private static String eventToString(int eventType) {
965         switch (eventType) {
966             case Event.NONE:
967                 return "NONE";
968             case Event.ACTIVITY_PAUSED:
969                 return "ACTIVITY_PAUSED";
970             case Event.ACTIVITY_RESUMED:
971                 return "ACTIVITY_RESUMED";
972             case Event.FOREGROUND_SERVICE_START:
973                 return "FOREGROUND_SERVICE_START";
974             case Event.FOREGROUND_SERVICE_STOP:
975                 return "FOREGROUND_SERVICE_STOP";
976             case Event.ACTIVITY_STOPPED:
977                 return "ACTIVITY_STOPPED";
978             case Event.END_OF_DAY:
979                 return "END_OF_DAY";
980             case Event.ROLLOVER_FOREGROUND_SERVICE:
981                 return "ROLLOVER_FOREGROUND_SERVICE";
982             case Event.CONTINUE_PREVIOUS_DAY:
983                 return "CONTINUE_PREVIOUS_DAY";
984             case Event.CONTINUING_FOREGROUND_SERVICE:
985                 return "CONTINUING_FOREGROUND_SERVICE";
986             case Event.CONFIGURATION_CHANGE:
987                 return "CONFIGURATION_CHANGE";
988             case Event.SYSTEM_INTERACTION:
989                 return "SYSTEM_INTERACTION";
990             case Event.USER_INTERACTION:
991                 return "USER_INTERACTION";
992             case Event.SHORTCUT_INVOCATION:
993                 return "SHORTCUT_INVOCATION";
994             case Event.CHOOSER_ACTION:
995                 return "CHOOSER_ACTION";
996             case Event.NOTIFICATION_SEEN:
997                 return "NOTIFICATION_SEEN";
998             case Event.STANDBY_BUCKET_CHANGED:
999                 return "STANDBY_BUCKET_CHANGED";
1000             case Event.NOTIFICATION_INTERRUPTION:
1001                 return "NOTIFICATION_INTERRUPTION";
1002             case Event.SLICE_PINNED:
1003                 return "SLICE_PINNED";
1004             case Event.SLICE_PINNED_PRIV:
1005                 return "SLICE_PINNED_PRIV";
1006             case Event.SCREEN_INTERACTIVE:
1007                 return "SCREEN_INTERACTIVE";
1008             case Event.SCREEN_NON_INTERACTIVE:
1009                 return "SCREEN_NON_INTERACTIVE";
1010             case Event.KEYGUARD_SHOWN:
1011                 return "KEYGUARD_SHOWN";
1012             case Event.KEYGUARD_HIDDEN:
1013                 return "KEYGUARD_HIDDEN";
1014             case Event.DEVICE_SHUTDOWN:
1015                 return "DEVICE_SHUTDOWN";
1016             case Event.DEVICE_STARTUP:
1017                 return "DEVICE_STARTUP";
1018             default:
1019                 return "UNKNOWN_TYPE_" + eventType;
1020         }
1021     }
1022 
getBackupPayload(String key)1023     byte[] getBackupPayload(String key){
1024         return mDatabase.getBackupPayload(key);
1025     }
1026 
applyRestoredPayload(String key, byte[] payload)1027     void applyRestoredPayload(String key, byte[] payload){
1028         mDatabase.applyRestoredPayload(key, payload);
1029     }
1030 }
1031