blob: afe27c75038b0fc60ba618a66bad4b079a5c06f7 [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;
27import android.text.format.DateUtils;
28import android.util.ArrayMap;
Adam Lesinski3c153512014-07-23 17:34:34 -070029import android.util.ArraySet;
30import android.util.Slog;
31
Adam Lesinski1bb18c42014-08-18 12:21:34 -070032import com.android.internal.util.IndentingPrintWriter;
Adam Lesinski7f61e962014-09-02 16:43:52 -070033import com.android.server.usage.UsageStatsDatabase.StatCombiner;
34
Adam Lesinski3c153512014-07-23 17:34:34 -070035import java.io.File;
36import java.io.IOException;
37import java.text.SimpleDateFormat;
Adam Lesinski35168002014-07-21 15:25:30 -070038import java.util.ArrayList;
39import java.util.Arrays;
Adam Lesinski35168002014-07-21 15:25:30 -070040import java.util.List;
Adam Lesinski3c153512014-07-23 17:34:34 -070041
42/**
43 * A per-user UsageStatsService. All methods are meant to be called with the main lock held
44 * in UsageStatsService.
45 */
46class UserUsageStatsService {
47 private static final String TAG = "UsageStatsService";
48 private static final boolean DEBUG = UsageStatsService.DEBUG;
49 private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Adam Lesinski1bb18c42014-08-18 12:21:34 -070050 private static final int sDateFormatFlags =
51 DateUtils.FORMAT_SHOW_DATE
52 | DateUtils.FORMAT_SHOW_TIME
53 | DateUtils.FORMAT_SHOW_YEAR
54 | DateUtils.FORMAT_NUMERIC_DATE;
Adam Lesinski3c153512014-07-23 17:34:34 -070055
Adam Lesinski1bb18c42014-08-18 12:21:34 -070056 private final Context mContext;
Adam Lesinski3c153512014-07-23 17:34:34 -070057 private final UsageStatsDatabase mDatabase;
Adam Lesinski35168002014-07-21 15:25:30 -070058 private final IntervalStats[] mCurrentStats;
Adam Lesinski3c153512014-07-23 17:34:34 -070059 private boolean mStatsChanged = false;
Adam Lesinskid26bea32014-09-03 16:49:59 -070060 private final UnixCalendar mDailyExpiryDate;
Adam Lesinski3c153512014-07-23 17:34:34 -070061 private final StatsUpdatedListener mListener;
62 private final String mLogPrefix;
63
64 interface StatsUpdatedListener {
65 void onStatsUpdated();
66 }
67
Amith Yamasanib0ff3222015-03-04 09:56:14 -080068 UserUsageStatsService(Context context, int userId, File usageStatsDir,
69 StatsUpdatedListener listener) {
Adam Lesinski1bb18c42014-08-18 12:21:34 -070070 mContext = context;
Adam Lesinskid26bea32014-09-03 16:49:59 -070071 mDailyExpiryDate = new UnixCalendar(0);
Adam Lesinski3c153512014-07-23 17:34:34 -070072 mDatabase = new UsageStatsDatabase(usageStatsDir);
Adam Lesinski35168002014-07-21 15:25:30 -070073 mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT];
Adam Lesinski3c153512014-07-23 17:34:34 -070074 mListener = listener;
75 mLogPrefix = "User[" + Integer.toString(userId) + "] ";
76 }
77
Adam Lesinski66143fa2014-09-11 08:31:05 -070078 void init(final long currentTimeMillis) {
79 mDatabase.init(currentTimeMillis);
Adam Lesinski3c153512014-07-23 17:34:34 -070080
81 int nullCount = 0;
82 for (int i = 0; i < mCurrentStats.length; i++) {
83 mCurrentStats[i] = mDatabase.getLatestUsageStats(i);
84 if (mCurrentStats[i] == null) {
Adam Lesinski35168002014-07-21 15:25:30 -070085 // Find out how many intervals we don't have data for.
86 // Ideally it should be all or none.
Adam Lesinski3c153512014-07-23 17:34:34 -070087 nullCount++;
88 }
89 }
90
91 if (nullCount > 0) {
92 if (nullCount != mCurrentStats.length) {
93 // This is weird, but we shouldn't fail if something like this
94 // happens.
95 Slog.w(TAG, mLogPrefix + "Some stats have no latest available");
96 } else {
97 // This must be first boot.
98 }
99
100 // By calling loadActiveStats, we will
101 // generate new stats for each bucket.
Adam Lesinski66143fa2014-09-11 08:31:05 -0700102 loadActiveStats(currentTimeMillis, false);
Adam Lesinski3c153512014-07-23 17:34:34 -0700103 } else {
104 // Set up the expiry date to be one day from the latest daily stat.
105 // This may actually be today and we will rollover on the first event
106 // that is reported.
107 mDailyExpiryDate.setTimeInMillis(
Adam Lesinski35168002014-07-21 15:25:30 -0700108 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime);
Adam Lesinskid26bea32014-09-03 16:49:59 -0700109 mDailyExpiryDate.addDays(1);
110 mDailyExpiryDate.truncateToDay();
111 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
112 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) +
113 "(" + mDailyExpiryDate.getTimeInMillis() + ")");
Adam Lesinski3c153512014-07-23 17:34:34 -0700114 }
115
116 // Now close off any events that were open at the time this was saved.
Adam Lesinski35168002014-07-21 15:25:30 -0700117 for (IntervalStats stat : mCurrentStats) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700118 final int pkgCount = stat.packageStats.size();
Adam Lesinski3c153512014-07-23 17:34:34 -0700119 for (int i = 0; i < pkgCount; i++) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700120 UsageStats pkgStats = stat.packageStats.valueAt(i);
Adam Lesinski35168002014-07-21 15:25:30 -0700121 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
122 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
123 stat.update(pkgStats.mPackageName, stat.lastTimeSaved,
124 UsageEvents.Event.END_OF_DAY);
Adam Lesinski3c153512014-07-23 17:34:34 -0700125 notifyStatsChanged();
126 }
127 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700128
129 stat.updateConfigurationStats(null, stat.lastTimeSaved);
Adam Lesinski3c153512014-07-23 17:34:34 -0700130 }
131 }
132
Adam Lesinski66143fa2014-09-11 08:31:05 -0700133 void onTimeChanged(long oldTime, long newTime) {
134 persistActiveStats();
135 mDatabase.onTimeChanged(newTime - oldTime);
136 loadActiveStats(newTime, true);
137 }
138
Adam Lesinski35168002014-07-21 15:25:30 -0700139 void reportEvent(UsageEvents.Event event) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700140 if (DEBUG) {
Adam Lesinski9d960752014-08-25 14:48:12 -0700141 Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage
Adam Lesinski7f61e962014-09-02 16:43:52 -0700142 + "[" + event.mTimeStamp + "]: "
143 + eventToString(event.mEventType));
Adam Lesinski3c153512014-07-23 17:34:34 -0700144 }
145
Adam Lesinski7f61e962014-09-02 16:43:52 -0700146 if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700147 // Need to rollover
Adam Lesinski66143fa2014-09-11 08:31:05 -0700148 rolloverStats(event.mTimeStamp);
Adam Lesinski3c153512014-07-23 17:34:34 -0700149 }
150
Adam Lesinski7f61e962014-09-02 16:43:52 -0700151 final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY];
152
153 final Configuration newFullConfig = event.mConfiguration;
154 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE &&
155 currentDailyStats.activeConfiguration != null) {
156 // Make the event configuration a delta.
157 event.mConfiguration = Configuration.generateDelta(
158 currentDailyStats.activeConfiguration, newFullConfig);
Adam Lesinski35168002014-07-21 15:25:30 -0700159 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700160
161 // Add the event to the daily list.
162 if (currentDailyStats.events == null) {
163 currentDailyStats.events = new TimeSparseArray<>();
164 }
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800165 if (event.mEventType != UsageEvents.Event.INTERACTION) {
166 currentDailyStats.events.put(event.mTimeStamp, event);
167 }
Adam Lesinski35168002014-07-21 15:25:30 -0700168
169 for (IntervalStats stats : mCurrentStats) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700170 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) {
171 stats.updateConfigurationStats(newFullConfig, event.mTimeStamp);
172 } else {
173 stats.update(event.mPackage, event.mTimeStamp, event.mEventType);
174 }
Adam Lesinski3c153512014-07-23 17:34:34 -0700175 }
176
177 notifyStatsChanged();
178 }
179
Adam Lesinski7f61e962014-09-02 16:43:52 -0700180 private static final StatCombiner<UsageStats> sUsageStatsCombiner =
181 new StatCombiner<UsageStats>() {
182 @Override
183 public void combine(IntervalStats stats, boolean mutable,
184 List<UsageStats> accResult) {
185 if (!mutable) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700186 accResult.addAll(stats.packageStats.values());
Adam Lesinski7f61e962014-09-02 16:43:52 -0700187 return;
188 }
189
Adam Lesinski37a46b42014-09-05 15:38:05 -0700190 final int statCount = stats.packageStats.size();
Adam Lesinski7f61e962014-09-02 16:43:52 -0700191 for (int i = 0; i < statCount; i++) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700192 accResult.add(new UsageStats(stats.packageStats.valueAt(i)));
Adam Lesinski7f61e962014-09-02 16:43:52 -0700193 }
194 }
195 };
196
197 private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner =
198 new StatCombiner<ConfigurationStats>() {
199 @Override
200 public void combine(IntervalStats stats, boolean mutable,
201 List<ConfigurationStats> accResult) {
202 if (!mutable) {
203 accResult.addAll(stats.configurations.values());
204 return;
205 }
206
207 final int configCount = stats.configurations.size();
208 for (int i = 0; i < configCount; i++) {
209 accResult.add(new ConfigurationStats(stats.configurations.valueAt(i)));
210 }
211 }
212 };
213
214 /**
215 * Generic query method that selects the appropriate IntervalStats for the specified time range
216 * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner}
217 * provided to select the stats to use from the IntervalStats object.
218 */
Adam Lesinskid26bea32014-09-03 16:49:59 -0700219 private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime,
Adam Lesinski7f61e962014-09-02 16:43:52 -0700220 StatCombiner<T> combiner) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700221 if (intervalType == UsageStatsManager.INTERVAL_BEST) {
222 intervalType = mDatabase.findBestFitBucket(beginTime, endTime);
223 if (intervalType < 0) {
224 // Nothing saved to disk yet, so every stat is just as equal (no rollover has
225 // occurred.
226 intervalType = UsageStatsManager.INTERVAL_DAILY;
227 }
Adam Lesinski35168002014-07-21 15:25:30 -0700228 }
229
Adam Lesinskid26bea32014-09-03 16:49:59 -0700230 if (intervalType < 0 || intervalType >= mCurrentStats.length) {
Adam Lesinski35168002014-07-21 15:25:30 -0700231 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700232 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType);
Adam Lesinski35168002014-07-21 15:25:30 -0700233 }
234 return null;
235 }
236
Adam Lesinskid26bea32014-09-03 16:49:59 -0700237 final IntervalStats currentStats = mCurrentStats[intervalType];
Adam Lesinski35168002014-07-21 15:25:30 -0700238
Adam Lesinski3c153512014-07-23 17:34:34 -0700239 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700240 Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= "
Adam Lesinski35168002014-07-21 15:25:30 -0700241 + beginTime + " AND endTime < " + endTime);
Adam Lesinski3c153512014-07-23 17:34:34 -0700242 }
243
Adam Lesinskid26bea32014-09-03 16:49:59 -0700244 if (beginTime >= currentStats.endTime) {
245 if (DEBUG) {
246 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is "
247 + currentStats.endTime);
248 }
249 // Nothing newer available.
250 return null;
251 }
252
253 // Truncate the endTime to just before the in-memory stats. Then, we'll append the
254 // in-memory stats to the results (if necessary) so as to avoid writing to disk too
255 // often.
256 final long truncatedEndTime = Math.min(currentStats.beginTime, endTime);
257
258 // Get the stats from disk.
259 List<T> results = mDatabase.queryUsageStats(intervalType, beginTime,
260 truncatedEndTime, combiner);
Adam Lesinski3c153512014-07-23 17:34:34 -0700261 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700262 Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk");
263 Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime +
264 " endTime=" + currentStats.endTime);
265 }
266
267 // Now check if the in-memory stats match the range and add them if they do.
268 if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) {
269 if (DEBUG) {
270 Slog.d(TAG, mLogPrefix + "Returning in-memory stats");
271 }
272
273 if (results == null) {
274 results = new ArrayList<>();
275 }
276 combiner.combine(currentStats, true, results);
277 }
278
279 if (DEBUG) {
280 Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0));
Adam Lesinski3c153512014-07-23 17:34:34 -0700281 }
282 return results;
283 }
284
Adam Lesinski7f61e962014-09-02 16:43:52 -0700285 List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) {
286 return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner);
287 }
288
289 List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) {
290 return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner);
291 }
292
Adam Lesinskid26bea32014-09-03 16:49:59 -0700293 UsageEvents queryEvents(final long beginTime, final long endTime) {
294 final ArraySet<String> names = new ArraySet<>();
295 List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY,
296 beginTime, endTime, new StatCombiner<UsageEvents.Event>() {
297 @Override
298 public void combine(IntervalStats stats, boolean mutable,
299 List<UsageEvents.Event> accumulatedResult) {
300 if (stats.events == null) {
301 return;
302 }
Adam Lesinski35168002014-07-21 15:25:30 -0700303
Adam Lesinskid26bea32014-09-03 16:49:59 -0700304 final int startIndex = stats.events.closestIndexOnOrAfter(beginTime);
305 if (startIndex < 0) {
306 return;
307 }
Adam Lesinski35168002014-07-21 15:25:30 -0700308
Adam Lesinskid26bea32014-09-03 16:49:59 -0700309 final int size = stats.events.size();
310 for (int i = startIndex; i < size; i++) {
311 if (stats.events.keyAt(i) >= endTime) {
312 return;
313 }
Adam Lesinski35168002014-07-21 15:25:30 -0700314
Adam Lesinskid26bea32014-09-03 16:49:59 -0700315 final UsageEvents.Event event = stats.events.valueAt(i);
316 names.add(event.mPackage);
317 if (event.mClass != null) {
318 names.add(event.mClass);
319 }
320 accumulatedResult.add(event);
321 }
322 }
323 });
324
325 if (results == null || results.isEmpty()) {
326 return null;
Adam Lesinski35168002014-07-21 15:25:30 -0700327 }
328
Adam Lesinskid26bea32014-09-03 16:49:59 -0700329 String[] table = names.toArray(new String[names.size()]);
330 Arrays.sort(table);
331 return new UsageEvents(results, table);
Adam Lesinski35168002014-07-21 15:25:30 -0700332 }
333
Amith Yamasanib0ff3222015-03-04 09:56:14 -0800334 long getLastPackageAccessTime(String packageName) {
335 final IntervalStats yearly = mCurrentStats[UsageStatsManager.INTERVAL_YEARLY];
336 UsageStats packageUsage;
337 if ((packageUsage = yearly.packageStats.get(packageName)) == null) {
338 return -1;
339 } else {
340 return packageUsage.getLastTimeUsed();
341 }
342 }
343
Adam Lesinski3c153512014-07-23 17:34:34 -0700344 void persistActiveStats() {
345 if (mStatsChanged) {
346 Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk");
347 try {
348 for (int i = 0; i < mCurrentStats.length; i++) {
349 mDatabase.putUsageStats(i, mCurrentStats[i]);
350 }
351 mStatsChanged = false;
352 } catch (IOException e) {
353 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e);
354 }
355 }
356 }
357
Adam Lesinski66143fa2014-09-11 08:31:05 -0700358 private void rolloverStats(final long currentTimeMillis) {
359 final long startTime = SystemClock.elapsedRealtime();
Adam Lesinski3c153512014-07-23 17:34:34 -0700360 Slog.i(TAG, mLogPrefix + "Rolling over usage stats");
361
362 // Finish any ongoing events with an END_OF_DAY event. Make a note of which components
363 // need a new CONTINUE_PREVIOUS_DAY entry.
Adam Lesinski7f61e962014-09-02 16:43:52 -0700364 final Configuration previousConfig =
365 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration;
Adam Lesinski3c153512014-07-23 17:34:34 -0700366 ArraySet<String> continuePreviousDay = new ArraySet<>();
Adam Lesinski35168002014-07-21 15:25:30 -0700367 for (IntervalStats stat : mCurrentStats) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700368 final int pkgCount = stat.packageStats.size();
Adam Lesinski3c153512014-07-23 17:34:34 -0700369 for (int i = 0; i < pkgCount; i++) {
Adam Lesinski37a46b42014-09-05 15:38:05 -0700370 UsageStats pkgStats = stat.packageStats.valueAt(i);
Adam Lesinski35168002014-07-21 15:25:30 -0700371 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
372 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700373 continuePreviousDay.add(pkgStats.mPackageName);
Adam Lesinski7f61e962014-09-02 16:43:52 -0700374 stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1,
375 UsageEvents.Event.END_OF_DAY);
Adam Lesinski66143fa2014-09-11 08:31:05 -0700376 notifyStatsChanged();
Adam Lesinski3c153512014-07-23 17:34:34 -0700377 }
378 }
Adam Lesinski7f61e962014-09-02 16:43:52 -0700379
380 stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1);
Adam Lesinski3c153512014-07-23 17:34:34 -0700381 }
382
383 persistActiveStats();
Adam Lesinski66143fa2014-09-11 08:31:05 -0700384 mDatabase.prune(currentTimeMillis);
385 loadActiveStats(currentTimeMillis, false);
Adam Lesinski3c153512014-07-23 17:34:34 -0700386
387 final int continueCount = continuePreviousDay.size();
388 for (int i = 0; i < continueCount; i++) {
389 String name = continuePreviousDay.valueAt(i);
Adam Lesinski7f61e962014-09-02 16:43:52 -0700390 final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime;
Adam Lesinski35168002014-07-21 15:25:30 -0700391 for (IntervalStats stat : mCurrentStats) {
Adam Lesinski7f61e962014-09-02 16:43:52 -0700392 stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY);
393 stat.updateConfigurationStats(previousConfig, beginTime);
Adam Lesinski66143fa2014-09-11 08:31:05 -0700394 notifyStatsChanged();
Adam Lesinski3c153512014-07-23 17:34:34 -0700395 }
396 }
397 persistActiveStats();
398
Adam Lesinski66143fa2014-09-11 08:31:05 -0700399 final long totalTime = SystemClock.elapsedRealtime() - startTime;
Adam Lesinski3c153512014-07-23 17:34:34 -0700400 Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime
401 + " milliseconds");
402 }
403
404 private void notifyStatsChanged() {
405 if (!mStatsChanged) {
406 mStatsChanged = true;
407 mListener.onStatsUpdated();
408 }
409 }
410
Adam Lesinski66143fa2014-09-11 08:31:05 -0700411 /**
412 * @param force To force all in-memory stats to be reloaded.
413 */
414 private void loadActiveStats(final long currentTimeMillis, boolean force) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700415 final UnixCalendar tempCal = mDailyExpiryDate;
416 for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) {
Adam Lesinski66143fa2014-09-11 08:31:05 -0700417 tempCal.setTimeInMillis(currentTimeMillis);
Adam Lesinskid26bea32014-09-03 16:49:59 -0700418 UnixCalendar.truncateTo(tempCal, intervalType);
Adam Lesinski3c153512014-07-23 17:34:34 -0700419
Adam Lesinski66143fa2014-09-11 08:31:05 -0700420 if (!force && mCurrentStats[intervalType] != null &&
Adam Lesinskid26bea32014-09-03 16:49:59 -0700421 mCurrentStats[intervalType].beginTime == tempCal.getTimeInMillis()) {
Adam Lesinski3c153512014-07-23 17:34:34 -0700422 // These are the same, no need to load them (in memory stats are always newer
423 // than persisted stats).
424 continue;
425 }
426
Adam Lesinskid26bea32014-09-03 16:49:59 -0700427 final long lastBeginTime = mDatabase.getLatestUsageStatsBeginTime(intervalType);
Adam Lesinski66143fa2014-09-11 08:31:05 -0700428 if (lastBeginTime >= tempCal.getTimeInMillis()) {
Adam Lesinski35168002014-07-21 15:25:30 -0700429 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700430 Slog.d(TAG, mLogPrefix + "Loading existing stats @ " +
431 sDateFormat.format(lastBeginTime) + "(" + lastBeginTime +
432 ") for interval " + intervalType);
Adam Lesinski35168002014-07-21 15:25:30 -0700433 }
Adam Lesinskid26bea32014-09-03 16:49:59 -0700434 mCurrentStats[intervalType] = mDatabase.getLatestUsageStats(intervalType);
Adam Lesinski3c153512014-07-23 17:34:34 -0700435 } else {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700436 mCurrentStats[intervalType] = null;
Adam Lesinski35168002014-07-21 15:25:30 -0700437 }
438
Adam Lesinskid26bea32014-09-03 16:49:59 -0700439 if (mCurrentStats[intervalType] == null) {
Adam Lesinski35168002014-07-21 15:25:30 -0700440 if (DEBUG) {
Adam Lesinskid26bea32014-09-03 16:49:59 -0700441 Slog.d(TAG, "Creating new stats @ " +
442 sDateFormat.format(tempCal.getTimeInMillis()) + "(" +
443 tempCal.getTimeInMillis() + ") for interval " + intervalType);
Adam Lesinski35168002014-07-21 15:25:30 -0700444
445 }
Adam Lesinskid26bea32014-09-03 16:49:59 -0700446 mCurrentStats[intervalType] = new IntervalStats();
447 mCurrentStats[intervalType].beginTime = tempCal.getTimeInMillis();
Adam Lesinski66143fa2014-09-11 08:31:05 -0700448 mCurrentStats[intervalType].endTime = currentTimeMillis;
Adam Lesinski3c153512014-07-23 17:34:34 -0700449 }
450 }
451 mStatsChanged = false;
Adam Lesinski66143fa2014-09-11 08:31:05 -0700452 mDailyExpiryDate.setTimeInMillis(currentTimeMillis);
Adam Lesinskid26bea32014-09-03 16:49:59 -0700453 mDailyExpiryDate.addDays(1);
454 mDailyExpiryDate.truncateToDay();
455 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " +
456 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" +
457 tempCal.getTimeInMillis() + ")");
Adam Lesinski3c153512014-07-23 17:34:34 -0700458 }
459
Adam Lesinski1bb18c42014-08-18 12:21:34 -0700460 //
461 // -- DUMP related methods --
462 //
463
464 void checkin(final IndentingPrintWriter pw) {
465 mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() {
466 @Override
467 public boolean checkin(IntervalStats stats) {
468 printIntervalStats(pw, stats, false);
469 return true;
470 }
471 });
472 }
473
474 void dump(IndentingPrintWriter pw) {
475 // This is not a check-in, only dump in-memory stats.
476 for (int interval = 0; interval < mCurrentStats.length; interval++) {
477 pw.print("In-memory ");
478 pw.print(intervalToString(interval));
479 pw.println(" stats");
480 printIntervalStats(pw, mCurrentStats[interval], true);
481 }
482 }
483
484 private String formatDateTime(long dateTime, boolean pretty) {
485 if (pretty) {
486 return "\"" + DateUtils.formatDateTime(mContext, dateTime, sDateFormatFlags) + "\"";
487 }
488 return Long.toString(dateTime);
489 }
490
491 private String formatElapsedTime(long elapsedTime, boolean pretty) {
492 if (pretty) {
493 return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\"";
494 }
495 return Long.toString(elapsedTime);
496 }
497
498 void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates) {
499 if (prettyDates) {
500 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext,
501 stats.beginTime, stats.endTime, sDateFormatFlags) + "\"");
502 } else {
503 pw.printPair("beginTime", stats.beginTime);
504 pw.printPair("endTime", stats.endTime);
505 }
506 pw.println();
507 pw.increaseIndent();
508 pw.println("packages");
509 pw.increaseIndent();
510 final ArrayMap<String, UsageStats> pkgStats = stats.packageStats;
511 final int pkgCount = pkgStats.size();
512 for (int i = 0; i < pkgCount; i++) {
513 final UsageStats usageStats = pkgStats.valueAt(i);
514 pw.printPair("package", usageStats.mPackageName);
515 pw.printPair("totalTime", formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates));
516 pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates));
517 pw.println();
518 }
519 pw.decreaseIndent();
520
521 pw.println("configurations");
522 pw.increaseIndent();
523 final ArrayMap<Configuration, ConfigurationStats> configStats =
524 stats.configurations;
525 final int configCount = configStats.size();
526 for (int i = 0; i < configCount; i++) {
527 final ConfigurationStats config = configStats.valueAt(i);
528 pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration));
529 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates));
530 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates));
531 pw.printPair("count", config.mActivationCount);
532 pw.println();
533 }
534 pw.decreaseIndent();
535
536 pw.println("events");
537 pw.increaseIndent();
538 final TimeSparseArray<UsageEvents.Event> events = stats.events;
539 final int eventCount = events != null ? events.size() : 0;
540 for (int i = 0; i < eventCount; i++) {
541 final UsageEvents.Event event = events.valueAt(i);
542 pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates));
543 pw.printPair("type", eventToString(event.mEventType));
544 pw.printPair("package", event.mPackage);
545 if (event.mClass != null) {
546 pw.printPair("class", event.mClass);
547 }
548 if (event.mConfiguration != null) {
549 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration));
550 }
551 pw.println();
552 }
553 pw.decreaseIndent();
554 pw.decreaseIndent();
555 }
556
557 private static String intervalToString(int interval) {
558 switch (interval) {
559 case UsageStatsManager.INTERVAL_DAILY:
560 return "daily";
561 case UsageStatsManager.INTERVAL_WEEKLY:
562 return "weekly";
563 case UsageStatsManager.INTERVAL_MONTHLY:
564 return "monthly";
565 case UsageStatsManager.INTERVAL_YEARLY:
566 return "yearly";
567 default:
568 return "?";
569 }
570 }
Adam Lesinski3c153512014-07-23 17:34:34 -0700571
572 private static String eventToString(int eventType) {
573 switch (eventType) {
Adam Lesinski35168002014-07-21 15:25:30 -0700574 case UsageEvents.Event.NONE:
Adam Lesinski3c153512014-07-23 17:34:34 -0700575 return "NONE";
Adam Lesinski35168002014-07-21 15:25:30 -0700576 case UsageEvents.Event.MOVE_TO_BACKGROUND:
Adam Lesinski3c153512014-07-23 17:34:34 -0700577 return "MOVE_TO_BACKGROUND";
Adam Lesinski35168002014-07-21 15:25:30 -0700578 case UsageEvents.Event.MOVE_TO_FOREGROUND:
Adam Lesinski3c153512014-07-23 17:34:34 -0700579 return "MOVE_TO_FOREGROUND";
Adam Lesinski35168002014-07-21 15:25:30 -0700580 case UsageEvents.Event.END_OF_DAY:
Adam Lesinski3c153512014-07-23 17:34:34 -0700581 return "END_OF_DAY";
Adam Lesinski35168002014-07-21 15:25:30 -0700582 case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
Adam Lesinski3c153512014-07-23 17:34:34 -0700583 return "CONTINUE_PREVIOUS_DAY";
Adam Lesinski7f61e962014-09-02 16:43:52 -0700584 case UsageEvents.Event.CONFIGURATION_CHANGE:
585 return "CONFIGURATION_CHANGE";
Adam Lesinski978a1ed2015-03-02 11:37:24 -0800586 case UsageEvents.Event.INTERACTION:
587 return "INTERACTION";
Adam Lesinski3c153512014-07-23 17:34:34 -0700588 default:
589 return "UNKNOWN";
590 }
591 }
592}