1 /** 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy 6 * of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 17 package com.android.server.usage; 18 19 import android.app.usage.ConfigurationStats; 20 import android.app.usage.TimeSparseArray; 21 import android.app.usage.UsageEvents; 22 import android.app.usage.UsageStats; 23 import android.app.usage.UsageStatsManager; 24 import android.content.res.Configuration; 25 import android.os.SystemClock; 26 import android.content.Context; 27 import android.text.format.DateUtils; 28 import android.util.ArrayMap; 29 import android.util.ArraySet; 30 import android.util.Slog; 31 32 import com.android.internal.util.IndentingPrintWriter; 33 import com.android.server.usage.UsageStatsDatabase.StatCombiner; 34 35 import java.io.File; 36 import java.io.IOException; 37 import java.text.SimpleDateFormat; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.List; 41 42 /** 43 * A per-user UsageStatsService. All methods are meant to be called with the main lock held 44 * in UsageStatsService. 45 */ 46 class 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"); 50 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; 55 56 private final Context mContext; 57 private final UsageStatsDatabase mDatabase; 58 private final IntervalStats[] mCurrentStats; 59 private boolean mStatsChanged = false; 60 private final UnixCalendar mDailyExpiryDate; 61 private final StatsUpdatedListener mListener; 62 private final String mLogPrefix; 63 private final int mUserId; 64 65 private static final long[] INTERVAL_LENGTH = new long[] { 66 UnixCalendar.DAY_IN_MILLIS, UnixCalendar.WEEK_IN_MILLIS, 67 UnixCalendar.MONTH_IN_MILLIS, UnixCalendar.YEAR_IN_MILLIS 68 }; 69 70 interface StatsUpdatedListener { onStatsUpdated()71 void onStatsUpdated(); onStatsReloaded()72 void onStatsReloaded(); 73 /** 74 * Callback that a system update was detected 75 * @param mUserId user that needs to be initialized 76 */ onNewUpdate(int mUserId)77 void onNewUpdate(int mUserId); 78 } 79 UserUsageStatsService(Context context, int userId, File usageStatsDir, StatsUpdatedListener listener)80 UserUsageStatsService(Context context, int userId, File usageStatsDir, 81 StatsUpdatedListener listener) { 82 mContext = context; 83 mDailyExpiryDate = new UnixCalendar(0); 84 mDatabase = new UsageStatsDatabase(usageStatsDir); 85 mCurrentStats = new IntervalStats[UsageStatsManager.INTERVAL_COUNT]; 86 mListener = listener; 87 mLogPrefix = "User[" + Integer.toString(userId) + "] "; 88 mUserId = userId; 89 } 90 init(final long currentTimeMillis)91 void init(final long currentTimeMillis) { 92 mDatabase.init(currentTimeMillis); 93 94 int nullCount = 0; 95 for (int i = 0; i < mCurrentStats.length; i++) { 96 mCurrentStats[i] = mDatabase.getLatestUsageStats(i); 97 if (mCurrentStats[i] == null) { 98 // Find out how many intervals we don't have data for. 99 // Ideally it should be all or none. 100 nullCount++; 101 } 102 } 103 104 if (nullCount > 0) { 105 if (nullCount != mCurrentStats.length) { 106 // This is weird, but we shouldn't fail if something like this 107 // happens. 108 Slog.w(TAG, mLogPrefix + "Some stats have no latest available"); 109 } else { 110 // This must be first boot. 111 } 112 113 // By calling loadActiveStats, we will 114 // generate new stats for each bucket. 115 loadActiveStats(currentTimeMillis); 116 } else { 117 // Set up the expiry date to be one day from the latest daily stat. 118 // This may actually be today and we will rollover on the first event 119 // that is reported. 120 updateRolloverDeadline(); 121 } 122 123 // Now close off any events that were open at the time this was saved. 124 for (IntervalStats stat : mCurrentStats) { 125 final int pkgCount = stat.packageStats.size(); 126 for (int i = 0; i < pkgCount; i++) { 127 UsageStats pkgStats = stat.packageStats.valueAt(i); 128 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || 129 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { 130 stat.update(pkgStats.mPackageName, stat.lastTimeSaved, 131 UsageEvents.Event.END_OF_DAY); 132 notifyStatsChanged(); 133 } 134 } 135 136 stat.updateConfigurationStats(null, stat.lastTimeSaved); 137 } 138 139 if (mDatabase.isNewUpdate()) { 140 notifyNewUpdate(); 141 } 142 } 143 onTimeChanged(long oldTime, long newTime)144 void onTimeChanged(long oldTime, long newTime) { 145 persistActiveStats(); 146 mDatabase.onTimeChanged(newTime - oldTime); 147 loadActiveStats(newTime); 148 } 149 reportEvent(UsageEvents.Event event)150 void reportEvent(UsageEvents.Event event) { 151 if (DEBUG) { 152 Slog.d(TAG, mLogPrefix + "Got usage event for " + event.mPackage 153 + "[" + event.mTimeStamp + "]: " 154 + eventToString(event.mEventType)); 155 } 156 157 if (event.mTimeStamp >= mDailyExpiryDate.getTimeInMillis()) { 158 // Need to rollover 159 rolloverStats(event.mTimeStamp); 160 } 161 162 final IntervalStats currentDailyStats = mCurrentStats[UsageStatsManager.INTERVAL_DAILY]; 163 164 final Configuration newFullConfig = event.mConfiguration; 165 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE && 166 currentDailyStats.activeConfiguration != null) { 167 // Make the event configuration a delta. 168 event.mConfiguration = Configuration.generateDelta( 169 currentDailyStats.activeConfiguration, newFullConfig); 170 } 171 172 // Add the event to the daily list. 173 if (currentDailyStats.events == null) { 174 currentDailyStats.events = new TimeSparseArray<>(); 175 } 176 if (event.mEventType != UsageEvents.Event.SYSTEM_INTERACTION) { 177 currentDailyStats.events.put(event.mTimeStamp, event); 178 } 179 180 for (IntervalStats stats : mCurrentStats) { 181 if (event.mEventType == UsageEvents.Event.CONFIGURATION_CHANGE) { 182 stats.updateConfigurationStats(newFullConfig, event.mTimeStamp); 183 } else if (event.mEventType == UsageEvents.Event.CHOOSER_ACTION) { 184 stats.updateChooserCounts(event.mPackage, event.mContentType, event.mAction); 185 String[] annotations = event.mContentAnnotations; 186 if (annotations != null) { 187 for (String annotation : annotations) { 188 stats.updateChooserCounts(event.mPackage, annotation, event.mAction); 189 } 190 } 191 } else { 192 stats.update(event.mPackage, event.mTimeStamp, event.mEventType); 193 } 194 } 195 196 notifyStatsChanged(); 197 } 198 199 private static final StatCombiner<UsageStats> sUsageStatsCombiner = 200 new StatCombiner<UsageStats>() { 201 @Override 202 public void combine(IntervalStats stats, boolean mutable, 203 List<UsageStats> accResult) { 204 if (!mutable) { 205 accResult.addAll(stats.packageStats.values()); 206 return; 207 } 208 209 final int statCount = stats.packageStats.size(); 210 for (int i = 0; i < statCount; i++) { 211 accResult.add(new UsageStats(stats.packageStats.valueAt(i))); 212 } 213 } 214 }; 215 216 private static final StatCombiner<ConfigurationStats> sConfigStatsCombiner = 217 new StatCombiner<ConfigurationStats>() { 218 @Override 219 public void combine(IntervalStats stats, boolean mutable, 220 List<ConfigurationStats> accResult) { 221 if (!mutable) { 222 accResult.addAll(stats.configurations.values()); 223 return; 224 } 225 226 final int configCount = stats.configurations.size(); 227 for (int i = 0; i < configCount; i++) { 228 accResult.add(new ConfigurationStats(stats.configurations.valueAt(i))); 229 } 230 } 231 }; 232 233 /** 234 * Generic query method that selects the appropriate IntervalStats for the specified time range 235 * and bucket, then calls the {@link com.android.server.usage.UsageStatsDatabase.StatCombiner} 236 * provided to select the stats to use from the IntervalStats object. 237 */ queryStats(int intervalType, final long beginTime, final long endTime, StatCombiner<T> combiner)238 private <T> List<T> queryStats(int intervalType, final long beginTime, final long endTime, 239 StatCombiner<T> combiner) { 240 if (intervalType == UsageStatsManager.INTERVAL_BEST) { 241 intervalType = mDatabase.findBestFitBucket(beginTime, endTime); 242 if (intervalType < 0) { 243 // Nothing saved to disk yet, so every stat is just as equal (no rollover has 244 // occurred. 245 intervalType = UsageStatsManager.INTERVAL_DAILY; 246 } 247 } 248 249 if (intervalType < 0 || intervalType >= mCurrentStats.length) { 250 if (DEBUG) { 251 Slog.d(TAG, mLogPrefix + "Bad intervalType used " + intervalType); 252 } 253 return null; 254 } 255 256 final IntervalStats currentStats = mCurrentStats[intervalType]; 257 258 if (DEBUG) { 259 Slog.d(TAG, mLogPrefix + "SELECT * FROM " + intervalType + " WHERE beginTime >= " 260 + beginTime + " AND endTime < " + endTime); 261 } 262 263 if (beginTime >= currentStats.endTime) { 264 if (DEBUG) { 265 Slog.d(TAG, mLogPrefix + "Requesting stats after " + beginTime + " but latest is " 266 + currentStats.endTime); 267 } 268 // Nothing newer available. 269 return null; 270 } 271 272 // Truncate the endTime to just before the in-memory stats. Then, we'll append the 273 // in-memory stats to the results (if necessary) so as to avoid writing to disk too 274 // often. 275 final long truncatedEndTime = Math.min(currentStats.beginTime, endTime); 276 277 // Get the stats from disk. 278 List<T> results = mDatabase.queryUsageStats(intervalType, beginTime, 279 truncatedEndTime, combiner); 280 if (DEBUG) { 281 Slog.d(TAG, "Got " + (results != null ? results.size() : 0) + " results from disk"); 282 Slog.d(TAG, "Current stats beginTime=" + currentStats.beginTime + 283 " endTime=" + currentStats.endTime); 284 } 285 286 // Now check if the in-memory stats match the range and add them if they do. 287 if (beginTime < currentStats.endTime && endTime > currentStats.beginTime) { 288 if (DEBUG) { 289 Slog.d(TAG, mLogPrefix + "Returning in-memory stats"); 290 } 291 292 if (results == null) { 293 results = new ArrayList<>(); 294 } 295 combiner.combine(currentStats, true, results); 296 } 297 298 if (DEBUG) { 299 Slog.d(TAG, mLogPrefix + "Results: " + (results != null ? results.size() : 0)); 300 } 301 return results; 302 } 303 queryUsageStats(int bucketType, long beginTime, long endTime)304 List<UsageStats> queryUsageStats(int bucketType, long beginTime, long endTime) { 305 return queryStats(bucketType, beginTime, endTime, sUsageStatsCombiner); 306 } 307 queryConfigurationStats(int bucketType, long beginTime, long endTime)308 List<ConfigurationStats> queryConfigurationStats(int bucketType, long beginTime, long endTime) { 309 return queryStats(bucketType, beginTime, endTime, sConfigStatsCombiner); 310 } 311 queryEvents(final long beginTime, final long endTime, boolean obfuscateInstantApps)312 UsageEvents queryEvents(final long beginTime, final long endTime, 313 boolean obfuscateInstantApps) { 314 final ArraySet<String> names = new ArraySet<>(); 315 List<UsageEvents.Event> results = queryStats(UsageStatsManager.INTERVAL_DAILY, 316 beginTime, endTime, new StatCombiner<UsageEvents.Event>() { 317 @Override 318 public void combine(IntervalStats stats, boolean mutable, 319 List<UsageEvents.Event> accumulatedResult) { 320 if (stats.events == null) { 321 return; 322 } 323 324 final int startIndex = stats.events.closestIndexOnOrAfter(beginTime); 325 if (startIndex < 0) { 326 return; 327 } 328 329 final int size = stats.events.size(); 330 for (int i = startIndex; i < size; i++) { 331 if (stats.events.keyAt(i) >= endTime) { 332 return; 333 } 334 335 UsageEvents.Event event = stats.events.valueAt(i); 336 if (obfuscateInstantApps) { 337 event = event.getObfuscatedIfInstantApp(); 338 } 339 names.add(event.mPackage); 340 if (event.mClass != null) { 341 names.add(event.mClass); 342 } 343 accumulatedResult.add(event); 344 } 345 } 346 }); 347 348 if (results == null || results.isEmpty()) { 349 return null; 350 } 351 352 String[] table = names.toArray(new String[names.size()]); 353 Arrays.sort(table); 354 return new UsageEvents(results, table); 355 } 356 persistActiveStats()357 void persistActiveStats() { 358 if (mStatsChanged) { 359 Slog.i(TAG, mLogPrefix + "Flushing usage stats to disk"); 360 try { 361 for (int i = 0; i < mCurrentStats.length; i++) { 362 mDatabase.putUsageStats(i, mCurrentStats[i]); 363 } 364 mStatsChanged = false; 365 } catch (IOException e) { 366 Slog.e(TAG, mLogPrefix + "Failed to persist active stats", e); 367 } 368 } 369 } 370 rolloverStats(final long currentTimeMillis)371 private void rolloverStats(final long currentTimeMillis) { 372 final long startTime = SystemClock.elapsedRealtime(); 373 Slog.i(TAG, mLogPrefix + "Rolling over usage stats"); 374 375 // Finish any ongoing events with an END_OF_DAY event. Make a note of which components 376 // need a new CONTINUE_PREVIOUS_DAY entry. 377 final Configuration previousConfig = 378 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].activeConfiguration; 379 ArraySet<String> continuePreviousDay = new ArraySet<>(); 380 for (IntervalStats stat : mCurrentStats) { 381 final int pkgCount = stat.packageStats.size(); 382 for (int i = 0; i < pkgCount; i++) { 383 UsageStats pkgStats = stat.packageStats.valueAt(i); 384 if (pkgStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND || 385 pkgStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) { 386 continuePreviousDay.add(pkgStats.mPackageName); 387 stat.update(pkgStats.mPackageName, mDailyExpiryDate.getTimeInMillis() - 1, 388 UsageEvents.Event.END_OF_DAY); 389 notifyStatsChanged(); 390 } 391 } 392 393 stat.updateConfigurationStats(null, mDailyExpiryDate.getTimeInMillis() - 1); 394 } 395 396 persistActiveStats(); 397 mDatabase.prune(currentTimeMillis); 398 loadActiveStats(currentTimeMillis); 399 400 final int continueCount = continuePreviousDay.size(); 401 for (int i = 0; i < continueCount; i++) { 402 String name = continuePreviousDay.valueAt(i); 403 final long beginTime = mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime; 404 for (IntervalStats stat : mCurrentStats) { 405 stat.update(name, beginTime, UsageEvents.Event.CONTINUE_PREVIOUS_DAY); 406 stat.updateConfigurationStats(previousConfig, beginTime); 407 notifyStatsChanged(); 408 } 409 } 410 persistActiveStats(); 411 412 final long totalTime = SystemClock.elapsedRealtime() - startTime; 413 Slog.i(TAG, mLogPrefix + "Rolling over usage stats complete. Took " + totalTime 414 + " milliseconds"); 415 } 416 notifyStatsChanged()417 private void notifyStatsChanged() { 418 if (!mStatsChanged) { 419 mStatsChanged = true; 420 mListener.onStatsUpdated(); 421 } 422 } 423 notifyNewUpdate()424 private void notifyNewUpdate() { 425 mListener.onNewUpdate(mUserId); 426 } 427 loadActiveStats(final long currentTimeMillis)428 private void loadActiveStats(final long currentTimeMillis) { 429 for (int intervalType = 0; intervalType < mCurrentStats.length; intervalType++) { 430 final IntervalStats stats = mDatabase.getLatestUsageStats(intervalType); 431 if (stats != null && currentTimeMillis - 500 >= stats.endTime && 432 currentTimeMillis < stats.beginTime + INTERVAL_LENGTH[intervalType]) { 433 if (DEBUG) { 434 Slog.d(TAG, mLogPrefix + "Loading existing stats @ " + 435 sDateFormat.format(stats.beginTime) + "(" + stats.beginTime + 436 ") for interval " + intervalType); 437 } 438 mCurrentStats[intervalType] = stats; 439 } else { 440 // No good fit remains. 441 if (DEBUG) { 442 Slog.d(TAG, "Creating new stats @ " + 443 sDateFormat.format(currentTimeMillis) + "(" + 444 currentTimeMillis + ") for interval " + intervalType); 445 } 446 447 mCurrentStats[intervalType] = new IntervalStats(); 448 mCurrentStats[intervalType].beginTime = currentTimeMillis; 449 mCurrentStats[intervalType].endTime = currentTimeMillis + 1; 450 } 451 } 452 453 mStatsChanged = false; 454 updateRolloverDeadline(); 455 456 // Tell the listener that the stats reloaded, which may have changed idle states. 457 mListener.onStatsReloaded(); 458 } 459 updateRolloverDeadline()460 private void updateRolloverDeadline() { 461 mDailyExpiryDate.setTimeInMillis( 462 mCurrentStats[UsageStatsManager.INTERVAL_DAILY].beginTime); 463 mDailyExpiryDate.addDays(1); 464 Slog.i(TAG, mLogPrefix + "Rollover scheduled @ " + 465 sDateFormat.format(mDailyExpiryDate.getTimeInMillis()) + "(" + 466 mDailyExpiryDate.getTimeInMillis() + ")"); 467 } 468 469 // 470 // -- DUMP related methods -- 471 // 472 checkin(final IndentingPrintWriter pw)473 void checkin(final IndentingPrintWriter pw) { 474 mDatabase.checkinDailyFiles(new UsageStatsDatabase.CheckinAction() { 475 @Override 476 public boolean checkin(IntervalStats stats) { 477 printIntervalStats(pw, stats, false); 478 return true; 479 } 480 }); 481 } 482 dump(IndentingPrintWriter pw)483 void dump(IndentingPrintWriter pw) { 484 // This is not a check-in, only dump in-memory stats. 485 for (int interval = 0; interval < mCurrentStats.length; interval++) { 486 pw.print("In-memory "); 487 pw.print(intervalToString(interval)); 488 pw.println(" stats"); 489 printIntervalStats(pw, mCurrentStats[interval], true); 490 } 491 } 492 formatDateTime(long dateTime, boolean pretty)493 private String formatDateTime(long dateTime, boolean pretty) { 494 if (pretty) { 495 return "\"" + DateUtils.formatDateTime(mContext, dateTime, sDateFormatFlags) + "\""; 496 } 497 return Long.toString(dateTime); 498 } 499 formatElapsedTime(long elapsedTime, boolean pretty)500 private String formatElapsedTime(long elapsedTime, boolean pretty) { 501 if (pretty) { 502 return "\"" + DateUtils.formatElapsedTime(elapsedTime / 1000) + "\""; 503 } 504 return Long.toString(elapsedTime); 505 } 506 printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, boolean prettyDates)507 void printIntervalStats(IndentingPrintWriter pw, IntervalStats stats, 508 boolean prettyDates) { 509 if (prettyDates) { 510 pw.printPair("timeRange", "\"" + DateUtils.formatDateRange(mContext, 511 stats.beginTime, stats.endTime, sDateFormatFlags) + "\""); 512 } else { 513 pw.printPair("beginTime", stats.beginTime); 514 pw.printPair("endTime", stats.endTime); 515 } 516 pw.println(); 517 pw.increaseIndent(); 518 pw.println("packages"); 519 pw.increaseIndent(); 520 final ArrayMap<String, UsageStats> pkgStats = stats.packageStats; 521 final int pkgCount = pkgStats.size(); 522 for (int i = 0; i < pkgCount; i++) { 523 final UsageStats usageStats = pkgStats.valueAt(i); 524 pw.printPair("package", usageStats.mPackageName); 525 pw.printPair("totalTime", 526 formatElapsedTime(usageStats.mTotalTimeInForeground, prettyDates)); 527 pw.printPair("lastTime", formatDateTime(usageStats.mLastTimeUsed, prettyDates)); 528 pw.println(); 529 } 530 pw.decreaseIndent(); 531 532 pw.println(); 533 pw.println("ChooserCounts"); 534 pw.increaseIndent(); 535 for (UsageStats usageStats : pkgStats.values()) { 536 pw.printPair("package", usageStats.mPackageName); 537 if (usageStats.mChooserCounts != null) { 538 final int chooserCountSize = usageStats.mChooserCounts.size(); 539 for (int i = 0; i < chooserCountSize; i++) { 540 final String action = usageStats.mChooserCounts.keyAt(i); 541 final ArrayMap<String, Integer> counts = usageStats.mChooserCounts.valueAt(i); 542 final int annotationSize = counts.size(); 543 for (int j = 0; j < annotationSize; j++) { 544 final String key = counts.keyAt(j); 545 final int count = counts.valueAt(j); 546 if (count != 0) { 547 pw.printPair("ChooserCounts", action + ":" + key + " is " + 548 Integer.toString(count)); 549 pw.println(); 550 } 551 } 552 } 553 } 554 pw.println(); 555 } 556 pw.decreaseIndent(); 557 558 pw.println("configurations"); 559 pw.increaseIndent(); 560 final ArrayMap<Configuration, ConfigurationStats> configStats = stats.configurations; 561 final int configCount = configStats.size(); 562 for (int i = 0; i < configCount; i++) { 563 final ConfigurationStats config = configStats.valueAt(i); 564 pw.printPair("config", Configuration.resourceQualifierString(config.mConfiguration)); 565 pw.printPair("totalTime", formatElapsedTime(config.mTotalTimeActive, prettyDates)); 566 pw.printPair("lastTime", formatDateTime(config.mLastTimeActive, prettyDates)); 567 pw.printPair("count", config.mActivationCount); 568 pw.println(); 569 } 570 pw.decreaseIndent(); 571 572 pw.println("events"); 573 pw.increaseIndent(); 574 final TimeSparseArray<UsageEvents.Event> events = stats.events; 575 final int eventCount = events != null ? events.size() : 0; 576 for (int i = 0; i < eventCount; i++) { 577 final UsageEvents.Event event = events.valueAt(i); 578 pw.printPair("time", formatDateTime(event.mTimeStamp, prettyDates)); 579 pw.printPair("type", eventToString(event.mEventType)); 580 pw.printPair("package", event.mPackage); 581 if (event.mClass != null) { 582 pw.printPair("class", event.mClass); 583 } 584 if (event.mConfiguration != null) { 585 pw.printPair("config", Configuration.resourceQualifierString(event.mConfiguration)); 586 } 587 if (event.mShortcutId != null) { 588 pw.printPair("shortcutId", event.mShortcutId); 589 } 590 pw.printHexPair("flags", event.mFlags); 591 pw.println(); 592 } 593 pw.decreaseIndent(); 594 pw.decreaseIndent(); 595 } 596 intervalToString(int interval)597 private static String intervalToString(int interval) { 598 switch (interval) { 599 case UsageStatsManager.INTERVAL_DAILY: 600 return "daily"; 601 case UsageStatsManager.INTERVAL_WEEKLY: 602 return "weekly"; 603 case UsageStatsManager.INTERVAL_MONTHLY: 604 return "monthly"; 605 case UsageStatsManager.INTERVAL_YEARLY: 606 return "yearly"; 607 default: 608 return "?"; 609 } 610 } 611 eventToString(int eventType)612 private static String eventToString(int eventType) { 613 switch (eventType) { 614 case UsageEvents.Event.NONE: 615 return "NONE"; 616 case UsageEvents.Event.MOVE_TO_BACKGROUND: 617 return "MOVE_TO_BACKGROUND"; 618 case UsageEvents.Event.MOVE_TO_FOREGROUND: 619 return "MOVE_TO_FOREGROUND"; 620 case UsageEvents.Event.END_OF_DAY: 621 return "END_OF_DAY"; 622 case UsageEvents.Event.CONTINUE_PREVIOUS_DAY: 623 return "CONTINUE_PREVIOUS_DAY"; 624 case UsageEvents.Event.CONFIGURATION_CHANGE: 625 return "CONFIGURATION_CHANGE"; 626 case UsageEvents.Event.SYSTEM_INTERACTION: 627 return "SYSTEM_INTERACTION"; 628 case UsageEvents.Event.USER_INTERACTION: 629 return "USER_INTERACTION"; 630 case UsageEvents.Event.SHORTCUT_INVOCATION: 631 return "SHORTCUT_INVOCATION"; 632 case UsageEvents.Event.CHOOSER_ACTION: 633 return "CHOOSER_ACTION"; 634 default: 635 return "UNKNOWN"; 636 } 637 } 638 getBackupPayload(String key)639 byte[] getBackupPayload(String key){ 640 return mDatabase.getBackupPayload(key); 641 } 642 applyRestoredPayload(String key, byte[] payload)643 void applyRestoredPayload(String key, byte[] payload){ 644 mDatabase.applyRestoredPayload(key, payload); 645 } 646 } 647