blob: 836eb318e4dc6f0139b19793f7f7eb85d5d37974 [file] [log] [blame]
Adam Lesinski35168002014-07-21 15:25:30 -07001/**
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
Adam Lesinski3c153512014-07-23 17:34:34 -070017package com.android.server.usage;
18
Adam Lesinski7f61e962014-09-02 16:43:52 -070019import android.app.usage.ConfigurationStats;
Adam Lesinski35168002014-07-21 15:25:30 -070020import android.app.usage.TimeSparseArray;
21import android.app.usage.UsageEvents;
Adam Lesinski3c153512014-07-23 17:34:34 -070022import android.app.usage.UsageStats;
23import android.app.usage.UsageStatsManager;
Adam Lesinski7f61e962014-09-02 16:43:52 -070024import android.content.res.Configuration;
Adam Lesinski66143fa2014-09-11 08:31:05 -070025import android.os.SystemClock;
Adam Lesinski1bb18c42014-08-18 12:21:34 -070026import android.content.Context;
Michael Wachenschwanz06e49402018-02-20 23:39:09 -080027import android.text.format.DateFormat;
Adam Lesinski1bb18c42014-08-18 12:21:34 -070028import android.text.format.DateUtils;
29import android.util.ArrayMap;
Adam Lesinski3c153512014-07-23 17:34:34 -070030import android.util.ArraySet;
31import android.util.Slog;
32
Adam Lesinski1bb18c42014-08-18 12:21:34 -070033import com.android.internal.util.IndentingPrintWriter;
Adam Lesinski7f61e962014-09-02 16:43:52 -070034import com.android.server.usage.UsageStatsDatabase.StatCombiner;
35
Adam Lesinski3c153512014-07-23 17:34:34 -070036import java.io.File;
37import java.io.IOException;
38import java.text.SimpleDateFormat;
Adam Lesinski35168002014-07-21 15:25:30 -070039import java.util.ArrayList;
40import java.util.Arrays;
Adam Lesinski35168002014-07-21 15:25:30 -070041import java.util.List;
Adam Lesinski3c153512014-07-23 17:34:34 -070042
43/**
44 * A per-user UsageStatsService. All methods are meant to be called with the main lock held
45 * in UsageStatsService.
46 */
47class UserUsageStatsService {
48 private static final String TAG = "UsageStatsService";
49 private static final boolean DEBUG = UsageStatsService.DEBUG;
Michael Wachenschwanz1088cbb2018-03-01 12:45:16 -080050 private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Adam Lesinski1bb18c42014-08-18 12:21:34 -070051 private static final int sDateFormatFlags =
52 DateUtils.FORMAT_SHOW_DATE
53 | DateUtils.FORMAT_SHOW_TIME
54 | DateUtils.FORMAT_SHOW_YEAR
55 | DateUtils.FORMAT_NUMERIC_DATE;
Adam Lesinski3c153512014-07-23 17:34:34 -070056
Adam Lesinski1bb18c42014-08-18 12:21:34 -070057 private final Context mContext;
Adam Lesinski3c153512014-07-23 17:34:34 -070058 private final UsageStatsDatabase mDatabase;
Adam Lesinski35168002014-07-21 15:25:30 -070059 private final IntervalStats[] mCurrentStats;
Adam Lesinski3c153512014-07-23 17:34:34 -070060 private boolean mStatsChanged = false;
Adam Lesinskid26bea32014-09-03 16:49:59 -070061 private final UnixCalendar mDailyExpiryDate;
Adam Lesinski3c153512014-07-23 17:34:34 -070062 private final StatsUpdatedListener mListener;
63 private final String mLogPrefix;
Amith Yamasani55717a62015-04-03 17:22:36 -070064 private final int mUserId;
Adam Lesinski3c153512014-07-23 17:34:34 -070065
Adam Lesinski7cba1d42015-08-04 16:17:37 -070066 private static final long[] INTERVAL_LENGTH = new long[] {
67 UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
68 UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS
69 };
70
Adam Lesinski3c153512014-07-23 17:34:34 -070071 interface StatsUpdatedListener {
72 void onStatsUpdated();
Adam Lesinskib2d3ffa2016-01-26 18:18:19 -080073 void onStatsReloaded();
Amith Yamasania93542f2016-02-03 18:02:06 -080074 /**
75 * Callback that a system update was detected
76 * @param mUserId user that needs to be initialized
77 */
78 void onNewUpdate(int mUserId);
Adam Lesinski3c153512014-07-23 17:34:34 -070079 }
80
Amith Yamasanib0ff3222015-03-04 09:56:14 -080081 UserUsageStatsService(Context context, int userId, File usageStatsDir,
82 StatsUpdatedListener listener) {
Adam Lesinski1bb18c42014-08-18 12:21:34 -070083 mContext = context;
Adam Lesinskid26bea32014-09-03 16:49:59 -070084 mDailyExpiryDate = new UnixCalendar(0);
Adam Lesinski3c153512014-07-23 17:34:34 -070085 mDatabase = new UsageStatsDatabase(usageStatsDir);
Adam Lesinski35168002014-07-21 15:25:30 -070086 mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
Adam Lesinski3c153512014-07-23 17:34:34 -070087 mListener = listener;
88 mLogPrefix = "User[" + Integer.toString(userId) + "] ";
Amith Yamasani55717a62015-04-03 17:22:36 -070089 mUserId = userId;
Adam Lesinski3c153512014-07-23 17:34:34 -070090 }
91
Amith Yamasania93542f2016-02-03 18:02:06 -080092 void init(final long currentTimeMillis) {
Adam Lesinski66143fa2014-09-11 08:31:05 -070093 mDatabase.init(currentTimeMillis);
Adam Lesinski3c153512014-07-23 17:34:34 -070094
95 int nullCount = 0;
96 for (int i = 0; i < mCurrentStats.length; i++) {
97 mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
98 if (mCurrentStats[i] == null) {
Adam Lesinski35168002014-07-21 15:25:30 -070099 // Find out how many intervals we don't have data for.
100 // Ideally it should be all or none.
Adam Lesinski3c153512014-07-23 17:34:34 -0700101 nullCount++;
102 }
103 }
104
105 if (nullCount > 0) {
106 if (nullCount != mCurrentStats.length) {
107 // This is weird, but we shouldn't fail if something like this
108 // happens.
109 Slog.w(TAG, mLogPrefix + "Some stats have no latest available");
110 } else {
111 // This must be first boot.
112 }
113
114 // By calling loadActiveStats, we will
115 // generate new stats for each bucket.
Amith Yamasania93542f2016-02-03 18:02:06 -0800116 loadActiveStats(currentTimeMillis);
Adam Lesinski3c153512014-07-23 17:34:34 -0700117 } else {
118 // Set up the expiry date to be one day from the latest daily stat.
119 // This may actually be today and we will rollover on the first event
120 // that is reported.
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700121 updateRolloverDeadline();
Adam Lesinski3c153512014-07-23 17:34:34 -0700122 }
123
124 // Now close off any events that were open at the time this was saved.
Adam Lesinski35168002014-07-21 15:25:30 -0700125 for (IntervalStats stat : mCurrentStats) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700126 final int pkgCount = stat.packageStats.size();
Adam Lesinski3c153512014-07-23 17:34:34 -0700127 for (int i = 0; i < pkgCount; i++) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700128 UsageStats pkgStats = stat.packageStats.valueAt(i);
Adam Lesinski35168002014-07-21 15:25:30 -0700129 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
130 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
131 stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
132 UsageEvents.Event.END_OF_DAY);
Adam Lesinski3c153512014-07-23 17:34:34 -0700133 notifyStatsChanged();
134 }
135 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700136
137 stat.updateConfigurationStats(null, stat.lastTimeSaved);
Adam Lesinski3c153512014-07-23 17:34:34 -0700138 }
Amith Yamasani55717a62015-04-03 17:22:36 -0700139
140 if (mDatabase.isNewUpdate()) {
Amith Yamasania93542f2016-02-03 18:02:06 -0800141 notifyNewUpdate();
Amith Yamasani55717a62015-04-03 17:22:36 -0700142 }
143 }
144
Amith Yamasania93542f2016-02-03 18:02:06 -0800145 void onTimeChanged(long oldTime, long newTime) {
Adam Lesinski66143fa2014-09-11 08:31:05 -0700146 persistActiveStats();
147 mDatabase.onTimeChanged(newTime - oldTime);
Amith Yamasania93542f2016-02-03 18:02:06 -0800148 loadActiveStats(newTime);
Adam Lesinski66143fa2014-09-11 08:31:05 -0700149 }
150
Amith Yamasania93542f2016-02-03 18:02:06 -0800151 void reportEvent(UsageEvents.Event event) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700152 if (DEBUG) {
Adam Lesinski9d960752014-08-25 14:48:12 -0700153 Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
Adam Lesinski7f61e962014-09-02 16:43:52 -0700154 + "[" + event.mTimeStamp + "]: "
155 + eventToString(event.mEventType));
Adam Lesinski3c153512014-07-23 17:34:34 -0700156 }
157
Adam Lesinski7f61e962014-09-02 16:43:52 -0700158 if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700159 // Need to rollover
Amith Yamasania93542f2016-02-03 18:02:06 -0800160 rolloverStats(event.mTimeStamp);
Adam Lesinski3c153512014-07-23 17:34:34 -0700161 }
162
Adam Lesinski7f61e962014-09-02 16:43:52 -0700163 final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
164
165 final Configuration newFullConfig = event.mConfiguration;
166 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
167 currentDailyStats.activeConfiguration != null) {
168 // Make the event configuration a delta.
169 event.mConfiguration = Configuration.generateDelta(
170 currentDailyStats.activeConfiguration, newFullConfig);
Adam Lesinski35168002014-07-21 15:25:30 -0700171 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700172
173 // Add the event to the daily list.
174 if (currentDailyStats.events == null) {
175 currentDailyStats.events = new TimeSparseArray<>();
176 }
Adam Lesinskic8e87292015-06-10 15:33:45 -0700177 if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) {
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800178 currentDailyStats.events.put(event.mTimeStamp, event);
179 }
Adam Lesinski35168002014-07-21 15:25:30 -0700180
181 for (IntervalStats stats : mCurrentStats) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700182 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
183 stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
Kang Li53b43142016-11-14 14:38:25 -0800184 } else if (event.mEventType == UsageEvents.Event.CHOOSER_ACTION) {
185 stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction);
186 String[] annotations = event.mContentAnnotations;
187 if (annotations != null) {
188 for (String annotation : annotations) {
Kang Li53b43142016-11-14 14:38:25 -0800189 stats.updateChooserCounts(event.mPackage, annotation, event.mAction);
190 }
191 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700192 } else {
193 stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
194 }
Adam Lesinski3c153512014-07-23 17:34:34 -0700195 }
196
Amith Yamasanicf768722015-04-23 20:36:41 -0700197 notifyStatsChanged();
198 }
199
Adam Lesinski7f61e962014-09-02 16:43:52 -0700200 private static final StatCombiner<UsageStats> sUsageStatsCombiner =
201 new StatCombiner<UsageStats>() {
202 @Override
203 public void combine(IntervalStats stats, boolean mutable,
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700204 List<UsageStats> accResult) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700205 if (!mutable) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700206 accResult.addAll(stats.packageStats.values());
Adam Lesinski7f61e962014-09-02 16:43:52 -0700207 return;
208 }
209
Adam Lesinski37a46b42014-09-05 15:38:05 -0700210 final int statCount = stats.packageStats.size();
Adam Lesinski7f61e962014-09-02 16:43:52 -0700211 for (int i = 0; i < statCount; i++) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700212 accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
Adam Lesinski7f61e962014-09-02 16:43:52 -0700213 }
214 }
215 };
216
217 private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
218 new StatCombiner<ConfigurationStats>() {
219 @Override
220 public void combine(IntervalStats stats, boolean mutable,
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700221 List<ConfigurationStats> accResult) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700222 if (!mutable) {
223 accResult.addAll(stats.configurations.values());
224 return;
225 }
226
227 final int configCount = stats.configurations.size();
228 for (int i = 0; i < configCount; i++) {
229 accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
230 }
231 }
232 };
233
234 /**
235 * Generic query method that selects the appropriate IntervalStats for the specified time range
236 * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
237 * provided to select the stats to use from the IntervalStats object.
238 */
Adam Lesinskid26bea32014-09-03 16:49:59 -0700239 private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
Adam Lesinski7f61e962014-09-02 16:43:52 -0700240 StatCombiner<T> combiner) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700241 if (intervalType == UsageStatsManager.INTERVAL_BEST) {
242 intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
243 if (intervalType < 0) {
244 // Nothing saved to disk yet, so every stat is just as equal (no rollover has
245 // occurred.
246 intervalType = UsageStatsManager.INTERVAL_DAILY;
247 }
Adam Lesinski35168002014-07-21 15:25:30 -0700248 }
249
Adam Lesinskid26bea32014-09-03 16:49:59 -0700250 if (intervalType < 0 || intervalType >= mCurrentStats.length) {
Adam Lesinski35168002014-07-21 15:25:30 -0700251 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700252 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
Adam Lesinski35168002014-07-21 15:25:30 -0700253 }
254 return null;
255 }
256
Adam Lesinskid26bea32014-09-03 16:49:59 -0700257 final IntervalStats currentStats = mCurrentStats[intervalType];
Adam Lesinski35168002014-07-21 15:25:30 -0700258
Adam Lesinski3c153512014-07-23 17:34:34 -0700259 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700260 Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
Adam Lesinski35168002014-07-21 15:25:30 -0700261 + beginTime + " AND endTime < " + endTime);
Adam Lesinski3c153512014-07-23 17:34:34 -0700262 }
263
Adam Lesinskid26bea32014-09-03 16:49:59 -0700264 if (beginTime >= currentStats.endTime) {
265 if (DEBUG) {
266 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
267 + currentStats.endTime);
268 }
269 // Nothing newer available.
270 return null;
271 }
272
273 // Truncate the endTime to just before the in-memory stats. Then, we'll append the
274 // in-memory stats to the results (if necessary) so as to avoid writing to disk too
275 // often.
276 final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
277
278 // Get the stats from disk.
279 List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
280 truncatedEndTime, combiner);
Adam Lesinski3c153512014-07-23 17:34:34 -0700281 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700282 Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
283 Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
284 " endTime=" + currentStats.endTime);
285 }
286
287 // Now check if the in-memory stats match the range and add them if they do.
288 if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
289 if (DEBUG) {
290 Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
291 }
292
293 if (results == null) {
294 results = new ArrayList<>();
295 }
296 combiner.combine(currentStats, true, results);
297 }
298
299 if (DEBUG) {
300 Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
Adam Lesinski3c153512014-07-23 17:34:34 -0700301 }
302 return results;
303 }
304
Adam Lesinski7f61e962014-09-02 16:43:52 -0700305 List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
306 return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
307 }
308
309 List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
310 return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
311 }
312
Makoto Onukiad623012017-05-15 09:29:34 -0700313 UsageEvents queryEvents(final long beginTime, final long endTime,
314 boolean obfuscateInstantApps) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700315 final ArraySet<String> names = new ArraySet<>();
316 List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
317 beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
318 @Override
319 public void combine(IntervalStats stats, boolean mutable,
320 List<UsageEvents.Event> accumulatedResult) {
321 if (stats.events == null) {
322 return;
323 }
Adam Lesinski35168002014-07-21 15:25:30 -0700324
Adam Lesinskid26bea32014-09-03 16:49:59 -0700325 final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
326 if (startIndex < 0) {
327 return;
328 }
Adam Lesinski35168002014-07-21 15:25:30 -0700329
Adam Lesinskid26bea32014-09-03 16:49:59 -0700330 final int size = stats.events.size();
331 for (int i = startIndex; i < size; i++) {
332 if (stats.events.keyAt(i) >= endTime) {
333 return;
334 }
Adam Lesinski35168002014-07-21 15:25:30 -0700335
Makoto Onukiad623012017-05-15 09:29:34 -0700336 UsageEvents.Event event = stats.events.valueAt(i);
337 if (obfuscateInstantApps) {
338 event = event.getObfuscatedIfInstantApp();
339 }
Adam Lesinskid26bea32014-09-03 16:49:59 -0700340 names.add(event.mPackage);
341 if (event.mClass != null) {
342 names.add(event.mClass);
343 }
344 accumulatedResult.add(event);
345 }
346 }
347 });
348
349 if (results == null || results.isEmpty()) {
350 return null;
Adam Lesinski35168002014-07-21 15:25:30 -0700351 }
352
Adam Lesinskid26bea32014-09-03 16:49:59 -0700353 String[] table = names.toArray(new String[names.size()]);
354 Arrays.sort(table);
355 return new UsageEvents(results, table);
Adam Lesinski35168002014-07-21 15:25:30 -0700356 }
357
Suprabh Shukla217ccda2018-02-23 17:57:12 -0800358 UsageEvents queryEventsForPackage(final long beginTime, final long endTime,
359 final String packageName) {
360 final ArraySet<String> names = new ArraySet<>();
361 names.add(packageName);
362 final List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
363 beginTime, endTime, (stats, mutable, accumulatedResult) -> {
364 if (stats.events == null) {
365 return;
366 }
367
368 final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
369 if (startIndex < 0) {
370 return;
371 }
372
373 final int size = stats.events.size();
374 for (int i = startIndex; i < size; i++) {
375 if (stats.events.keyAt(i) >= endTime) {
376 return;
377 }
378
379 final UsageEvents.Event event = stats.events.valueAt(i);
380 if (!packageName.equals(event.mPackage)) {
381 continue;
382 }
383 if (event.mClass != null) {
384 names.add(event.mClass);
385 }
386 accumulatedResult.add(event);
387 }
388 });
389
390 if (results == null || results.isEmpty()) {
391 return null;
392 }
393
394 final String[] table = names.toArray(new String[names.size()]);
395 Arrays.sort(table);
396 return new UsageEvents(results, table);
397 }
398
Adam Lesinski3c153512014-07-23 17:34:34 -0700399 void persistActiveStats() {
400 if (mStatsChanged) {
401 Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
402 try {
403 for (int i = 0; i < mCurrentStats.length; i++) {
404 mDatabase.putUsageStats(i, mCurrentStats[i]);
405 }
406 mStatsChanged = false;
407 } catch (IOException e) {
408 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
409 }
410 }
411 }
412
Amith Yamasania93542f2016-02-03 18:02:06 -0800413 private void rolloverStats(final long currentTimeMillis) {
Adam Lesinski66143fa2014-09-11 08:31:05 -0700414 final long startTime = SystemClock.elapsedRealtime();
Adam Lesinski3c153512014-07-23 17:34:34 -0700415 Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
416
417 // Finish any ongoing events with an END_OF_DAY event. Make a note of which components
418 // need a new CONTINUE_PREVIOUS_DAY entry.
Adam Lesinski7f61e962014-09-02 16:43:52 -0700419 final Configuration previousConfig =
420 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
Adam Lesinski3c153512014-07-23 17:34:34 -0700421 ArraySet<String> continuePreviousDay = new ArraySet<>();
Adam Lesinski35168002014-07-21 15:25:30 -0700422 for (IntervalStats stat : mCurrentStats) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700423 final int pkgCount = stat.packageStats.size();
Adam Lesinski3c153512014-07-23 17:34:34 -0700424 for (int i = 0; i < pkgCount; i++) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700425 UsageStats pkgStats = stat.packageStats.valueAt(i);
Adam Lesinski35168002014-07-21 15:25:30 -0700426 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
427 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700428 continuePreviousDay.add(pkgStats.mPackageName);
Adam Lesinski7f61e962014-09-02 16:43:52 -0700429 stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1,
430 UsageEvents.Event.END_OF_DAY);
Adam Lesinski66143fa2014-09-11 08:31:05 -0700431 notifyStatsChanged();
Adam Lesinski3c153512014-07-23 17:34:34 -0700432 }
433 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700434
435 stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1);
Adam Lesinski3c153512014-07-23 17:34:34 -0700436 }
437
438 persistActiveStats();
Adam Lesinski66143fa2014-09-11 08:31:05 -0700439 mDatabase.prune(currentTimeMillis);
Amith Yamasania93542f2016-02-03 18:02:06 -0800440 loadActiveStats(currentTimeMillis);
Adam Lesinski3c153512014-07-23 17:34:34 -0700441
442 final int continueCount = continuePreviousDay.size();
443 for (int i = 0; i < continueCount; i++) {
444 String name = continuePreviousDay.valueAt(i);
Adam Lesinski7f61e962014-09-02 16:43:52 -0700445 final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime;
Adam Lesinski35168002014-07-21 15:25:30 -0700446 for (IntervalStats stat : mCurrentStats) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700447 stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
448 stat.updateConfigurationStats(previousConfig, beginTime);
Adam Lesinski66143fa2014-09-11 08:31:05 -0700449 notifyStatsChanged();
Adam Lesinski3c153512014-07-23 17:34:34 -0700450 }
451 }
452 persistActiveStats();
453
Adam Lesinski66143fa2014-09-11 08:31:05 -0700454 final long totalTime = SystemClock.elapsedRealtime() - startTime;
Adam Lesinski3c153512014-07-23 17:34:34 -0700455 Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
456 + " milliseconds");
457 }
458
459 private void notifyStatsChanged() {
460 if (!mStatsChanged) {
461 mStatsChanged = true;
462 mListener.onStatsUpdated();
463 }
464 }
465
Amith Yamasania93542f2016-02-03 18:02:06 -0800466 private void notifyNewUpdate() {
467 mListener.onNewUpdate(mUserId);
468 }
469
470 private void loadActiveStats(final long currentTimeMillis) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700471 for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700472 final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
473 if (stats != null && currentTimeMillis - 500 >= stats.endTime &&
474 currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
Adam Lesinski35168002014-07-21 15:25:30 -0700475 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700476 Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700477 sDateFormat.format(stats.beginTime) + "(" + stats.beginTime +
Adam Lesinskid26bea32014-09-03 16:49:59 -0700478 ") for interval " + intervalType);
Adam Lesinski35168002014-07-21 15:25:30 -0700479 }
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700480 mCurrentStats[intervalType] = stats;
Adam Lesinski3c153512014-07-23 17:34:34 -0700481 } else {
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700482 // No good fit remains.
Adam Lesinski35168002014-07-21 15:25:30 -0700483 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700484 Slog.d(TAG, "Creating new stats @ " +
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700485 sDateFormat.format(currentTimeMillis) + "(" +
486 currentTimeMillis + ") for interval " + intervalType);
Adam Lesinski35168002014-07-21 15:25:30 -0700487 }
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700488
Adam Lesinskid26bea32014-09-03 16:49:59 -0700489 mCurrentStats[intervalType] = new IntervalStats();
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700490 mCurrentStats[intervalType].beginTime = currentTimeMillis;
491 mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
Adam Lesinski3c153512014-07-23 17:34:34 -0700492 }
493 }
Adam Lesinskida4a3772016-01-07 18:24:53 -0800494
Adam Lesinski3c153512014-07-23 17:34:34 -0700495 mStatsChanged = false;
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700496 updateRolloverDeadline();
Adam Lesinskib2d3ffa2016-01-26 18:18:19 -0800497
498 // Tell the listener that the stats reloaded, which may have changed idle states.
499 mListener.onStatsReloaded();
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700500 }
501
502 private void updateRolloverDeadline() {
503 mDailyExpiryDate.setTimeInMillis(
504 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
Adam Lesinskid26bea32014-09-03 16:49:59 -0700505 mDailyExpiryDate.addDays(1);
Adam Lesinskid26bea32014-09-03 16:49:59 -0700506 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
507 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700508 mDailyExpiryDate.getTimeInMillis() + ")");
Adam Lesinski3c153512014-07-23 17:34:34 -0700509 }
510
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700511 //
512 // -- DUMP related methods --
513 //
514
Amith Yamasania93542f2016-02-03 18:02:06 -0800515 void checkin(final IndentingPrintWriter pw) {
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700516 mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
517 @Override
518 public boolean checkin(IntervalStats stats) {
Michael Wachenschwanz1088cbb2018-03-01 12:45:16 -0800519 printIntervalStats(pw, stats, false, false, null);
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700520 return true;
521 }
522 });
523 }
524
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700525 void dump(IndentingPrintWriter pw, String pkg) {
Michael Wachenschwanz1088cbb2018-03-01 12:45:16 -0800526 dump(pw, pkg, false);
527 }
528 void dump(IndentingPrintWriter pw, String pkg, boolean compact) {
529 printLast24HrEvents(pw, !compact, pkg);
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700530 for (int interval = 0; interval < mCurrentStats.length; interval++) {
531 pw.print("In-memory ");
532 pw.print(intervalToString(interval));
533 pw.println(" stats");
Michael Wachenschwanz1088cbb2018-03-01 12:45:16 -0800534 printIntervalStats(pw, mCurrentStats[interval], !compact, true, pkg);
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700535 }
536 }
537
538 private String formatDateTime(long dateTime, boolean pretty) {
539 if (pretty) {
Michael Wachenschwanz1088cbb2018-03-01 12:45:16 -0800540 return "\"" + sDateFormat.format(dateTime)+ "\"";
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700541 }
542 return Long.toString(dateTime);
543 }
544
545 private String formatElapsedTime(long elapsedTime, boolean pretty) {
546 if (pretty) {
547 return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
548 }
549 return Long.toString(elapsedTime);
550 }
551
Michael Wachenschwanz06e49402018-02-20 23:39:09 -0800552
553 void printEvent(IndentingPrintWriter pw, UsageEvents.Event event, boolean prettyDates) {
554 pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
555 pw.printPair("type", eventToString(event.mEventType));
556 pw.printPair("package", event.mPackage);
557 if (event.mClass != null) {
558 pw.printPair("class", event.mClass);
559 }
560 if (event.mConfiguration != null) {
561 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
562 }
563 if (event.mShortcutId != null) {
564 pw.printPair("shortcutId", event.mShortcutId);
565 }
566 if (event.mEventType == UsageEvents.Event.STANDBY_BUCKET_CHANGED) {
Amith Yamasani119be9a2018-02-18 22:23:00 -0800567 pw.printPair("standbyBucket", event.getStandbyBucket());
568 pw.printPair("reason", UsageStatsManager.reasonToString(event.getStandbyReason()));
Michael Wachenschwanz06e49402018-02-20 23:39:09 -0800569 }
570 pw.printHexPair("flags", event.mFlags);
571 pw.println();
572 }
573
574 void printLast24HrEvents(IndentingPrintWriter pw, boolean prettyDates, final String pkg) {
575 final long endTime = System.currentTimeMillis();
576 UnixCalendar yesterday = new UnixCalendar(endTime);
577 yesterday.addDays(-1);
578
579 final long beginTime = yesterday.getTimeInMillis();
580
581 List<UsageEvents.Event> events = queryStats(UsageStatsManager.INTERVAL_DAILY,
582 beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
583 @Override
584 public void combine(IntervalStats stats, boolean mutable,
585 List<UsageEvents.Event> accumulatedResult) {
586 if (stats.events == null) {
587 return;
588 }
589
590 final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
591 if (startIndex < 0) {
592 return;
593 }
594
595 final int size = stats.events.size();
596 for (int i = startIndex; i < size; i++) {
597 if (stats.events.keyAt(i) >= endTime) {
598 return;
599 }
600
601 UsageEvents.Event event = stats.events.valueAt(i);
602 if (pkg != null && !pkg.equals(event.mPackage)) {
603 continue;
604 }
605 accumulatedResult.add(event);
606 }
607 }
608 });
609
610 pw.print("Last 24 hour events (");
611 if (prettyDates) {
612 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
613 beginTime, endTime, sDateFormatFlags) + "\"");
614 } else {
615 pw.printPair("beginTime", beginTime);
616 pw.printPair("endTime", endTime);
617 }
618 pw.println(")");
Michael Wachenschwanz78646e52018-02-27 15:54:27 -0800619 if (events != null) {
620 pw.increaseIndent();
621 for (UsageEvents.Event event : events) {
622 printEvent(pw, event, prettyDates);
623 }
624 pw.decreaseIndent();
Michael Wachenschwanz06e49402018-02-20 23:39:09 -0800625 }
Michael Wachenschwanz06e49402018-02-20 23:39:09 -0800626 }
627
Amith Yamasania93542f2016-02-03 18:02:06 -0800628 void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats,
Michael Wachenschwanz1088cbb2018-03-01 12:45:16 -0800629 boolean prettyDates, boolean skipEvents, String pkg) {
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700630 if (prettyDates) {
631 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
632 stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
633 } else {
634 pw.printPair("beginTime", stats.beginTime);
635 pw.printPair("endTime", stats.endTime);
636 }
637 pw.println();
638 pw.increaseIndent();
639 pw.println("packages");
640 pw.increaseIndent();
641 final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
642 final int pkgCount = pkgStats.size();
643 for (int i = 0; i < pkgCount; i++) {
644 final UsageStats usageStats = pkgStats.valueAt(i);
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700645 if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
646 continue;
647 }
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700648 pw.printPair("package", usageStats.mPackageName);
Adam Lesinskic8e87292015-06-10 15:33:45 -0700649 pw.printPair("totalTime",
650 formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700651 pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
652 pw.println();
653 }
654 pw.decreaseIndent();
655
Kang Li53b43142016-11-14 14:38:25 -0800656 pw.println();
Kang Li53b43142016-11-14 14:38:25 -0800657 pw.println("ChooserCounts");
658 pw.increaseIndent();
659 for (UsageStats usageStats : pkgStats.values()) {
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700660 if (pkg != null && !pkg.equals(usageStats.mPackageName)) {
661 continue;
662 }
Kang Li53b43142016-11-14 14:38:25 -0800663 pw.printPair("package", usageStats.mPackageName);
664 if (usageStats.mChooserCounts != null) {
665 final int chooserCountSize = usageStats.mChooserCounts.size();
666 for (int i = 0; i < chooserCountSize; i++) {
667 final String action = usageStats.mChooserCounts.keyAt(i);
668 final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i);
669 final int annotationSize = counts.size();
670 for (int j = 0; j < annotationSize; j++) {
671 final String key = counts.keyAt(j);
672 final int count = counts.valueAt(j);
673 if (count != 0) {
674 pw.printPair("ChooserCounts", action + ":" + key + " is " +
675 Integer.toString(count));
676 pw.println();
677 }
678 }
679 }
680 }
681 pw.println();
682 }
Amith Yamasani61d5fd72017-02-24 11:02:07 -0800683 pw.decreaseIndent();
Kang Li53b43142016-11-14 14:38:25 -0800684
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700685 if (pkg == null) {
686 pw.println("configurations");
687 pw.increaseIndent();
688 final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
689 final int configCount = configStats.size();
690 for (int i = 0; i < configCount; i++) {
691 final ConfigurationStats config = configStats.valueAt(i);
692 pw.printPair("config", Configuration.resourceQualifierString(
693 config.mConfiguration));
694 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
695 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
696 pw.printPair("count", config.mActivationCount);
697 pw.println();
698 }
699 pw.decreaseIndent();
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700700 }
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700701
Michael Wachenschwanz06e49402018-02-20 23:39:09 -0800702 // The last 24 hours of events is already printed in the non checkin dump
703 // No need to repeat here.
Michael Wachenschwanz1088cbb2018-03-01 12:45:16 -0800704 if (!skipEvents) {
Michael Wachenschwanz06e49402018-02-20 23:39:09 -0800705 pw.println("events");
706 pw.increaseIndent();
707 final TimeSparseArray<UsageEvents.Event> events = stats.events;
708 final int eventCount = events != null ? events.size() : 0;
709 for (int i = 0; i < eventCount; i++) {
710 final UsageEvents.Event event = events.valueAt(i);
711 if (pkg != null && !pkg.equals(event.mPackage)) {
712 continue;
713 }
714 printEvent(pw, event, prettyDates);
Dianne Hackbornc81983a2017-10-20 16:16:32 -0700715 }
Michael Wachenschwanz06e49402018-02-20 23:39:09 -0800716 pw.decreaseIndent();
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700717 }
718 pw.decreaseIndent();
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700719 }
720
721 private static String intervalToString(int interval) {
722 switch (interval) {
723 case UsageStatsManager.INTERVAL_DAILY:
724 return "daily";
725 case UsageStatsManager.INTERVAL_WEEKLY:
726 return "weekly";
727 case UsageStatsManager.INTERVAL_MONTHLY:
728 return "monthly";
729 case UsageStatsManager.INTERVAL_YEARLY:
730 return "yearly";
731 default:
732 return "?";
733 }
734 }
Adam Lesinski3c153512014-07-23 17:34:34 -0700735
736 private static String eventToString(int eventType) {
737 switch (eventType) {
Adam Lesinski35168002014-07-21 15:25:30 -0700738 case UsageEvents.Event.NONE:
Adam Lesinski3c153512014-07-23 17:34:34 -0700739 return "NONE";
Adam Lesinski35168002014-07-21 15:25:30 -0700740 case UsageEvents.Event.MOVE_TO_BACKGROUND:
Adam Lesinski3c153512014-07-23 17:34:34 -0700741 return "MOVE_TO_BACKGROUND";
Adam Lesinski35168002014-07-21 15:25:30 -0700742 case UsageEvents.Event.MOVE_TO_FOREGROUND:
Adam Lesinski3c153512014-07-23 17:34:34 -0700743 return "MOVE_TO_FOREGROUND";
Adam Lesinski35168002014-07-21 15:25:30 -0700744 case UsageEvents.Event.END_OF_DAY:
Adam Lesinski3c153512014-07-23 17:34:34 -0700745 return "END_OF_DAY";
Adam Lesinski35168002014-07-21 15:25:30 -0700746 case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
Adam Lesinski3c153512014-07-23 17:34:34 -0700747 return "CONTINUE_PREVIOUS_DAY";
Adam Lesinski7f61e962014-09-02 16:43:52 -0700748 case UsageEvents.Event.CONFIGURATION_CHANGE:
749 return "CONFIGURATION_CHANGE";
Adam Lesinskic8e87292015-06-10 15:33:45 -0700750 case UsageEvents.Event.SYSTEM_INTERACTION:
751 return "SYSTEM_INTERACTION";
752 case UsageEvents.Event.USER_INTERACTION:
753 return "USER_INTERACTION";
Makoto Onukiac042502016-05-20 16:39:42 -0700754 case UsageEvents.Event.SHORTCUT_INVOCATION:
755 return "SHORTCUT_INVOCATION";
Kang Li53b43142016-11-14 14:38:25 -0800756 case UsageEvents.Event.CHOOSER_ACTION:
757 return "CHOOSER_ACTION";
Amith Yamasani803eab692017-11-09 17:47:04 -0800758 case UsageEvents.Event.NOTIFICATION_SEEN:
759 return "NOTIFICATION_SEEN";
Amith Yamasanibfc4bf52018-01-19 06:55:08 -0800760 case UsageEvents.Event.STANDBY_BUCKET_CHANGED:
761 return "STANDBY_BUCKET_CHANGED";
Adam Lesinski3c153512014-07-23 17:34:34 -0700762 default:
763 return "UNKNOWN";
764 }
765 }
Ritesh Reddy8a6ce2c2015-12-17 17:03:54 +0000766
767 byte[] getBackupPayload(String key){
768 return mDatabase.getBackupPayload(key);
769 }
770
771 void applyRestoredPayload(String key, byte[] payload){
772 mDatabase.applyRestoredPayload(key, payload);
773 }
Adam Lesinski3c153512014-07-23 17:34:34 -0700774}