blob: aa2cf77b5ae1da330eced948ffb815a9951aa649 [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;
Amith Yamasani55717a62015-04-03 17:22:36 -070022import android.app.usage.UsageEvents.Event;
Adam Lesinski3c153512014-07-23 17:34:34 -070023import android.app.usage.UsageStats;
24import android.app.usage.UsageStatsManager;
Amith Yamasani55717a62015-04-03 17:22:36 -070025import android.content.pm.PackageInfo;
26import android.content.pm.PackageManager;
Adam Lesinski7f61e962014-09-02 16:43:52 -070027import android.content.res.Configuration;
Adam Lesinski66143fa2014-09-11 08:31:05 -070028import android.os.SystemClock;
Adam Lesinski1bb18c42014-08-18 12:21:34 -070029import android.content.Context;
30import android.text.format.DateUtils;
31import android.util.ArrayMap;
Adam Lesinski3c153512014-07-23 17:34:34 -070032import android.util.ArraySet;
33import android.util.Slog;
34
Adam Lesinski1bb18c42014-08-18 12:21:34 -070035import com.android.internal.util.IndentingPrintWriter;
Adam Lesinski7f61e962014-09-02 16:43:52 -070036import com.android.server.usage.UsageStatsDatabase.StatCombiner;
37
Adam Lesinski3c153512014-07-23 17:34:34 -070038import java.io.File;
39import java.io.IOException;
40import java.text.SimpleDateFormat;
Adam Lesinski35168002014-07-21 15:25:30 -070041import java.util.ArrayList;
42import java.util.Arrays;
Adam Lesinski35168002014-07-21 15:25:30 -070043import java.util.List;
Adam Lesinski3c153512014-07-23 17:34:34 -070044
45/**
46 * A per-user UsageStatsService. All methods are meant to be called with the main lock held
47 * in UsageStatsService.
48 */
49class UserUsageStatsService {
50 private static final String TAG = "UsageStatsService";
51 private static final boolean DEBUG = UsageStatsService.DEBUG;
52 private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Adam Lesinski1bb18c42014-08-18 12:21:34 -070053 private static final int sDateFormatFlags =
54 DateUtils.FORMAT_SHOW_DATE
55 | DateUtils.FORMAT_SHOW_TIME
56 | DateUtils.FORMAT_SHOW_YEAR
57 | DateUtils.FORMAT_NUMERIC_DATE;
Adam Lesinski3c153512014-07-23 17:34:34 -070058
Adam Lesinski1bb18c42014-08-18 12:21:34 -070059 private final Context mContext;
Adam Lesinski3c153512014-07-23 17:34:34 -070060 private final UsageStatsDatabase mDatabase;
Adam Lesinski35168002014-07-21 15:25:30 -070061 private final IntervalStats[] mCurrentStats;
Adam Lesinskida4a3772016-01-07 18:24:53 -080062 private IntervalStats mAppIdleRollingWindow;
Adam Lesinski3c153512014-07-23 17:34:34 -070063 private boolean mStatsChanged = false;
Adam Lesinskid26bea32014-09-03 16:49:59 -070064 private final UnixCalendar mDailyExpiryDate;
Adam Lesinski3c153512014-07-23 17:34:34 -070065 private final StatsUpdatedListener mListener;
66 private final String mLogPrefix;
Amith Yamasani55717a62015-04-03 17:22:36 -070067 private final int mUserId;
Adam Lesinski3c153512014-07-23 17:34:34 -070068
Adam Lesinski7cba1d42015-08-04 16:17:37 -070069 private static final long[] INTERVAL_LENGTH = new long[] {
70 UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS,
71 UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS
72 };
73
Adam Lesinski3c153512014-07-23 17:34:34 -070074 interface StatsUpdatedListener {
75 void onStatsUpdated();
Adam Lesinskif0ef3c12016-01-13 12:26:07 -080076 long getAppIdleRollingWindowDurationMillis();
Adam Lesinski3c153512014-07-23 17:34:34 -070077 }
78
Amith Yamasanib0ff3222015-03-04 09:56:14 -080079 UserUsageStatsService(Context context, int userId, File usageStatsDir,
80 StatsUpdatedListener listener) {
Adam Lesinski1bb18c42014-08-18 12:21:34 -070081 mContext = context;
Adam Lesinskid26bea32014-09-03 16:49:59 -070082 mDailyExpiryDate = new UnixCalendar(0);
Adam Lesinski3c153512014-07-23 17:34:34 -070083 mDatabase = new UsageStatsDatabase(usageStatsDir);
Adam Lesinski35168002014-07-21 15:25:30 -070084 mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
Adam Lesinski3c153512014-07-23 17:34:34 -070085 mListener = listener;
86 mLogPrefix = "User[" + Integer.toString(userId) + "] ";
Amith Yamasani55717a62015-04-03 17:22:36 -070087 mUserId = userId;
Adam Lesinski3c153512014-07-23 17:34:34 -070088 }
89
Amith Yamasani06bf8242015-05-08 16:36:21 -070090 void init(final long currentTimeMillis, final long deviceUsageTime) {
Adam Lesinski66143fa2014-09-11 08:31:05 -070091 mDatabase.init(currentTimeMillis);
Adam Lesinski3c153512014-07-23 17:34:34 -070092
93 int nullCount = 0;
94 for (int i = 0; i < mCurrentStats.length; i++) {
95 mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
96 if (mCurrentStats[i] == null) {
Adam Lesinski35168002014-07-21 15:25:30 -070097 // Find out how many intervals we don't have data for.
98 // Ideally it should be all or none.
Adam Lesinski3c153512014-07-23 17:34:34 -070099 nullCount++;
100 }
101 }
102
103 if (nullCount > 0) {
104 if (nullCount != mCurrentStats.length) {
105 // This is weird, but we shouldn't fail if something like this
106 // happens.
107 Slog.w(TAG, mLogPrefix + "Some stats have no latest available");
108 } else {
109 // This must be first boot.
110 }
111
112 // By calling loadActiveStats, we will
113 // generate new stats for each bucket.
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700114 loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false);
Adam Lesinski3c153512014-07-23 17:34:34 -0700115 } else {
116 // Set up the expiry date to be one day from the latest daily stat.
117 // This may actually be today and we will rollover on the first event
118 // that is reported.
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700119 updateRolloverDeadline();
Adam Lesinski3c153512014-07-23 17:34:34 -0700120 }
121
122 // Now close off any events that were open at the time this was saved.
Adam Lesinski35168002014-07-21 15:25:30 -0700123 for (IntervalStats stat : mCurrentStats) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700124 final int pkgCount = stat.packageStats.size();
Adam Lesinski3c153512014-07-23 17:34:34 -0700125 for (int i = 0; i < pkgCount; i++) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700126 UsageStats pkgStats = stat.packageStats.valueAt(i);
Adam Lesinski35168002014-07-21 15:25:30 -0700127 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
128 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
129 stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
130 UsageEvents.Event.END_OF_DAY);
Adam Lesinski3c153512014-07-23 17:34:34 -0700131 notifyStatsChanged();
132 }
133 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700134
135 stat.updateConfigurationStats(null, stat.lastTimeSaved);
Adam Lesinski3c153512014-07-23 17:34:34 -0700136 }
Amith Yamasani55717a62015-04-03 17:22:36 -0700137
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800138 refreshAppIdleRollingWindow(currentTimeMillis, deviceUsageTime);
139
Amith Yamasani55717a62015-04-03 17:22:36 -0700140 if (mDatabase.isNewUpdate()) {
Amith Yamasani06bf8242015-05-08 16:36:21 -0700141 initializeDefaultsForApps(currentTimeMillis, deviceUsageTime,
142 mDatabase.isFirstUpdate());
Amith Yamasani55717a62015-04-03 17:22:36 -0700143 }
144 }
145
146 /**
147 * If any of the apps don't have a last-used entry, add one now.
148 * @param currentTimeMillis the current time
149 * @param firstUpdate if it is the first update, touch all installed apps, otherwise only
150 * touch the system apps
151 */
Amith Yamasani06bf8242015-05-08 16:36:21 -0700152 private void initializeDefaultsForApps(long currentTimeMillis, long deviceUsageTime,
153 boolean firstUpdate) {
Amith Yamasani55717a62015-04-03 17:22:36 -0700154 PackageManager pm = mContext.getPackageManager();
Jeff Sharkeye06b4d12016-01-06 14:51:50 -0700155 List<PackageInfo> packages = pm.getInstalledPackagesAsUser(0, mUserId);
Amith Yamasani55717a62015-04-03 17:22:36 -0700156 final int packageCount = packages.size();
157 for (int i = 0; i < packageCount; i++) {
158 final PackageInfo pi = packages.get(i);
159 String packageName = pi.packageName;
160 if (pi.applicationInfo != null && (firstUpdate || pi.applicationInfo.isSystemApp())
Amith Yamasani06bf8242015-05-08 16:36:21 -0700161 && getBeginIdleTime(packageName) == -1) {
Amith Yamasani55717a62015-04-03 17:22:36 -0700162 for (IntervalStats stats : mCurrentStats) {
Adam Lesinskic8e87292015-06-10 15:33:45 -0700163 stats.update(packageName, currentTimeMillis, Event.SYSTEM_INTERACTION);
Amith Yamasani06bf8242015-05-08 16:36:21 -0700164 stats.updateBeginIdleTime(packageName, deviceUsageTime);
Amith Yamasani55717a62015-04-03 17:22:36 -0700165 }
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800166
167 mAppIdleRollingWindow.update(packageName, currentTimeMillis,
168 Event.SYSTEM_INTERACTION);
169 mAppIdleRollingWindow.updateBeginIdleTime(packageName, deviceUsageTime);
170 mStatsChanged = true;
Amith Yamasani55717a62015-04-03 17:22:36 -0700171 }
172 }
173 // Persist the new OTA-related access stats.
174 persistActiveStats();
Adam Lesinski3c153512014-07-23 17:34:34 -0700175 }
176
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800177 void onTimeChanged(long oldTime, long newTime, long deviceUsageTime,
178 boolean resetBeginIdleTime) {
Adam Lesinski66143fa2014-09-11 08:31:05 -0700179 persistActiveStats();
180 mDatabase.onTimeChanged(newTime - oldTime);
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700181 loadActiveStats(newTime, resetBeginIdleTime);
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800182 refreshAppIdleRollingWindow(newTime, deviceUsageTime);
Adam Lesinski66143fa2014-09-11 08:31:05 -0700183 }
184
Amith Yamasani06bf8242015-05-08 16:36:21 -0700185 void reportEvent(UsageEvents.Event event, long deviceUsageTime) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700186 if (DEBUG) {
Adam Lesinski9d960752014-08-25 14:48:12 -0700187 Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
Adam Lesinski7f61e962014-09-02 16:43:52 -0700188 + "[" + event.mTimeStamp + "]: "
189 + eventToString(event.mEventType));
Adam Lesinski3c153512014-07-23 17:34:34 -0700190 }
191
Adam Lesinski7f61e962014-09-02 16:43:52 -0700192 if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700193 // Need to rollover
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800194 rolloverStats(event.mTimeStamp, deviceUsageTime);
Adam Lesinski3c153512014-07-23 17:34:34 -0700195 }
196
Adam Lesinski7f61e962014-09-02 16:43:52 -0700197 final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
198
199 final Configuration newFullConfig = event.mConfiguration;
200 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
201 currentDailyStats.activeConfiguration != null) {
202 // Make the event configuration a delta.
203 event.mConfiguration = Configuration.generateDelta(
204 currentDailyStats.activeConfiguration, newFullConfig);
Adam Lesinski35168002014-07-21 15:25:30 -0700205 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700206
207 // Add the event to the daily list.
208 if (currentDailyStats.events == null) {
209 currentDailyStats.events = new TimeSparseArray<>();
210 }
Adam Lesinskic8e87292015-06-10 15:33:45 -0700211 if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) {
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800212 currentDailyStats.events.put(event.mTimeStamp, event);
213 }
Adam Lesinski35168002014-07-21 15:25:30 -0700214
215 for (IntervalStats stats : mCurrentStats) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700216 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
217 stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
218 } else {
219 stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
Amith Yamasani06bf8242015-05-08 16:36:21 -0700220 stats.updateBeginIdleTime(event.mPackage, deviceUsageTime);
Adam Lesinski7f61e962014-09-02 16:43:52 -0700221 }
Adam Lesinski3c153512014-07-23 17:34:34 -0700222 }
223
Adam Lesinskida4a3772016-01-07 18:24:53 -0800224 if (event.mEventType != Event.CONFIGURATION_CHANGE) {
225 mAppIdleRollingWindow.update(event.mPackage, event.mTimeStamp, event.mEventType);
226 mAppIdleRollingWindow.updateBeginIdleTime(event.mPackage, deviceUsageTime);
227 }
228
Adam Lesinski3c153512014-07-23 17:34:34 -0700229 notifyStatsChanged();
230 }
231
Amith Yamasanicf768722015-04-23 20:36:41 -0700232 /**
Amith Yamasani547116e2015-05-19 16:51:30 -0700233 * Sets the beginIdleTime for each of the intervals.
234 * @param beginIdleTime
Amith Yamasanicf768722015-04-23 20:36:41 -0700235 */
Amith Yamasani547116e2015-05-19 16:51:30 -0700236 void setBeginIdleTime(String packageName, long beginIdleTime) {
Amith Yamasanicf768722015-04-23 20:36:41 -0700237 for (IntervalStats stats : mCurrentStats) {
Amith Yamasani547116e2015-05-19 16:51:30 -0700238 stats.updateBeginIdleTime(packageName, beginIdleTime);
239 }
Adam Lesinskida4a3772016-01-07 18:24:53 -0800240 mAppIdleRollingWindow.updateBeginIdleTime(packageName, beginIdleTime);
Amith Yamasani547116e2015-05-19 16:51:30 -0700241 notifyStatsChanged();
242 }
243
Adam Lesinskic8e87292015-06-10 15:33:45 -0700244 void setSystemLastUsedTime(String packageName, long lastUsedTime) {
Amith Yamasani547116e2015-05-19 16:51:30 -0700245 for (IntervalStats stats : mCurrentStats) {
Adam Lesinskic8e87292015-06-10 15:33:45 -0700246 stats.updateSystemLastUsedTime(packageName, lastUsedTime);
Amith Yamasanicf768722015-04-23 20:36:41 -0700247 }
Adam Lesinskida4a3772016-01-07 18:24:53 -0800248 mAppIdleRollingWindow.updateSystemLastUsedTime(packageName, lastUsedTime);
Amith Yamasanicf768722015-04-23 20:36:41 -0700249 notifyStatsChanged();
250 }
251
Adam Lesinski7f61e962014-09-02 16:43:52 -0700252 private static final StatCombiner<UsageStats> sUsageStatsCombiner =
253 new StatCombiner<UsageStats>() {
254 @Override
255 public void combine(IntervalStats stats, boolean mutable,
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700256 List<UsageStats> accResult) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700257 if (!mutable) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700258 accResult.addAll(stats.packageStats.values());
Adam Lesinski7f61e962014-09-02 16:43:52 -0700259 return;
260 }
261
Adam Lesinski37a46b42014-09-05 15:38:05 -0700262 final int statCount = stats.packageStats.size();
Adam Lesinski7f61e962014-09-02 16:43:52 -0700263 for (int i = 0; i < statCount; i++) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700264 accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
Adam Lesinski7f61e962014-09-02 16:43:52 -0700265 }
266 }
267 };
268
269 private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
270 new StatCombiner<ConfigurationStats>() {
271 @Override
272 public void combine(IntervalStats stats, boolean mutable,
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700273 List<ConfigurationStats> accResult) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700274 if (!mutable) {
275 accResult.addAll(stats.configurations.values());
276 return;
277 }
278
279 final int configCount = stats.configurations.size();
280 for (int i = 0; i < configCount; i++) {
281 accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
282 }
283 }
284 };
285
286 /**
287 * Generic query method that selects the appropriate IntervalStats for the specified time range
288 * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
289 * provided to select the stats to use from the IntervalStats object.
290 */
Adam Lesinskid26bea32014-09-03 16:49:59 -0700291 private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
Adam Lesinski7f61e962014-09-02 16:43:52 -0700292 StatCombiner<T> combiner) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700293 if (intervalType == UsageStatsManager.INTERVAL_BEST) {
294 intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
295 if (intervalType < 0) {
296 // Nothing saved to disk yet, so every stat is just as equal (no rollover has
297 // occurred.
298 intervalType = UsageStatsManager.INTERVAL_DAILY;
299 }
Adam Lesinski35168002014-07-21 15:25:30 -0700300 }
301
Adam Lesinskid26bea32014-09-03 16:49:59 -0700302 if (intervalType < 0 || intervalType >= mCurrentStats.length) {
Adam Lesinski35168002014-07-21 15:25:30 -0700303 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700304 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
Adam Lesinski35168002014-07-21 15:25:30 -0700305 }
306 return null;
307 }
308
Adam Lesinskid26bea32014-09-03 16:49:59 -0700309 final IntervalStats currentStats = mCurrentStats[intervalType];
Adam Lesinski35168002014-07-21 15:25:30 -0700310
Adam Lesinski3c153512014-07-23 17:34:34 -0700311 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700312 Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
Adam Lesinski35168002014-07-21 15:25:30 -0700313 + beginTime + " AND endTime < " + endTime);
Adam Lesinski3c153512014-07-23 17:34:34 -0700314 }
315
Adam Lesinskid26bea32014-09-03 16:49:59 -0700316 if (beginTime >= currentStats.endTime) {
317 if (DEBUG) {
318 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
319 + currentStats.endTime);
320 }
321 // Nothing newer available.
322 return null;
323 }
324
325 // Truncate the endTime to just before the in-memory stats. Then, we'll append the
326 // in-memory stats to the results (if necessary) so as to avoid writing to disk too
327 // often.
328 final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
329
330 // Get the stats from disk.
331 List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
332 truncatedEndTime, combiner);
Adam Lesinski3c153512014-07-23 17:34:34 -0700333 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700334 Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
335 Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
336 " endTime=" + currentStats.endTime);
337 }
338
339 // Now check if the in-memory stats match the range and add them if they do.
340 if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
341 if (DEBUG) {
342 Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
343 }
344
345 if (results == null) {
346 results = new ArrayList<>();
347 }
348 combiner.combine(currentStats, true, results);
349 }
350
351 if (DEBUG) {
352 Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
Adam Lesinski3c153512014-07-23 17:34:34 -0700353 }
354 return results;
355 }
356
Adam Lesinski7f61e962014-09-02 16:43:52 -0700357 List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
358 return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
359 }
360
361 List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
362 return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
363 }
364
Adam Lesinskid26bea32014-09-03 16:49:59 -0700365 UsageEvents queryEvents(final long beginTime, final long endTime) {
366 final ArraySet<String> names = new ArraySet<>();
367 List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
368 beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
369 @Override
370 public void combine(IntervalStats stats, boolean mutable,
371 List<UsageEvents.Event> accumulatedResult) {
372 if (stats.events == null) {
373 return;
374 }
Adam Lesinski35168002014-07-21 15:25:30 -0700375
Adam Lesinskid26bea32014-09-03 16:49:59 -0700376 final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
377 if (startIndex < 0) {
378 return;
379 }
Adam Lesinski35168002014-07-21 15:25:30 -0700380
Adam Lesinskid26bea32014-09-03 16:49:59 -0700381 final int size = stats.events.size();
382 for (int i = startIndex; i < size; i++) {
383 if (stats.events.keyAt(i) >= endTime) {
384 return;
385 }
Adam Lesinski35168002014-07-21 15:25:30 -0700386
Adam Lesinskid26bea32014-09-03 16:49:59 -0700387 final UsageEvents.Event event = stats.events.valueAt(i);
388 names.add(event.mPackage);
389 if (event.mClass != null) {
390 names.add(event.mClass);
391 }
392 accumulatedResult.add(event);
393 }
394 }
395 });
396
397 if (results == null || results.isEmpty()) {
398 return null;
Adam Lesinski35168002014-07-21 15:25:30 -0700399 }
400
Adam Lesinskid26bea32014-09-03 16:49:59 -0700401 String[] table = names.toArray(new String[names.size()]);
402 Arrays.sort(table);
403 return new UsageEvents(results, table);
Adam Lesinski35168002014-07-21 15:25:30 -0700404 }
405
Amith Yamasani06bf8242015-05-08 16:36:21 -0700406 long getBeginIdleTime(String packageName) {
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800407 UsageStats packageUsage;
Adam Lesinskida4a3772016-01-07 18:24:53 -0800408 if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800409 return -1;
410 } else {
Amith Yamasani06bf8242015-05-08 16:36:21 -0700411 return packageUsage.getBeginIdleTime();
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800412 }
413 }
414
Adam Lesinskic8e87292015-06-10 15:33:45 -0700415 long getSystemLastUsedTime(String packageName) {
Amith Yamasani547116e2015-05-19 16:51:30 -0700416 UsageStats packageUsage;
Adam Lesinskida4a3772016-01-07 18:24:53 -0800417 if ((packageUsage = mAppIdleRollingWindow.packageStats.get(packageName)) == null) {
Amith Yamasani547116e2015-05-19 16:51:30 -0700418 return -1;
419 } else {
Adam Lesinskic8e87292015-06-10 15:33:45 -0700420 return packageUsage.getLastTimeSystemUsed();
Amith Yamasani547116e2015-05-19 16:51:30 -0700421 }
422 }
423
Adam Lesinski3c153512014-07-23 17:34:34 -0700424 void persistActiveStats() {
425 if (mStatsChanged) {
426 Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
427 try {
428 for (int i = 0; i < mCurrentStats.length; i++) {
429 mDatabase.putUsageStats(i, mCurrentStats[i]);
430 }
431 mStatsChanged = false;
432 } catch (IOException e) {
433 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
434 }
435 }
436 }
437
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800438 private void rolloverStats(final long currentTimeMillis, final long deviceUsageTime) {
Adam Lesinski66143fa2014-09-11 08:31:05 -0700439 final long startTime = SystemClock.elapsedRealtime();
Adam Lesinski3c153512014-07-23 17:34:34 -0700440 Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
441
442 // Finish any ongoing events with an END_OF_DAY event. Make a note of which components
443 // need a new CONTINUE_PREVIOUS_DAY entry.
Adam Lesinski7f61e962014-09-02 16:43:52 -0700444 final Configuration previousConfig =
445 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
Adam Lesinski3c153512014-07-23 17:34:34 -0700446 ArraySet<String> continuePreviousDay = new ArraySet<>();
Adam Lesinski35168002014-07-21 15:25:30 -0700447 for (IntervalStats stat : mCurrentStats) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700448 final int pkgCount = stat.packageStats.size();
Adam Lesinski3c153512014-07-23 17:34:34 -0700449 for (int i = 0; i < pkgCount; i++) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700450 UsageStats pkgStats = stat.packageStats.valueAt(i);
Adam Lesinski35168002014-07-21 15:25:30 -0700451 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
452 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700453 continuePreviousDay.add(pkgStats.mPackageName);
Adam Lesinski7f61e962014-09-02 16:43:52 -0700454 stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1,
455 UsageEvents.Event.END_OF_DAY);
Adam Lesinski66143fa2014-09-11 08:31:05 -0700456 notifyStatsChanged();
Adam Lesinski3c153512014-07-23 17:34:34 -0700457 }
458 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700459
460 stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1);
Adam Lesinski3c153512014-07-23 17:34:34 -0700461 }
462
463 persistActiveStats();
Adam Lesinski66143fa2014-09-11 08:31:05 -0700464 mDatabase.prune(currentTimeMillis);
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700465 loadActiveStats(currentTimeMillis, /*resetBeginIdleTime=*/ false);
Adam Lesinski3c153512014-07-23 17:34:34 -0700466
467 final int continueCount = continuePreviousDay.size();
468 for (int i = 0; i < continueCount; i++) {
469 String name = continuePreviousDay.valueAt(i);
Adam Lesinski7f61e962014-09-02 16:43:52 -0700470 final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime;
Adam Lesinski35168002014-07-21 15:25:30 -0700471 for (IntervalStats stat : mCurrentStats) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700472 stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
473 stat.updateConfigurationStats(previousConfig, beginTime);
Adam Lesinski66143fa2014-09-11 08:31:05 -0700474 notifyStatsChanged();
Adam Lesinski3c153512014-07-23 17:34:34 -0700475 }
476 }
477 persistActiveStats();
478
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800479 refreshAppIdleRollingWindow(currentTimeMillis, deviceUsageTime);
Adam Lesinskida4a3772016-01-07 18:24:53 -0800480
Adam Lesinski66143fa2014-09-11 08:31:05 -0700481 final long totalTime = SystemClock.elapsedRealtime() - startTime;
Adam Lesinski3c153512014-07-23 17:34:34 -0700482 Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
483 + " milliseconds");
484 }
485
486 private void notifyStatsChanged() {
487 if (!mStatsChanged) {
488 mStatsChanged = true;
489 mListener.onStatsUpdated();
490 }
491 }
492
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700493 private void loadActiveStats(final long currentTimeMillis, boolean resetBeginIdleTime) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700494 for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700495 final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType);
496 if (stats != null && currentTimeMillis - 500 >= stats.endTime &&
497 currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) {
Adam Lesinski35168002014-07-21 15:25:30 -0700498 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700499 Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700500 sDateFormat.format(stats.beginTime) + "(" + stats.beginTime +
Adam Lesinskid26bea32014-09-03 16:49:59 -0700501 ") for interval " + intervalType);
Adam Lesinski35168002014-07-21 15:25:30 -0700502 }
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700503 mCurrentStats[intervalType] = stats;
Adam Lesinski3c153512014-07-23 17:34:34 -0700504 } else {
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700505 // No good fit remains.
Adam Lesinski35168002014-07-21 15:25:30 -0700506 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700507 Slog.d(TAG, "Creating new stats @ " +
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700508 sDateFormat.format(currentTimeMillis) + "(" +
509 currentTimeMillis + ") for interval " + intervalType);
Adam Lesinski35168002014-07-21 15:25:30 -0700510 }
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700511
Adam Lesinskid26bea32014-09-03 16:49:59 -0700512 mCurrentStats[intervalType] = new IntervalStats();
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700513 mCurrentStats[intervalType].beginTime = currentTimeMillis;
514 mCurrentStats[intervalType].endTime = currentTimeMillis + 1;
Adam Lesinski3c153512014-07-23 17:34:34 -0700515 }
Amith Yamasani520d8f22015-05-08 16:36:21 -0700516
517 if (resetBeginIdleTime) {
518 for (UsageStats usageStats : mCurrentStats[intervalType].packageStats.values()) {
519 usageStats.mBeginIdleTime = 0;
520 }
521 }
Adam Lesinski3c153512014-07-23 17:34:34 -0700522 }
Adam Lesinskida4a3772016-01-07 18:24:53 -0800523
Adam Lesinski3c153512014-07-23 17:34:34 -0700524 mStatsChanged = false;
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700525 updateRolloverDeadline();
526 }
527
528 private void updateRolloverDeadline() {
529 mDailyExpiryDate.setTimeInMillis(
530 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
Adam Lesinskid26bea32014-09-03 16:49:59 -0700531 mDailyExpiryDate.addDays(1);
Adam Lesinskid26bea32014-09-03 16:49:59 -0700532 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
533 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
Adam Lesinski7cba1d42015-08-04 16:17:37 -0700534 mDailyExpiryDate.getTimeInMillis() + ")");
Adam Lesinski3c153512014-07-23 17:34:34 -0700535 }
536
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800537 private static void mergePackageStats(IntervalStats dst, IntervalStats src,
538 final long deviceUsageTime) {
Adam Lesinskida4a3772016-01-07 18:24:53 -0800539 dst.endTime = Math.max(dst.endTime, src.endTime);
540
541 final int srcPackageCount = src.packageStats.size();
542 for (int i = 0; i < srcPackageCount; i++) {
543 final String packageName = src.packageStats.keyAt(i);
544 final UsageStats srcStats = src.packageStats.valueAt(i);
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800545 UsageStats dstStats = dst.packageStats.get(packageName);
Adam Lesinskida4a3772016-01-07 18:24:53 -0800546 if (dstStats == null) {
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800547 dstStats = new UsageStats(srcStats);
548 dst.packageStats.put(packageName, dstStats);
Adam Lesinskida4a3772016-01-07 18:24:53 -0800549 } else {
550 dstStats.add(src.packageStats.valueAt(i));
551 }
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800552
553 // App idle times can not begin in the future. This happens if we had a time change.
554 if (dstStats.mBeginIdleTime > deviceUsageTime) {
555 dstStats.mBeginIdleTime = deviceUsageTime;
556 }
Adam Lesinskida4a3772016-01-07 18:24:53 -0800557 }
558 }
559
560 /**
Adam Lesinskida4a3772016-01-07 18:24:53 -0800561 * App idle operates on a rolling window of time. When we roll over time, we end up with a
562 * period of time where in-memory stats are empty and we don't hit the disk for older stats
563 * for performance reasons. Suddenly all apps will become idle.
564 *
565 * Instead, at times we do a deep query to find all the apps that have run in the past few
566 * days and keep the cached data up to date.
567 *
568 * @param currentTimeMillis
569 */
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800570 void refreshAppIdleRollingWindow(final long currentTimeMillis, final long deviceUsageTime) {
Adam Lesinskida4a3772016-01-07 18:24:53 -0800571 // Start the rolling window for AppIdle requests.
Adam Lesinskif0ef3c12016-01-13 12:26:07 -0800572 final long startRangeMillis = currentTimeMillis -
573 mListener.getAppIdleRollingWindowDurationMillis();
574
Adam Lesinskida4a3772016-01-07 18:24:53 -0800575 List<IntervalStats> stats = mDatabase.queryUsageStats(UsageStatsManager.INTERVAL_DAILY,
Adam Lesinskif0ef3c12016-01-13 12:26:07 -0800576 startRangeMillis, currentTimeMillis, new StatCombiner<IntervalStats>() {
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800577 @Override
578 public void combine(IntervalStats stats, boolean mutable,
579 List<IntervalStats> accumulatedResult) {
580 IntervalStats accum;
581 if (accumulatedResult.isEmpty()) {
582 accum = new IntervalStats();
583 accum.beginTime = stats.beginTime;
584 accumulatedResult.add(accum);
585 } else {
586 accum = accumulatedResult.get(0);
587 }
588
589 mergePackageStats(accum, stats, deviceUsageTime);
590 }
591 });
Adam Lesinskida4a3772016-01-07 18:24:53 -0800592
593 if (stats == null || stats.isEmpty()) {
594 mAppIdleRollingWindow = new IntervalStats();
595 mergePackageStats(mAppIdleRollingWindow,
Adam Lesinski7856f3c2016-01-13 11:03:35 -0800596 mCurrentStats[UsageStatsManager.INTERVAL_YEARLY], deviceUsageTime);
Adam Lesinskida4a3772016-01-07 18:24:53 -0800597 } else {
598 mAppIdleRollingWindow = stats.get(0);
599 }
600 }
601
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700602 //
603 // -- DUMP related methods --
604 //
605
Amith Yamasani06bf8242015-05-08 16:36:21 -0700606 void checkin(final IndentingPrintWriter pw, final long screenOnTime) {
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700607 mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
608 @Override
609 public boolean checkin(IntervalStats stats) {
Amith Yamasani06bf8242015-05-08 16:36:21 -0700610 printIntervalStats(pw, stats, screenOnTime, false);
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700611 return true;
612 }
613 });
614 }
615
Amith Yamasani06bf8242015-05-08 16:36:21 -0700616 void dump(IndentingPrintWriter pw, final long screenOnTime) {
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700617 // This is not a check-in, only dump in-memory stats.
618 for (int interval = 0; interval < mCurrentStats.length; interval++) {
619 pw.print("In-memory ");
620 pw.print(intervalToString(interval));
621 pw.println(" stats");
Amith Yamasani06bf8242015-05-08 16:36:21 -0700622 printIntervalStats(pw, mCurrentStats[interval], screenOnTime, true);
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700623 }
Adam Lesinskida4a3772016-01-07 18:24:53 -0800624
625 pw.println("AppIdleRollingWindow cache");
626 printIntervalStats(pw, mAppIdleRollingWindow, screenOnTime, true);
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700627 }
628
629 private String formatDateTime(long dateTime, boolean pretty) {
630 if (pretty) {
631 return "\"" + DateUtils.formatDateTime(mContext, dateTime, sDateFormatFlags) + "\"";
632 }
633 return Long.toString(dateTime);
634 }
635
636 private String formatElapsedTime(long elapsedTime, boolean pretty) {
637 if (pretty) {
638 return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
639 }
640 return Long.toString(elapsedTime);
641 }
642
Amith Yamasani06bf8242015-05-08 16:36:21 -0700643 void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, long screenOnTime,
644 boolean prettyDates) {
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700645 if (prettyDates) {
646 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
647 stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
648 } else {
649 pw.printPair("beginTime", stats.beginTime);
650 pw.printPair("endTime", stats.endTime);
651 }
652 pw.println();
653 pw.increaseIndent();
654 pw.println("packages");
655 pw.increaseIndent();
656 final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
657 final int pkgCount = pkgStats.size();
658 for (int i = 0; i < pkgCount; i++) {
659 final UsageStats usageStats = pkgStats.valueAt(i);
660 pw.printPair("package", usageStats.mPackageName);
Adam Lesinskic8e87292015-06-10 15:33:45 -0700661 pw.printPair("totalTime",
662 formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700663 pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
Adam Lesinskic8e87292015-06-10 15:33:45 -0700664 pw.printPair("lastTimeSystem",
665 formatDateTime(usageStats.mLastTimeSystemUsed, prettyDates));
Amith Yamasani06bf8242015-05-08 16:36:21 -0700666 pw.printPair("inactiveTime",
667 formatElapsedTime(screenOnTime - usageStats.mBeginIdleTime, prettyDates));
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700668 pw.println();
669 }
670 pw.decreaseIndent();
671
672 pw.println("configurations");
673 pw.increaseIndent();
Adam Lesinskic8e87292015-06-10 15:33:45 -0700674 final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations;
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700675 final int configCount = configStats.size();
676 for (int i = 0; i < configCount; i++) {
677 final ConfigurationStats config = configStats.valueAt(i);
678 pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
679 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
680 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
681 pw.printPair("count", config.mActivationCount);
682 pw.println();
683 }
684 pw.decreaseIndent();
685
686 pw.println("events");
687 pw.increaseIndent();
688 final TimeSparseArray<UsageEvents.Event> events = stats.events;
689 final int eventCount = events != null ? events.size() : 0;
690 for (int i = 0; i < eventCount; i++) {
691 final UsageEvents.Event event = events.valueAt(i);
692 pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
693 pw.printPair("type", eventToString(event.mEventType));
694 pw.printPair("package", event.mPackage);
695 if (event.mClass != null) {
696 pw.printPair("class", event.mClass);
697 }
698 if (event.mConfiguration != null) {
699 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
700 }
701 pw.println();
702 }
703 pw.decreaseIndent();
704 pw.decreaseIndent();
705 }
706
707 private static String intervalToString(int interval) {
708 switch (interval) {
709 case UsageStatsManager.INTERVAL_DAILY:
710 return "daily";
711 case UsageStatsManager.INTERVAL_WEEKLY:
712 return "weekly";
713 case UsageStatsManager.INTERVAL_MONTHLY:
714 return "monthly";
715 case UsageStatsManager.INTERVAL_YEARLY:
716 return "yearly";
717 default:
718 return "?";
719 }
720 }
Adam Lesinski3c153512014-07-23 17:34:34 -0700721
722 private static String eventToString(int eventType) {
723 switch (eventType) {
Adam Lesinski35168002014-07-21 15:25:30 -0700724 case UsageEvents.Event.NONE:
Adam Lesinski3c153512014-07-23 17:34:34 -0700725 return "NONE";
Adam Lesinski35168002014-07-21 15:25:30 -0700726 case UsageEvents.Event.MOVE_TO_BACKGROUND:
Adam Lesinski3c153512014-07-23 17:34:34 -0700727 return "MOVE_TO_BACKGROUND";
Adam Lesinski35168002014-07-21 15:25:30 -0700728 case UsageEvents.Event.MOVE_TO_FOREGROUND:
Adam Lesinski3c153512014-07-23 17:34:34 -0700729 return "MOVE_TO_FOREGROUND";
Adam Lesinski35168002014-07-21 15:25:30 -0700730 case UsageEvents.Event.END_OF_DAY:
Adam Lesinski3c153512014-07-23 17:34:34 -0700731 return "END_OF_DAY";
Adam Lesinski35168002014-07-21 15:25:30 -0700732 case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
Adam Lesinski3c153512014-07-23 17:34:34 -0700733 return "CONTINUE_PREVIOUS_DAY";
Adam Lesinski7f61e962014-09-02 16:43:52 -0700734 case UsageEvents.Event.CONFIGURATION_CHANGE:
735 return "CONFIGURATION_CHANGE";
Adam Lesinskic8e87292015-06-10 15:33:45 -0700736 case UsageEvents.Event.SYSTEM_INTERACTION:
737 return "SYSTEM_INTERACTION";
738 case UsageEvents.Event.USER_INTERACTION:
739 return "USER_INTERACTION";
Adam Lesinski3c153512014-07-23 17:34:34 -0700740 default:
741 return "UNKNOWN";
742 }
743 }
Ritesh Reddy8a6ce2c2015-12-17 17:03:54 +0000744
745 byte[] getBackupPayload(String key){
746 return mDatabase.getBackupPayload(key);
747 }
748
749 void applyRestoredPayload(String key, byte[] payload){
750 mDatabase.applyRestoredPayload(key, payload);
751 }
Adam Lesinski3c153512014-07-23 17:34:34 -0700752}