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