1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy 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, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.car.watchdog; 18 19 import static com.android.car.watchdog.TimeSource.ZONE_OFFSET; 20 21 import android.annotation.IntDef; 22 import android.annotation.Nullable; 23 import android.annotation.UserIdInt; 24 import android.automotive.watchdog.PerStateBytes; 25 import android.car.builtin.util.Slogf; 26 import android.car.watchdog.IoOveruseStats; 27 import android.car.watchdog.PackageKillableState.KillableState; 28 import android.content.ContentValues; 29 import android.content.Context; 30 import android.database.Cursor; 31 import android.database.SQLException; 32 import android.database.sqlite.SQLiteDatabase; 33 import android.database.sqlite.SQLiteOpenHelper; 34 import android.os.Process; 35 import android.util.ArrayMap; 36 import android.util.ArraySet; 37 import android.util.SparseArray; 38 39 import com.android.car.CarLog; 40 import com.android.car.internal.util.IntArray; 41 import com.android.internal.annotations.GuardedBy; 42 import com.android.internal.annotations.VisibleForTesting; 43 44 import java.io.File; 45 import java.lang.annotation.Retention; 46 import java.lang.annotation.RetentionPolicy; 47 import java.time.Instant; 48 import java.time.Period; 49 import java.time.ZonedDateTime; 50 import java.time.temporal.ChronoUnit; 51 import java.time.temporal.TemporalUnit; 52 import java.util.ArrayList; 53 import java.util.Comparator; 54 import java.util.List; 55 import java.util.Locale; 56 import java.util.Objects; 57 58 /** 59 * Defines the database to store/retrieve system resource stats history from local storage. 60 */ 61 public final class WatchdogStorage { 62 private static final String TAG = CarLog.tagFor(WatchdogStorage.class); 63 private static final int RETENTION_PERIOD_IN_DAYS = 30; 64 65 /** 66 * The database is clean when it is synchronized with the in-memory cache. Cannot start a 67 * write while in this state. 68 */ 69 private static final int DB_STATE_CLEAN = 1; 70 71 /** 72 * The database is dirty when it is not synchronized with the in-memory cache. When the 73 * database is in this state, no write is in progress. 74 */ 75 private static final int DB_STATE_DIRTY = 2; 76 77 /** 78 * Database write in progress. Cannot start a new write when the database is in this state. 79 */ 80 private static final int DB_STATE_WRITE_IN_PROGRESS = 3; 81 82 /** 83 * The database enters this state when the database is marked dirty while a write is in 84 * progress. 85 */ 86 private static final int DB_STATE_WRITE_IN_PROGRESS_DIRTY = 4; 87 88 @Retention(RetentionPolicy.SOURCE) 89 @IntDef(prefix = {"DB_STATE_"}, value = { 90 DB_STATE_CLEAN, 91 DB_STATE_DIRTY, 92 DB_STATE_WRITE_IN_PROGRESS, 93 DB_STATE_WRITE_IN_PROGRESS_DIRTY 94 }) 95 private @interface DatabaseStateType{} 96 97 public static final int FAILED_TRANSACTION = -1; 98 /* Stats are stored on a daily basis. */ 99 public static final TemporalUnit STATS_TEMPORAL_UNIT = ChronoUnit.DAYS; 100 /* Number of days to retain the stats in local storage. */ 101 public static final Period RETENTION_PERIOD = 102 Period.ofDays(RETENTION_PERIOD_IN_DAYS).normalized(); 103 public static final String ZONE_MODIFIER = "utc"; 104 public static final String DATE_MODIFIER = "unixepoch"; 105 106 private final WatchdogDbHelper mDbHelper; 107 private final ArrayMap<String, UserPackage> mUserPackagesByKey = new ArrayMap<>(); 108 private final ArrayMap<String, UserPackage> mUserPackagesById = new ArrayMap<>(); 109 private TimeSource mTimeSource; 110 private final Object mLock = new Object(); 111 // Cache of today's I/O overuse stats collected during the previous boot. The data contained in 112 // the cache won't change until the next boot, so it is safe to cache the data in memory. 113 @GuardedBy("mLock") 114 private final List<IoUsageStatsEntry> mTodayIoUsageStatsEntries = new ArrayList<>(); 115 @GuardedBy("mLock") 116 private @DatabaseStateType int mCurrentDbState = DB_STATE_CLEAN; 117 WatchdogStorage(Context context, TimeSource timeSource)118 public WatchdogStorage(Context context, TimeSource timeSource) { 119 this(context, /* useDataSystemCarDir= */ true, timeSource); 120 } 121 122 @VisibleForTesting WatchdogStorage(Context context, boolean useDataSystemCarDir, TimeSource timeSource)123 WatchdogStorage(Context context, boolean useDataSystemCarDir, TimeSource timeSource) { 124 mTimeSource = timeSource; 125 mDbHelper = new WatchdogDbHelper(context, useDataSystemCarDir, mTimeSource); 126 } 127 128 /** Releases resources. */ release()129 public void release() { 130 mDbHelper.close(); 131 } 132 133 /** Handles database shrink. */ shrinkDatabase()134 public void shrinkDatabase() { 135 try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) { 136 mDbHelper.onShrink(db); 137 } 138 } 139 140 /** 141 * Marks the database as dirty. The database is dirty when it is not synchronized with the 142 * memory cache. 143 */ markDirty()144 public void markDirty() { 145 synchronized (mLock) { 146 mCurrentDbState = mCurrentDbState == DB_STATE_WRITE_IN_PROGRESS 147 ? DB_STATE_WRITE_IN_PROGRESS_DIRTY : DB_STATE_DIRTY; 148 } 149 Slogf.i(TAG, "Database marked dirty."); 150 } 151 152 /** 153 * Starts write to database only if database is dirty and no writing is in progress. 154 * 155 * @return {@code true} if start was successful, otherwise {@code false}. 156 */ startWrite()157 public boolean startWrite() { 158 synchronized (mLock) { 159 if (mCurrentDbState != DB_STATE_DIRTY) { 160 Slogf.e(TAG, "Cannot start a new write while the DB state is %s", 161 toDbStateString(mCurrentDbState)); 162 return false; 163 } 164 mCurrentDbState = DB_STATE_WRITE_IN_PROGRESS; 165 return true; 166 } 167 } 168 169 /** Ends write to database if write is in progress. */ endWrite()170 public void endWrite() { 171 synchronized (mLock) { 172 mCurrentDbState = mCurrentDbState == DB_STATE_CLEAN ? DB_STATE_CLEAN : DB_STATE_DIRTY; 173 } 174 } 175 176 /** Marks the database as clean during an in progress database write. */ markWriteSuccessful()177 public void markWriteSuccessful() { 178 synchronized (mLock) { 179 if (mCurrentDbState != DB_STATE_WRITE_IN_PROGRESS) { 180 Slogf.e(TAG, "Failed to mark write successful as the current db state is %s", 181 toDbStateString(mCurrentDbState)); 182 return; 183 } 184 mCurrentDbState = DB_STATE_CLEAN; 185 } 186 } 187 188 /** Saves the given user package settings entries and returns whether the change succeeded. */ saveUserPackageSettings(List<UserPackageSettingsEntry> entries)189 public boolean saveUserPackageSettings(List<UserPackageSettingsEntry> entries) { 190 ArraySet<Integer> usersWithMissingIds = new ArraySet<>(); 191 boolean isWriteSuccessful = false; 192 try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) { 193 try { 194 db.beginTransaction(); 195 for (int i = 0; i < entries.size(); ++i) { 196 UserPackageSettingsEntry entry = entries.get(i); 197 // Note: DO NOT replace existing entries in the UserPackageSettingsTable because 198 // the replace operation deletes the old entry and inserts a new entry in the 199 // table. This deletes the entries (in other tables) that are associated with 200 // the old userPackageId. And also the userPackageId is auto-incremented. 201 if (mUserPackagesByKey.get(UserPackage.getKey(entry.userId, entry.packageName)) 202 != null && UserPackageSettingsTable.updateEntry(db, entry)) { 203 continue; 204 } 205 usersWithMissingIds.add(entry.userId); 206 if (!UserPackageSettingsTable.replaceEntry(db, entry)) { 207 return false; 208 } 209 } 210 db.setTransactionSuccessful(); 211 isWriteSuccessful = true; 212 } finally { 213 db.endTransaction(); 214 } 215 populateUserPackages(db, usersWithMissingIds); 216 } 217 return isWriteSuccessful; 218 } 219 220 /** Returns the user package setting entries. */ getUserPackageSettings()221 public List<UserPackageSettingsEntry> getUserPackageSettings() { 222 ArrayMap<String, UserPackageSettingsEntry> entriesById; 223 try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) { 224 entriesById = UserPackageSettingsTable.querySettings(db); 225 } 226 List<UserPackageSettingsEntry> entries = new ArrayList<>(entriesById.size()); 227 for (int i = 0; i < entriesById.size(); ++i) { 228 String userPackageId = entriesById.keyAt(i); 229 UserPackageSettingsEntry entry = entriesById.valueAt(i); 230 UserPackage userPackage = new UserPackage(userPackageId, entry.userId, 231 entry.packageName); 232 mUserPackagesByKey.put(userPackage.getKey(), userPackage); 233 mUserPackagesById.put(userPackage.userPackageId, userPackage); 234 entries.add(entry); 235 } 236 return entries; 237 } 238 239 /** 240 * Saves the given I/O usage stats. 241 * 242 * @return the number of saved entries, on success. Otherwise, returns 243 * {@code FAILED_TRANSACTION} 244 */ saveIoUsageStats(List<IoUsageStatsEntry> entries)245 public int saveIoUsageStats(List<IoUsageStatsEntry> entries) { 246 return saveIoUsageStats(entries, /* shouldCheckRetention= */ true); 247 } 248 249 /** Returns the saved I/O usage stats for the current day. */ getTodayIoUsageStats()250 public List<IoUsageStatsEntry> getTodayIoUsageStats() { 251 synchronized (mLock) { 252 if (!mTodayIoUsageStatsEntries.isEmpty()) { 253 return new ArrayList<>(mTodayIoUsageStatsEntries); 254 } 255 long includingStartEpochSeconds = mTimeSource.getCurrentDate().toEpochSecond(); 256 long excludingEndEpochSeconds = mTimeSource.getCurrentDateTime().toEpochSecond(); 257 ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> ioUsagesById; 258 try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) { 259 ioUsagesById = IoUsageStatsTable.queryStats(db, includingStartEpochSeconds, 260 excludingEndEpochSeconds); 261 } 262 for (int i = 0; i < ioUsagesById.size(); ++i) { 263 String userPackageId = ioUsagesById.keyAt(i); 264 UserPackage userPackage = mUserPackagesById.get(userPackageId); 265 if (userPackage == null) { 266 Slogf.i(TAG, 267 "Failed to find user id and package name for user package id: '%s'", 268 userPackageId); 269 continue; 270 } 271 mTodayIoUsageStatsEntries.add(new IoUsageStatsEntry( 272 userPackage.userId, userPackage.packageName, 273 ioUsagesById.valueAt(i))); 274 } 275 return new ArrayList<>(mTodayIoUsageStatsEntries); 276 } 277 } 278 279 /** Deletes user package settings and resource overuse stats. */ deleteUserPackage(@serIdInt int userId, String packageName)280 public void deleteUserPackage(@UserIdInt int userId, String packageName) { 281 UserPackage userPackage = mUserPackagesByKey.get(UserPackage.getKey(userId, packageName)); 282 if (userPackage == null) { 283 Slogf.w(TAG, "Failed to find user package id for user id '%d' and package '%s", 284 userId, packageName); 285 return; 286 } 287 mUserPackagesByKey.remove(userPackage.getKey()); 288 mUserPackagesById.remove(userPackage.userPackageId); 289 try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) { 290 UserPackageSettingsTable.deleteUserPackage(db, userId, packageName); 291 } 292 } 293 294 /** 295 * Returns the aggregated historical I/O overuse stats for the given user package or 296 * {@code null} when stats are not available. 297 */ 298 @Nullable getHistoricalIoOveruseStats(@serIdInt int userId, String packageName, int numDaysAgo)299 public IoOveruseStats getHistoricalIoOveruseStats(@UserIdInt int userId, String packageName, 300 int numDaysAgo) { 301 ZonedDateTime currentDate = mTimeSource.getCurrentDate(); 302 long includingStartEpochSeconds = currentDate.minusDays(numDaysAgo).toEpochSecond(); 303 long excludingEndEpochSeconds = currentDate.toEpochSecond(); 304 try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) { 305 UserPackage userPackage = mUserPackagesByKey.get( 306 UserPackage.getKey(userId, packageName)); 307 if (userPackage == null) { 308 /* Packages without historical stats don't have userPackage entry. */ 309 return null; 310 } 311 return IoUsageStatsTable.queryIoOveruseStatsForUserPackageId(db, 312 userPackage.userPackageId, includingStartEpochSeconds, 313 excludingEndEpochSeconds); 314 } 315 } 316 317 /** 318 * Returns daily system-level I/O usage summaries for the given period or {@code null} when 319 * summaries are not available. 320 */ getDailySystemIoUsageSummaries( long minSystemTotalWrittenBytes, long includingStartEpochSeconds, long excludingEndEpochSeconds)321 public @Nullable List<AtomsProto.CarWatchdogDailyIoUsageSummary> getDailySystemIoUsageSummaries( 322 long minSystemTotalWrittenBytes, long includingStartEpochSeconds, 323 long excludingEndEpochSeconds) { 324 try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) { 325 List<AtomsProto.CarWatchdogDailyIoUsageSummary> dailyIoUsageSummaries = 326 IoUsageStatsTable.queryDailySystemIoUsageSummaries(db, 327 includingStartEpochSeconds, excludingEndEpochSeconds); 328 if (dailyIoUsageSummaries == null) { 329 return null; 330 } 331 long systemTotalWrittenBytes = 0; 332 for (int i = 0; i < dailyIoUsageSummaries.size(); i++) { 333 AtomsProto.CarWatchdogPerStateBytes writtenBytes = 334 dailyIoUsageSummaries.get(i).getWrittenBytes(); 335 systemTotalWrittenBytes += writtenBytes.getForegroundBytes() 336 + writtenBytes.getBackgroundBytes() + writtenBytes.getGarageModeBytes(); 337 } 338 if (systemTotalWrittenBytes < minSystemTotalWrittenBytes) { 339 return null; 340 } 341 return dailyIoUsageSummaries; 342 } 343 } 344 345 /** 346 * Returns top N disk I/O users' daily I/O usage summaries for the given period or {@code null} 347 * when summaries are not available. 348 */ getTopUsersDailyIoUsageSummaries( int numTopUsers, long minSystemTotalWrittenBytes, long includingStartEpochSeconds, long excludingEndEpochSeconds)349 public @Nullable List<UserPackageDailySummaries> getTopUsersDailyIoUsageSummaries( 350 int numTopUsers, long minSystemTotalWrittenBytes, long includingStartEpochSeconds, 351 long excludingEndEpochSeconds) { 352 ArrayMap<String, List<AtomsProto.CarWatchdogDailyIoUsageSummary>> summariesById; 353 try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) { 354 long systemTotalWrittenBytes = IoUsageStatsTable.querySystemTotalWrittenBytes(db, 355 includingStartEpochSeconds, excludingEndEpochSeconds); 356 if (systemTotalWrittenBytes < minSystemTotalWrittenBytes) { 357 return null; 358 } 359 summariesById = IoUsageStatsTable.queryTopUsersDailyIoUsageSummaries(db, 360 numTopUsers, includingStartEpochSeconds, excludingEndEpochSeconds); 361 } 362 if (summariesById == null) { 363 return null; 364 } 365 ArrayList<UserPackageDailySummaries> userPackageDailySummaries = new ArrayList<>(); 366 for (int i = 0; i < summariesById.size(); ++i) { 367 String id = summariesById.keyAt(i); 368 UserPackage userPackage = mUserPackagesById.get(id); 369 if (userPackage == null) { 370 Slogf.i(TAG, 371 "Failed to find user id and package name for user package id: '%s'", 372 id); 373 continue; 374 } 375 userPackageDailySummaries.add(new UserPackageDailySummaries(userPackage.userId, 376 userPackage.packageName, summariesById.valueAt(i))); 377 } 378 userPackageDailySummaries 379 .sort(Comparator.comparingLong(UserPackageDailySummaries::getTotalWrittenBytes) 380 .reversed()); 381 return userPackageDailySummaries; 382 } 383 384 /** 385 * Returns the aggregated historical overuses minus the forgiven overuses for all saved 386 * packages. Forgiven overuses are overuses that have been attributed previously to a package's 387 * recurring overuse. 388 */ getNotForgivenHistoricalIoOveruses(int numDaysAgo)389 public List<NotForgivenOverusesEntry> getNotForgivenHistoricalIoOveruses(int numDaysAgo) { 390 ZonedDateTime currentDate = 391 mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT); 392 long includingStartEpochSeconds = currentDate.minusDays(numDaysAgo).toEpochSecond(); 393 long excludingEndEpochSeconds = currentDate.toEpochSecond(); 394 ArrayMap<String, Integer> notForgivenOverusesById; 395 try (SQLiteDatabase db = mDbHelper.getReadableDatabase()) { 396 notForgivenOverusesById = IoUsageStatsTable.queryNotForgivenHistoricalOveruses(db, 397 includingStartEpochSeconds, excludingEndEpochSeconds); 398 } 399 List<NotForgivenOverusesEntry> notForgivenOverusesEntries = new ArrayList<>(); 400 for (int i = 0; i < notForgivenOverusesById.size(); i++) { 401 String id = notForgivenOverusesById.keyAt(i); 402 UserPackage userPackage = mUserPackagesById.get(id); 403 if (userPackage == null) { 404 Slogf.w(TAG, 405 "Failed to find user id and package name for user package id: '%s'", 406 id); 407 continue; 408 } 409 notForgivenOverusesEntries.add(new NotForgivenOverusesEntry(userPackage.userId, 410 userPackage.packageName, notForgivenOverusesById.valueAt(i))); 411 } 412 return notForgivenOverusesEntries; 413 } 414 415 /** 416 * Forgives all historical overuses between yesterday and {@code numDaysAgo} 417 * for a list of specific {@code userIds} and {@code packageNames}. 418 */ forgiveHistoricalOveruses(SparseArray<List<String>> packagesByUserId, int numDaysAgo)419 public void forgiveHistoricalOveruses(SparseArray<List<String>> packagesByUserId, 420 int numDaysAgo) { 421 if (packagesByUserId.size() == 0) { 422 Slogf.w(TAG, "No I/O usage stats provided to forgive historical overuses."); 423 return; 424 } 425 ZonedDateTime currentDate = 426 mTimeSource.now().atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT); 427 long includingStartEpochSeconds = currentDate.minusDays(numDaysAgo).toEpochSecond(); 428 long excludingEndEpochSeconds = currentDate.toEpochSecond(); 429 List<String> userPackageIds = new ArrayList<>(); 430 for (int i = 0; i < packagesByUserId.size(); i++) { 431 int userId = packagesByUserId.keyAt(i); 432 List<String> packages = packagesByUserId.valueAt(i); 433 for (int pkgIdx = 0; pkgIdx < packages.size(); pkgIdx++) { 434 UserPackage userPackage = 435 mUserPackagesByKey.get(UserPackage.getKey(userId, packages.get(pkgIdx))); 436 if (userPackage == null) { 437 // Packages without historical stats don't have userPackage entry. 438 continue; 439 } 440 userPackageIds.add(userPackage.userPackageId); 441 } 442 } 443 try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) { 444 IoUsageStatsTable.forgiveHistoricalOverusesForPackage(db, userPackageIds, 445 includingStartEpochSeconds, excludingEndEpochSeconds); 446 } 447 } 448 449 /** 450 * Deletes all user package settings and resource stats for all non-alive users. 451 * 452 * @param aliveUserIds Array of alive user ids. 453 */ syncUsers(int[] aliveUserIds)454 public void syncUsers(int[] aliveUserIds) { 455 IntArray aliveUsers = IntArray.wrap(aliveUserIds); 456 for (int i = mUserPackagesByKey.size() - 1; i >= 0; --i) { 457 UserPackage userPackage = mUserPackagesByKey.valueAt(i); 458 if (aliveUsers.indexOf(userPackage.userId) == -1) { 459 mUserPackagesByKey.removeAt(i); 460 mUserPackagesById.remove(userPackage.userPackageId); 461 } 462 } 463 try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) { 464 UserPackageSettingsTable.syncUserPackagesWithAliveUsers(db, aliveUsers); 465 } 466 } 467 468 @VisibleForTesting saveIoUsageStats(List<IoUsageStatsEntry> entries, boolean shouldCheckRetention)469 int saveIoUsageStats(List<IoUsageStatsEntry> entries, boolean shouldCheckRetention) { 470 ZonedDateTime currentDate = mTimeSource.getCurrentDate(); 471 List<ContentValues> rows = new ArrayList<>(entries.size()); 472 for (int i = 0; i < entries.size(); ++i) { 473 IoUsageStatsEntry entry = entries.get(i); 474 UserPackage userPackage = mUserPackagesByKey.get( 475 UserPackage.getKey(entry.userId, entry.packageName)); 476 if (userPackage == null) { 477 Slogf.i(TAG, "Failed to find user package id for user id '%d' and package '%s", 478 entry.userId, entry.packageName); 479 continue; 480 } 481 android.automotive.watchdog.IoOveruseStats ioOveruseStats = 482 entry.ioUsage.getInternalIoOveruseStats(); 483 ZonedDateTime statsDate = Instant.ofEpochSecond(ioOveruseStats.startTime) 484 .atZone(ZONE_OFFSET).truncatedTo(STATS_TEMPORAL_UNIT); 485 if (shouldCheckRetention && STATS_TEMPORAL_UNIT.between(statsDate, currentDate) 486 >= RETENTION_PERIOD.get(STATS_TEMPORAL_UNIT)) { 487 continue; 488 } 489 long statsDateEpochSeconds = statsDate.toEpochSecond(); 490 rows.add(IoUsageStatsTable.getContentValues( 491 userPackage.userPackageId, entry, statsDateEpochSeconds)); 492 } 493 try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) { 494 return atomicReplaceEntries(db, IoUsageStatsTable.TABLE_NAME, rows); 495 } 496 } 497 populateUserPackages(SQLiteDatabase db, ArraySet<Integer> users)498 private void populateUserPackages(SQLiteDatabase db, ArraySet<Integer> users) { 499 List<UserPackage> userPackages = UserPackageSettingsTable.queryUserPackages(db, users); 500 if (userPackages == null) { 501 return; 502 } 503 for (int i = 0; i < userPackages.size(); ++i) { 504 UserPackage userPackage = userPackages.get(i); 505 mUserPackagesByKey.put(userPackage.getKey(), userPackage); 506 mUserPackagesById.put(userPackage.userPackageId, userPackage); 507 } 508 } 509 510 /** 511 * Atomically replace rows in a database table. 512 * 513 * @return the number of replaced entries, on success. Otherwise, returns 514 * {@code FAILED_TRANSACTION} 515 */ atomicReplaceEntries(SQLiteDatabase db, String tableName, List<ContentValues> rows)516 private static int atomicReplaceEntries(SQLiteDatabase db, String tableName, 517 List<ContentValues> rows) { 518 if (rows.isEmpty()) { 519 return 0; 520 } 521 try { 522 db.beginTransaction(); 523 for (int i = 0; i < rows.size(); ++i) { 524 try { 525 if (db.replaceOrThrow(tableName, null, rows.get(i)) == -1) { 526 Slogf.e(TAG, "Failed to insert %s entry [%s]", tableName, rows.get(i)); 527 return FAILED_TRANSACTION; 528 } 529 } catch (SQLException e) { 530 Slogf.e(TAG, e, "Failed to insert %s entry [%s]", tableName, rows.get(i)); 531 return FAILED_TRANSACTION; 532 } 533 } 534 db.setTransactionSuccessful(); 535 } finally { 536 db.endTransaction(); 537 } 538 return rows.size(); 539 } 540 toDbStateString(int dbState)541 private static String toDbStateString(int dbState) { 542 switch (dbState) { 543 case DB_STATE_CLEAN: 544 return "DB_STATE_CLEAN"; 545 case DB_STATE_DIRTY: 546 return "DB_STATE_DIRTY"; 547 case DB_STATE_WRITE_IN_PROGRESS: 548 return "DB_STATE_WRITE_IN_PROGRESS"; 549 case DB_STATE_WRITE_IN_PROGRESS_DIRTY: 550 return "DB_STATE_WRITE_IN_PROGRESS_DIRTY"; 551 default: 552 return "UNKNOWN"; 553 } 554 } 555 556 /** Defines the user package settings entry stored in the UserPackageSettingsTable. */ 557 static final class UserPackageSettingsEntry { 558 public final @UserIdInt int userId; 559 public final String packageName; 560 public final @KillableState int killableState; 561 UserPackageSettingsEntry(@serIdInt int userId, String packageName, @KillableState int killableState)562 UserPackageSettingsEntry(@UserIdInt int userId, String packageName, 563 @KillableState int killableState) { 564 this.userId = userId; 565 this.packageName = packageName; 566 this.killableState = killableState; 567 } 568 569 @Override equals(Object obj)570 public boolean equals(Object obj) { 571 if (obj == this) { 572 return true; 573 } 574 if (!(obj instanceof UserPackageSettingsEntry)) { 575 return false; 576 } 577 UserPackageSettingsEntry other = (UserPackageSettingsEntry) obj; 578 return userId == other.userId && packageName.equals(other.packageName) 579 && killableState == other.killableState; 580 } 581 582 @Override hashCode()583 public int hashCode() { 584 return Objects.hash(userId, packageName, killableState); 585 } 586 587 @Override toString()588 public String toString() { 589 return new StringBuilder().append("UserPackageSettingsEntry{userId: ").append(userId) 590 .append(", packageName: ").append(packageName) 591 .append(", killableState: ").append(killableState).append('}') 592 .toString(); 593 } 594 } 595 596 /** Defines the daily summaries for user packages. */ 597 static final class UserPackageDailySummaries { 598 public final @UserIdInt int userId; 599 public final String packageName; 600 public final List<AtomsProto.CarWatchdogDailyIoUsageSummary> dailyIoUsageSummaries; 601 private final long mTotalWrittenBytes; 602 UserPackageDailySummaries(@serIdInt int userId, String packageName, List<AtomsProto.CarWatchdogDailyIoUsageSummary> dailyIoUsageSummaries)603 UserPackageDailySummaries(@UserIdInt int userId, String packageName, 604 List<AtomsProto.CarWatchdogDailyIoUsageSummary> dailyIoUsageSummaries) { 605 this.userId = userId; 606 this.packageName = packageName; 607 this.dailyIoUsageSummaries = dailyIoUsageSummaries; 608 this.mTotalWrittenBytes = computeTotalWrittenBytes(); 609 } 610 611 @Override equals(Object obj)612 public boolean equals(Object obj) { 613 if (obj == this) { 614 return true; 615 } 616 if (!(obj instanceof UserPackageDailySummaries)) { 617 return false; 618 } 619 UserPackageDailySummaries other = (UserPackageDailySummaries) obj; 620 return userId == other.userId && packageName.equals(other.packageName) 621 && dailyIoUsageSummaries.equals(other.dailyIoUsageSummaries); 622 } 623 624 @Override hashCode()625 public int hashCode() { 626 return Objects.hash(userId, packageName, dailyIoUsageSummaries, mTotalWrittenBytes); 627 } 628 629 @Override toString()630 public String toString() { 631 return new StringBuilder().append("UserPackageDailySummaries{userId: ").append(userId) 632 .append(", packageName: ").append(packageName) 633 .append(", dailyIoUsageSummaries: ").append(dailyIoUsageSummaries).append('}') 634 .toString(); 635 } 636 getTotalWrittenBytes()637 long getTotalWrittenBytes() { 638 return mTotalWrittenBytes; 639 } 640 computeTotalWrittenBytes()641 long computeTotalWrittenBytes() { 642 long totalBytes = 0; 643 for (int i = 0; i < dailyIoUsageSummaries.size(); ++i) { 644 AtomsProto.CarWatchdogPerStateBytes writtenBytes = 645 dailyIoUsageSummaries.get(i).getWrittenBytes(); 646 if (writtenBytes.hasForegroundBytes()) { 647 totalBytes += writtenBytes.getForegroundBytes(); 648 } 649 if (writtenBytes.hasBackgroundBytes()) { 650 totalBytes += writtenBytes.getBackgroundBytes(); 651 } 652 if (writtenBytes.hasGarageModeBytes()) { 653 totalBytes += writtenBytes.getGarageModeBytes(); 654 } 655 } 656 return totalBytes; 657 } 658 } 659 660 /** 661 * Defines the contents and queries for the user package settings table. 662 */ 663 static final class UserPackageSettingsTable { 664 public static final String TABLE_NAME = "user_package_settings"; 665 public static final String COLUMN_USER_PACKAGE_ID = "user_package_id"; 666 public static final String COLUMN_PACKAGE_NAME = "package_name"; 667 public static final String COLUMN_USER_ID = "user_id"; 668 public static final String COLUMN_KILLABLE_STATE = "killable_state"; 669 createTable(SQLiteDatabase db)670 public static void createTable(SQLiteDatabase db) { 671 StringBuilder createCommand = new StringBuilder(); 672 createCommand.append("CREATE TABLE ").append(TABLE_NAME).append(" (") 673 // Maximum value for COLUMN_USER_PACKAGE_ID is the max integer size supported by 674 // the database. Thus, the number of entries that can be inserted into this 675 // table is bound by this upper limit for the lifetime of the device 676 // (i.e., Even when a userId is reused, the previous user_package_ids for 677 // the corresponding userId won't be reused). When the IDs are exhausted, 678 // any new insert operation will result in the error "database or disk is full". 679 .append(COLUMN_USER_PACKAGE_ID).append(" INTEGER PRIMARY KEY AUTOINCREMENT, ") 680 .append(COLUMN_PACKAGE_NAME).append(" TEXT NOT NULL, ") 681 .append(COLUMN_USER_ID).append(" INTEGER NOT NULL, ") 682 .append(COLUMN_KILLABLE_STATE).append(" INTEGER NOT NULL, ") 683 .append("UNIQUE(").append(COLUMN_PACKAGE_NAME) 684 .append(", ").append(COLUMN_USER_ID).append("))"); 685 db.execSQL(createCommand.toString()); 686 Slogf.i(TAG, "Successfully created the %s table in the %s database version %d", 687 TABLE_NAME, WatchdogDbHelper.DATABASE_NAME, WatchdogDbHelper.DATABASE_VERSION); 688 } 689 updateEntry(SQLiteDatabase db, UserPackageSettingsEntry entry)690 public static boolean updateEntry(SQLiteDatabase db, UserPackageSettingsEntry entry) { 691 ContentValues values = new ContentValues(); 692 values.put(COLUMN_KILLABLE_STATE, entry.killableState); 693 694 StringBuilder whereClause = new StringBuilder(COLUMN_PACKAGE_NAME).append(" = ? AND ") 695 .append(COLUMN_USER_ID).append(" = ?"); 696 String[] whereArgs = new String[]{entry.packageName, String.valueOf(entry.userId)}; 697 698 if (db.update(TABLE_NAME, values, whereClause.toString(), whereArgs) < 1) { 699 Slogf.e(TAG, "Failed to update %d entry with package name: %s and user id: %d", 700 TABLE_NAME, entry.packageName, entry.userId); 701 return false; 702 } 703 return true; 704 } 705 replaceEntry(SQLiteDatabase db, UserPackageSettingsEntry entry)706 public static boolean replaceEntry(SQLiteDatabase db, UserPackageSettingsEntry entry) { 707 ContentValues values = new ContentValues(); 708 values.put(COLUMN_USER_ID, entry.userId); 709 values.put(COLUMN_PACKAGE_NAME, entry.packageName); 710 values.put(COLUMN_KILLABLE_STATE, entry.killableState); 711 712 if (db.replaceOrThrow(UserPackageSettingsTable.TABLE_NAME, null, values) == -1) { 713 Slogf.e(TAG, "Failed to replaced %s entry [%s]", TABLE_NAME, values); 714 return false; 715 } 716 return true; 717 } 718 querySettings(SQLiteDatabase db)719 public static ArrayMap<String, UserPackageSettingsEntry> querySettings(SQLiteDatabase db) { 720 StringBuilder queryBuilder = new StringBuilder(); 721 queryBuilder.append("SELECT ") 722 .append(COLUMN_USER_PACKAGE_ID).append(", ") 723 .append(COLUMN_USER_ID).append(", ") 724 .append(COLUMN_PACKAGE_NAME).append(", ") 725 .append(COLUMN_KILLABLE_STATE) 726 .append(" FROM ").append(TABLE_NAME); 727 728 try (Cursor cursor = db.rawQuery(queryBuilder.toString(), new String[]{})) { 729 ArrayMap<String, UserPackageSettingsEntry> entriesById = new ArrayMap<>( 730 cursor.getCount()); 731 while (cursor.moveToNext()) { 732 entriesById.put(cursor.getString(0), new UserPackageSettingsEntry( 733 cursor.getInt(1), cursor.getString(2), cursor.getInt(3))); 734 } 735 return entriesById; 736 } 737 } 738 739 /** 740 * Returns the UserPackage entries for the given users. When no users are provided or no 741 * data returned by the DB query, returns null. 742 */ 743 @Nullable queryUserPackages(SQLiteDatabase db, ArraySet<Integer> users)744 public static List<UserPackage> queryUserPackages(SQLiteDatabase db, 745 ArraySet<Integer> users) { 746 int numUsers = users.size(); 747 if (numUsers == 0) { 748 return null; 749 } 750 StringBuilder queryBuilder = new StringBuilder(); 751 queryBuilder.append("SELECT ") 752 .append(COLUMN_USER_PACKAGE_ID).append(", ") 753 .append(COLUMN_USER_ID).append(", ") 754 .append(COLUMN_PACKAGE_NAME) 755 .append(" FROM ").append(TABLE_NAME) 756 .append(" WHERE ").append(COLUMN_USER_ID).append(" IN ("); 757 for (int i = 0; i < numUsers; ++i) { 758 queryBuilder.append(users.valueAt(i)); 759 if (i < numUsers - 1) { 760 queryBuilder.append(", "); 761 } 762 } 763 queryBuilder.append(")"); 764 try (Cursor cursor = db.rawQuery(queryBuilder.toString(), new String[]{})) { 765 if (cursor.getCount() == 0) { 766 return null; 767 } 768 List<UserPackage> userPackages = new ArrayList<>(cursor.getCount()); 769 while (cursor.moveToNext()) { 770 userPackages.add(new UserPackage( 771 cursor.getString(0), cursor.getInt(1), cursor.getString(2))); 772 } 773 return userPackages; 774 } 775 } 776 deleteUserPackage(SQLiteDatabase db, @UserIdInt int userId, String packageName)777 public static void deleteUserPackage(SQLiteDatabase db, @UserIdInt int userId, 778 String packageName) { 779 String whereClause = COLUMN_USER_ID + "= ? and " + COLUMN_PACKAGE_NAME + "= ?"; 780 String[] whereArgs = new String[]{String.valueOf(userId), packageName}; 781 int deletedRows = db.delete(TABLE_NAME, whereClause, whereArgs); 782 Slogf.i(TAG, "Deleted %d user package settings db rows for user %d and package %s", 783 deletedRows, userId, packageName); 784 } 785 syncUserPackagesWithAliveUsers(SQLiteDatabase db, IntArray aliveUsers)786 public static void syncUserPackagesWithAliveUsers(SQLiteDatabase db, IntArray aliveUsers) { 787 StringBuilder queryBuilder = new StringBuilder(); 788 for (int i = 0; i < aliveUsers.size(); ++i) { 789 if (i == 0) { 790 queryBuilder.append(COLUMN_USER_ID).append(" NOT IN ("); 791 } else { 792 queryBuilder.append(", "); 793 } 794 queryBuilder.append(aliveUsers.get(i)); 795 if (i == aliveUsers.size() - 1) { 796 queryBuilder.append(")"); 797 } 798 } 799 int deletedRows = db.delete(TABLE_NAME, queryBuilder.toString(), new String[]{}); 800 Slogf.i(TAG, "Deleted %d user package settings db rows while syncing with alive users", 801 deletedRows); 802 } 803 } 804 805 /** Defines the I/O usage entry stored in the IoUsageStatsTable. */ 806 static final class IoUsageStatsEntry { 807 public final @UserIdInt int userId; 808 public final String packageName; 809 public final WatchdogPerfHandler.PackageIoUsage ioUsage; 810 IoUsageStatsEntry(@serIdInt int userId, String packageName, WatchdogPerfHandler.PackageIoUsage ioUsage)811 IoUsageStatsEntry(@UserIdInt int userId, 812 String packageName, WatchdogPerfHandler.PackageIoUsage ioUsage) { 813 this.userId = userId; 814 this.packageName = packageName; 815 this.ioUsage = ioUsage; 816 } 817 } 818 819 /** Defines the not forgiven overuses stored in the IoUsageStatsTable. */ 820 static final class NotForgivenOverusesEntry { 821 public final @UserIdInt int userId; 822 public final String packageName; 823 public final int notForgivenOveruses; 824 NotForgivenOverusesEntry(@serIdInt int userId, String packageName, int notForgivenOveruses)825 NotForgivenOverusesEntry(@UserIdInt int userId, 826 String packageName, int notForgivenOveruses) { 827 this.userId = userId; 828 this.packageName = packageName; 829 this.notForgivenOveruses = notForgivenOveruses; 830 } 831 832 @Override equals(Object obj)833 public boolean equals(Object obj) { 834 if (this == obj) { 835 return true; 836 } 837 if (!(obj instanceof NotForgivenOverusesEntry)) { 838 return false; 839 } 840 NotForgivenOverusesEntry other = (NotForgivenOverusesEntry) obj; 841 return userId == other.userId 842 && packageName.equals(other.packageName) 843 && notForgivenOveruses == other.notForgivenOveruses; 844 } 845 846 @Override hashCode()847 public int hashCode() { 848 return Objects.hash(userId, packageName, notForgivenOveruses); 849 } 850 851 @Override toString()852 public String toString() { 853 return "NotForgivenOverusesEntry {UserId: " + userId 854 + ", Package name: " + packageName 855 + ", Not forgiven overuses: " + notForgivenOveruses + "}"; 856 } 857 } 858 859 /** 860 * Defines the contents and queries for the I/O usage stats table. 861 */ 862 static final class IoUsageStatsTable { 863 public static final String TABLE_NAME = "io_usage_stats"; 864 public static final String COLUMN_USER_PACKAGE_ID = "user_package_id"; 865 public static final String COLUMN_DATE_EPOCH = "date_epoch"; 866 public static final String COLUMN_NUM_OVERUSES = "num_overuses"; 867 public static final String COLUMN_NUM_FORGIVEN_OVERUSES = "num_forgiven_overuses"; 868 public static final String COLUMN_NUM_TIMES_KILLED = "num_times_killed"; 869 public static final String COLUMN_WRITTEN_FOREGROUND_BYTES = "written_foreground_bytes"; 870 public static final String COLUMN_WRITTEN_BACKGROUND_BYTES = "written_background_bytes"; 871 public static final String COLUMN_WRITTEN_GARAGE_MODE_BYTES = "written_garage_mode_bytes"; 872 /* Below columns will be null for historical stats i.e., when the date != current date. */ 873 public static final String COLUMN_REMAINING_FOREGROUND_WRITE_BYTES = 874 "remaining_foreground_write_bytes"; 875 public static final String COLUMN_REMAINING_BACKGROUND_WRITE_BYTES = 876 "remaining_background_write_bytes"; 877 public static final String COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES = 878 "remaining_garage_mode_write_bytes"; 879 public static final String COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES = 880 "forgiven_foreground_write_bytes"; 881 public static final String COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES = 882 "forgiven_background_write_bytes"; 883 public static final String COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES = 884 "forgiven_garage_mode_write_bytes"; 885 createTable(SQLiteDatabase db)886 public static void createTable(SQLiteDatabase db) { 887 StringBuilder createCommand = new StringBuilder(); 888 createCommand.append("CREATE TABLE ").append(TABLE_NAME).append(" (") 889 .append(COLUMN_USER_PACKAGE_ID).append(" INTEGER NOT NULL, ") 890 .append(COLUMN_DATE_EPOCH).append(" INTEGER NOT NULL, ") 891 .append(COLUMN_NUM_OVERUSES).append(" INTEGER NOT NULL, ") 892 .append(COLUMN_NUM_FORGIVEN_OVERUSES).append(" INTEGER NOT NULL, ") 893 .append(COLUMN_NUM_TIMES_KILLED).append(" INTEGER NOT NULL, ") 894 .append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" INTEGER, ") 895 .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" INTEGER, ") 896 .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(" INTEGER, ") 897 .append(COLUMN_REMAINING_FOREGROUND_WRITE_BYTES).append(" INTEGER, ") 898 .append(COLUMN_REMAINING_BACKGROUND_WRITE_BYTES).append(" INTEGER, ") 899 .append(COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES).append(" INTEGER, ") 900 .append(COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES).append(" INTEGER, ") 901 .append(COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES).append(" INTEGER, ") 902 .append(COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES).append(" INTEGER, ") 903 .append("PRIMARY KEY (").append(COLUMN_USER_PACKAGE_ID).append(", ") 904 .append(COLUMN_DATE_EPOCH).append("), FOREIGN KEY (") 905 .append(COLUMN_USER_PACKAGE_ID).append(") REFERENCES ") 906 .append(UserPackageSettingsTable.TABLE_NAME).append(" (") 907 .append(UserPackageSettingsTable.COLUMN_USER_PACKAGE_ID) 908 .append(") ON DELETE CASCADE)"); 909 db.execSQL(createCommand.toString()); 910 Slogf.i(TAG, "Successfully created the %s table in the %s database version %d", 911 TABLE_NAME, WatchdogDbHelper.DATABASE_NAME, WatchdogDbHelper.DATABASE_VERSION); 912 } 913 getContentValues( String userPackageId, IoUsageStatsEntry entry, long statsDateEpochSeconds)914 public static ContentValues getContentValues( 915 String userPackageId, IoUsageStatsEntry entry, long statsDateEpochSeconds) { 916 android.automotive.watchdog.IoOveruseStats ioOveruseStats = 917 entry.ioUsage.getInternalIoOveruseStats(); 918 ContentValues values = new ContentValues(); 919 values.put(COLUMN_USER_PACKAGE_ID, userPackageId); 920 values.put(COLUMN_DATE_EPOCH, statsDateEpochSeconds); 921 values.put(COLUMN_NUM_OVERUSES, ioOveruseStats.totalOveruses); 922 values.put(COLUMN_NUM_FORGIVEN_OVERUSES, entry.ioUsage.getForgivenOveruses()); 923 values.put(COLUMN_NUM_TIMES_KILLED, entry.ioUsage.getTotalTimesKilled()); 924 values.put( 925 COLUMN_WRITTEN_FOREGROUND_BYTES, ioOveruseStats.writtenBytes.foregroundBytes); 926 values.put( 927 COLUMN_WRITTEN_BACKGROUND_BYTES, ioOveruseStats.writtenBytes.backgroundBytes); 928 values.put( 929 COLUMN_WRITTEN_GARAGE_MODE_BYTES, ioOveruseStats.writtenBytes.garageModeBytes); 930 values.put(COLUMN_REMAINING_FOREGROUND_WRITE_BYTES, 931 ioOveruseStats.remainingWriteBytes.foregroundBytes); 932 values.put(COLUMN_REMAINING_BACKGROUND_WRITE_BYTES, 933 ioOveruseStats.remainingWriteBytes.backgroundBytes); 934 values.put(COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES, 935 ioOveruseStats.remainingWriteBytes.garageModeBytes); 936 android.automotive.watchdog.PerStateBytes forgivenWriteBytes = 937 entry.ioUsage.getForgivenWriteBytes(); 938 values.put(COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES, forgivenWriteBytes.foregroundBytes); 939 values.put(COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES, forgivenWriteBytes.backgroundBytes); 940 values.put(COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES, forgivenWriteBytes.garageModeBytes); 941 return values; 942 } 943 queryStats( SQLiteDatabase db, long includingStartEpochSeconds, long excludingEndEpochSeconds)944 public static ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> queryStats( 945 SQLiteDatabase db, long includingStartEpochSeconds, long excludingEndEpochSeconds) { 946 StringBuilder queryBuilder = new StringBuilder(); 947 queryBuilder.append("SELECT ") 948 .append(COLUMN_USER_PACKAGE_ID).append(", ") 949 .append("MIN(").append(COLUMN_DATE_EPOCH).append("), ") 950 .append("SUM(").append(COLUMN_NUM_OVERUSES).append("), ") 951 .append("SUM(").append(COLUMN_NUM_FORGIVEN_OVERUSES).append("), ") 952 .append("SUM(").append(COLUMN_NUM_TIMES_KILLED).append("), ") 953 .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append("), ") 954 .append("SUM(").append(COLUMN_WRITTEN_BACKGROUND_BYTES).append("), ") 955 .append("SUM(").append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append("), ") 956 .append("SUM(").append(COLUMN_REMAINING_FOREGROUND_WRITE_BYTES).append("), ") 957 .append("SUM(").append(COLUMN_REMAINING_BACKGROUND_WRITE_BYTES).append("), ") 958 .append("SUM(").append(COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES).append("), ") 959 .append("SUM(").append(COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES).append("), ") 960 .append("SUM(").append(COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES).append("), ") 961 .append("SUM(").append(COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES).append(") ") 962 .append("FROM ").append(TABLE_NAME).append(" WHERE ") 963 .append(COLUMN_DATE_EPOCH).append(">= ? and ") 964 .append(COLUMN_DATE_EPOCH).append("< ? GROUP BY ") 965 .append(COLUMN_USER_PACKAGE_ID); 966 String[] selectionArgs = new String[]{String.valueOf(includingStartEpochSeconds), 967 String.valueOf(excludingEndEpochSeconds)}; 968 969 ArrayMap<String, WatchdogPerfHandler.PackageIoUsage> ioUsageById = new ArrayMap<>(); 970 try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) { 971 while (cursor.moveToNext()) { 972 android.automotive.watchdog.IoOveruseStats ioOveruseStats = 973 new android.automotive.watchdog.IoOveruseStats(); 974 ioOveruseStats.startTime = cursor.getLong(1); 975 ioOveruseStats.durationInSeconds = 976 excludingEndEpochSeconds - includingStartEpochSeconds; 977 ioOveruseStats.totalOveruses = cursor.getInt(2); 978 ioOveruseStats.writtenBytes = new PerStateBytes(); 979 ioOveruseStats.writtenBytes.foregroundBytes = cursor.getLong(5); 980 ioOveruseStats.writtenBytes.backgroundBytes = cursor.getLong(6); 981 ioOveruseStats.writtenBytes.garageModeBytes = cursor.getLong(7); 982 ioOveruseStats.remainingWriteBytes = new PerStateBytes(); 983 ioOveruseStats.remainingWriteBytes.foregroundBytes = cursor.getLong(8); 984 ioOveruseStats.remainingWriteBytes.backgroundBytes = cursor.getLong(9); 985 ioOveruseStats.remainingWriteBytes.garageModeBytes = cursor.getLong(10); 986 PerStateBytes forgivenWriteBytes = new PerStateBytes(); 987 forgivenWriteBytes.foregroundBytes = cursor.getLong(11); 988 forgivenWriteBytes.backgroundBytes = cursor.getLong(12); 989 forgivenWriteBytes.garageModeBytes = cursor.getLong(13); 990 991 ioUsageById.put(cursor.getString(0), new WatchdogPerfHandler.PackageIoUsage( 992 ioOveruseStats, forgivenWriteBytes, 993 /* forgivenOveruses= */ cursor.getInt(3), 994 /* totalTimesKilled= */ cursor.getInt(4))); 995 } 996 } 997 return ioUsageById; 998 } 999 queryIoOveruseStatsForUserPackageId( SQLiteDatabase db, String userPackageId, long includingStartEpochSeconds, long excludingEndEpochSeconds)1000 public static @Nullable IoOveruseStats queryIoOveruseStatsForUserPackageId( 1001 SQLiteDatabase db, String userPackageId, long includingStartEpochSeconds, 1002 long excludingEndEpochSeconds) { 1003 StringBuilder queryBuilder = new StringBuilder(); 1004 queryBuilder.append("SELECT SUM(").append(COLUMN_NUM_OVERUSES).append("), ") 1005 .append("SUM(").append(COLUMN_NUM_TIMES_KILLED).append("), ") 1006 .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append("), ") 1007 .append("SUM(").append(COLUMN_WRITTEN_BACKGROUND_BYTES).append("), ") 1008 .append("SUM(").append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append("), ") 1009 .append("MIN(").append(COLUMN_DATE_EPOCH).append(") ") 1010 .append("FROM ").append(TABLE_NAME).append(" WHERE ") 1011 .append(COLUMN_USER_PACKAGE_ID).append("=? and ") 1012 .append(COLUMN_DATE_EPOCH).append(" >= ? and ") 1013 .append(COLUMN_DATE_EPOCH).append("< ?"); 1014 String[] selectionArgs = new String[]{userPackageId, 1015 String.valueOf(includingStartEpochSeconds), 1016 String.valueOf(excludingEndEpochSeconds)}; 1017 long totalOveruses = 0; 1018 long totalTimesKilled = 0; 1019 long totalBytesWritten = 0; 1020 long earliestEpochSecond = excludingEndEpochSeconds; 1021 try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) { 1022 if (cursor.getCount() == 0) { 1023 return null; 1024 } 1025 while (cursor.moveToNext()) { 1026 totalOveruses += cursor.getLong(0); 1027 totalTimesKilled += cursor.getLong(1); 1028 totalBytesWritten += cursor.getLong(2) + cursor.getLong(3) + cursor.getLong(4); 1029 earliestEpochSecond = Math.min(cursor.getLong(5), earliestEpochSecond); 1030 } 1031 } 1032 if (totalBytesWritten == 0) { 1033 return null; 1034 } 1035 long durationInSeconds = excludingEndEpochSeconds - earliestEpochSecond; 1036 IoOveruseStats.Builder statsBuilder = new IoOveruseStats.Builder( 1037 earliestEpochSecond, durationInSeconds); 1038 statsBuilder.setTotalOveruses(totalOveruses); 1039 statsBuilder.setTotalTimesKilled(totalTimesKilled); 1040 statsBuilder.setTotalBytesWritten(totalBytesWritten); 1041 return statsBuilder.build(); 1042 } 1043 queryNotForgivenHistoricalOveruses( SQLiteDatabase db, long includingStartEpochSeconds, long excludingEndEpochSeconds)1044 public static ArrayMap<String, Integer> queryNotForgivenHistoricalOveruses( 1045 SQLiteDatabase db, long includingStartEpochSeconds, long excludingEndEpochSeconds) { 1046 StringBuilder queryBuilder = new StringBuilder("SELECT ") 1047 .append(COLUMN_USER_PACKAGE_ID).append(", ") 1048 .append("SUM(").append(COLUMN_NUM_OVERUSES).append("), ") 1049 .append("SUM(").append(COLUMN_NUM_FORGIVEN_OVERUSES).append(") ") 1050 .append("FROM ").append(TABLE_NAME).append(" WHERE ") 1051 .append(COLUMN_DATE_EPOCH).append(" >= ? and ") 1052 .append(COLUMN_DATE_EPOCH).append("< ? GROUP BY ") 1053 .append(COLUMN_USER_PACKAGE_ID); 1054 String[] selectionArgs = new String[]{String.valueOf(includingStartEpochSeconds), 1055 String.valueOf(excludingEndEpochSeconds)}; 1056 ArrayMap<String, Integer> notForgivenOverusesById = new ArrayMap<>(); 1057 try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) { 1058 while (cursor.moveToNext()) { 1059 if (cursor.getInt(1) <= cursor.getInt(2)) { 1060 continue; 1061 } 1062 notForgivenOverusesById.put(cursor.getString(0), 1063 cursor.getInt(1) - cursor.getInt(2)); 1064 } 1065 } 1066 return notForgivenOverusesById; 1067 } 1068 forgiveHistoricalOverusesForPackage(SQLiteDatabase db, List<String> userPackageIds, long includingStartEpochSeconds, long excludingEndEpochSeconds)1069 public static void forgiveHistoricalOverusesForPackage(SQLiteDatabase db, 1070 List<String> userPackageIds, long includingStartEpochSeconds, 1071 long excludingEndEpochSeconds) { 1072 if (userPackageIds.isEmpty()) { 1073 Slogf.e(TAG, "No user package ids provided to forgive historical overuses."); 1074 return; 1075 } 1076 StringBuilder updateQueryBuilder = new StringBuilder("UPDATE ").append(TABLE_NAME) 1077 .append(" SET ") 1078 .append(COLUMN_NUM_FORGIVEN_OVERUSES).append("=").append(COLUMN_NUM_OVERUSES) 1079 .append(" WHERE ") 1080 .append(COLUMN_DATE_EPOCH).append(">= ").append(includingStartEpochSeconds) 1081 .append(" and ") 1082 .append(COLUMN_DATE_EPOCH).append("< ").append(excludingEndEpochSeconds); 1083 for (int i = 0; i < userPackageIds.size(); i++) { 1084 if (i == 0) { 1085 updateQueryBuilder.append(" and ").append(COLUMN_USER_PACKAGE_ID) 1086 .append(" IN ("); 1087 } else { 1088 updateQueryBuilder.append(", "); 1089 } 1090 updateQueryBuilder.append(userPackageIds.get(i)); 1091 if (i == userPackageIds.size() - 1) { 1092 updateQueryBuilder.append(")"); 1093 } 1094 } 1095 1096 db.execSQL(updateQueryBuilder.toString()); 1097 Slogf.i(TAG, "Attempted to forgive overuses for I/O usage stats entries on pid %d", 1098 Process.myPid()); 1099 } 1100 1101 public static @Nullable List<AtomsProto.CarWatchdogDailyIoUsageSummary> queryDailySystemIoUsageSummaries(SQLiteDatabase db, long includingStartEpochSeconds, long excludingEndEpochSeconds)1102 queryDailySystemIoUsageSummaries(SQLiteDatabase db, long includingStartEpochSeconds, 1103 long excludingEndEpochSeconds) { 1104 StringBuilder queryBuilder = new StringBuilder(); 1105 queryBuilder.append("SELECT SUM(").append(COLUMN_NUM_OVERUSES).append("), ") 1106 .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append("), ") 1107 .append("SUM(").append(COLUMN_WRITTEN_BACKGROUND_BYTES).append("), ") 1108 .append("SUM(").append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append("), ") 1109 .append("date(").append(COLUMN_DATE_EPOCH).append(", '").append(DATE_MODIFIER) 1110 .append("', '").append(ZONE_MODIFIER).append("') as stats_date_epoch ") 1111 .append("FROM ").append(TABLE_NAME).append(" WHERE ") 1112 .append(COLUMN_DATE_EPOCH).append(" >= ? and ") 1113 .append(COLUMN_DATE_EPOCH).append(" < ? ") 1114 .append("GROUP BY stats_date_epoch ") 1115 .append("HAVING SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" + ") 1116 .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" + ") 1117 .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(") > 0 ") 1118 .append("ORDER BY stats_date_epoch ASC"); 1119 1120 String[] selectionArgs = new String[]{String.valueOf(includingStartEpochSeconds), 1121 String.valueOf(excludingEndEpochSeconds)}; 1122 List<AtomsProto.CarWatchdogDailyIoUsageSummary> summaries = new ArrayList<>(); 1123 try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) { 1124 if (cursor.getCount() == 0) { 1125 return null; 1126 } 1127 while (cursor.moveToNext()) { 1128 summaries.add(AtomsProto.CarWatchdogDailyIoUsageSummary.newBuilder() 1129 .setWrittenBytes(WatchdogPerfHandler.constructCarWatchdogPerStateBytes( 1130 /* foregroundBytes= */ cursor.getLong(1), 1131 /* backgroundBytes= */ cursor.getLong(2), 1132 /* garageModeBytes= */ cursor.getLong(3))) 1133 .setOveruseCount(cursor.getInt(0)) 1134 .build()); 1135 } 1136 } 1137 return summaries; 1138 } 1139 querySystemTotalWrittenBytes(SQLiteDatabase db, long includingStartEpochSeconds, long excludingEndEpochSeconds)1140 public static long querySystemTotalWrittenBytes(SQLiteDatabase db, 1141 long includingStartEpochSeconds, long excludingEndEpochSeconds) { 1142 StringBuilder queryBuilder = new StringBuilder(); 1143 queryBuilder.append("SELECT SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" + ") 1144 .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" + ") 1145 .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(") ") 1146 .append("FROM ").append(TABLE_NAME).append(" WHERE ") 1147 .append(COLUMN_DATE_EPOCH).append(" >= ? and ") 1148 .append(COLUMN_DATE_EPOCH).append(" < ? "); 1149 1150 String[] selectionArgs = new String[]{String.valueOf(includingStartEpochSeconds), 1151 String.valueOf(excludingEndEpochSeconds)}; 1152 long totalWrittenBytes = 0; 1153 try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) { 1154 while (cursor.moveToNext()) { 1155 totalWrittenBytes += cursor.getLong(0); 1156 } 1157 } 1158 return totalWrittenBytes; 1159 } 1160 1161 public static @Nullable ArrayMap<String, List<AtomsProto.CarWatchdogDailyIoUsageSummary>> queryTopUsersDailyIoUsageSummaries(SQLiteDatabase db, int numTopUsers, long includingStartEpochSeconds, long excludingEndEpochSeconds)1162 queryTopUsersDailyIoUsageSummaries(SQLiteDatabase db, int numTopUsers, 1163 long includingStartEpochSeconds, long excludingEndEpochSeconds) { 1164 StringBuilder innerQueryBuilder = new StringBuilder(); 1165 innerQueryBuilder.append("SELECT ").append(COLUMN_USER_PACKAGE_ID) 1166 .append(" FROM (SELECT ").append(COLUMN_USER_PACKAGE_ID).append(", ") 1167 .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" + ") 1168 .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" + ") 1169 .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(") AS total_written_bytes ") 1170 .append("FROM ").append(TABLE_NAME).append(" WHERE ") 1171 .append(COLUMN_DATE_EPOCH).append(" >= ? and ") 1172 .append(COLUMN_DATE_EPOCH).append(" < ?") 1173 .append(" GROUP BY ").append(COLUMN_USER_PACKAGE_ID) 1174 .append(" ORDER BY total_written_bytes DESC LIMIT ").append(numTopUsers) 1175 .append(')'); 1176 1177 StringBuilder queryBuilder = new StringBuilder(); 1178 queryBuilder.append("SELECT ").append(COLUMN_USER_PACKAGE_ID).append(", ") 1179 .append("SUM(").append(COLUMN_NUM_OVERUSES).append("), ") 1180 .append("SUM(").append(COLUMN_WRITTEN_FOREGROUND_BYTES).append("), ") 1181 .append("SUM(").append(COLUMN_WRITTEN_BACKGROUND_BYTES).append("), ") 1182 .append("SUM(").append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append("), ") 1183 .append("date(").append(COLUMN_DATE_EPOCH).append(", '").append(DATE_MODIFIER) 1184 .append("', '").append(ZONE_MODIFIER).append("') as stats_date_epoch ") 1185 .append("FROM ").append(TABLE_NAME).append(" WHERE ") 1186 .append(COLUMN_DATE_EPOCH).append(" >= ? and ") 1187 .append(COLUMN_DATE_EPOCH).append(" < ? and (") 1188 .append(COLUMN_WRITTEN_FOREGROUND_BYTES).append(" > 0 or ") 1189 .append(COLUMN_WRITTEN_BACKGROUND_BYTES).append(" > 0 or ") 1190 .append(COLUMN_WRITTEN_GARAGE_MODE_BYTES).append(" > 0) and ") 1191 .append(COLUMN_USER_PACKAGE_ID) 1192 .append(" in (").append(innerQueryBuilder) 1193 .append(") GROUP BY stats_date_epoch, ").append(COLUMN_USER_PACKAGE_ID) 1194 .append(" ORDER BY ").append(COLUMN_USER_PACKAGE_ID) 1195 .append(", stats_date_epoch ASC"); 1196 1197 String[] selectionArgs = new String[]{ 1198 // Outer query selection arguments. 1199 String.valueOf(includingStartEpochSeconds), 1200 String.valueOf(excludingEndEpochSeconds), 1201 // Inner query selection arguments. 1202 String.valueOf(includingStartEpochSeconds), 1203 String.valueOf(excludingEndEpochSeconds)}; 1204 1205 ArrayMap<String, List<AtomsProto.CarWatchdogDailyIoUsageSummary>> summariesById = 1206 new ArrayMap<>(); 1207 try (Cursor cursor = db.rawQuery(queryBuilder.toString(), selectionArgs)) { 1208 if (cursor.getCount() == 0) { 1209 return null; 1210 } 1211 while (cursor.moveToNext()) { 1212 String id = cursor.getString(0); 1213 List<AtomsProto.CarWatchdogDailyIoUsageSummary> summaries = 1214 summariesById.get(id); 1215 if (summaries == null) { 1216 summaries = new ArrayList<>(); 1217 } 1218 summaries.add(AtomsProto.CarWatchdogDailyIoUsageSummary.newBuilder() 1219 .setWrittenBytes(WatchdogPerfHandler.constructCarWatchdogPerStateBytes( 1220 /* foregroundBytes= */ cursor.getLong(2), 1221 /* backgroundBytes= */ cursor.getLong(3), 1222 /* garageModeBytes= */ cursor.getLong(4))) 1223 .setOveruseCount(cursor.getInt(1)) 1224 .build()); 1225 summariesById.put(id, summaries); 1226 } 1227 } 1228 return summariesById; 1229 } 1230 truncateToDate(SQLiteDatabase db, ZonedDateTime latestTruncateDate)1231 public static void truncateToDate(SQLiteDatabase db, ZonedDateTime latestTruncateDate) { 1232 String selection = COLUMN_DATE_EPOCH + " <= ?"; 1233 String[] selectionArgs = { String.valueOf(latestTruncateDate.toEpochSecond()) }; 1234 1235 int rows = db.delete(TABLE_NAME, selection, selectionArgs); 1236 Slogf.i(TAG, "Truncated %d I/O usage stats entries on pid %d", rows, Process.myPid()); 1237 } 1238 trimHistoricalStats(SQLiteDatabase db, ZonedDateTime currentDate)1239 public static void trimHistoricalStats(SQLiteDatabase db, ZonedDateTime currentDate) { 1240 ContentValues values = new ContentValues(); 1241 values.putNull(COLUMN_REMAINING_FOREGROUND_WRITE_BYTES); 1242 values.putNull(COLUMN_REMAINING_BACKGROUND_WRITE_BYTES); 1243 values.putNull(COLUMN_REMAINING_GARAGE_MODE_WRITE_BYTES); 1244 values.putNull(COLUMN_FORGIVEN_FOREGROUND_WRITE_BYTES); 1245 values.putNull(COLUMN_FORGIVEN_BACKGROUND_WRITE_BYTES); 1246 values.putNull(COLUMN_FORGIVEN_GARAGE_MODE_WRITE_BYTES); 1247 1248 String selection = COLUMN_DATE_EPOCH + " < ?"; 1249 String[] selectionArgs = { String.valueOf(currentDate.toEpochSecond()) }; 1250 1251 int rows = db.update(TABLE_NAME, values, selection, selectionArgs); 1252 Slogf.i(TAG, "Trimmed %d I/O usage stats entries on pid %d", rows, Process.myPid()); 1253 } 1254 } 1255 1256 /** 1257 * Defines the Watchdog database and database level operations. 1258 */ 1259 static final class WatchdogDbHelper extends SQLiteOpenHelper { 1260 public static final String DATABASE_NAME = "car_watchdog.db"; 1261 1262 private static final int DATABASE_VERSION = 2; 1263 1264 private ZonedDateTime mLatestShrinkDate; 1265 private TimeSource mTimeSource; 1266 WatchdogDbHelper(Context context, boolean useDataSystemCarDir, TimeSource timeSource)1267 WatchdogDbHelper(Context context, boolean useDataSystemCarDir, TimeSource timeSource) { 1268 /* Use device protected storage because CarService may need to access the database 1269 * before the user has authenticated. 1270 */ 1271 super(context.createDeviceProtectedStorageContext(), useDataSystemCarDir 1272 ? new File(CarWatchdogService.getWatchdogDirFile(), DATABASE_NAME) 1273 .getAbsolutePath() 1274 : DATABASE_NAME, 1275 /* name= */ null, DATABASE_VERSION); 1276 mTimeSource = timeSource; 1277 } 1278 1279 @Override onCreate(SQLiteDatabase db)1280 public void onCreate(SQLiteDatabase db) { 1281 UserPackageSettingsTable.createTable(db); 1282 IoUsageStatsTable.createTable(db); 1283 } 1284 1285 @Override onConfigure(SQLiteDatabase db)1286 public void onConfigure(SQLiteDatabase db) { 1287 db.setForeignKeyConstraintsEnabled(true); 1288 } 1289 close()1290 public synchronized void close() { 1291 super.close(); 1292 mLatestShrinkDate = null; 1293 } 1294 onShrink(SQLiteDatabase db)1295 public void onShrink(SQLiteDatabase db) { 1296 ZonedDateTime currentDate = mTimeSource.getCurrentDate(); 1297 if (currentDate.equals(mLatestShrinkDate)) { 1298 return; 1299 } 1300 IoUsageStatsTable.truncateToDate(db, currentDate.minus(RETENTION_PERIOD)); 1301 IoUsageStatsTable.trimHistoricalStats(db, currentDate); 1302 mLatestShrinkDate = currentDate; 1303 Slogf.i(TAG, "Shrunk watchdog database for the date '%s'", mLatestShrinkDate); 1304 } 1305 1306 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion)1307 public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { 1308 if (oldVersion != 1) { 1309 return; 1310 } 1311 // Upgrade logic from version 1 to 2. 1312 int upgradeVersion = oldVersion; 1313 db.beginTransaction(); 1314 try { 1315 upgradeToVersion2(db); 1316 db.setTransactionSuccessful(); 1317 upgradeVersion = currentVersion; 1318 Slogf.i(TAG, "Successfully upgraded database from version %d to %d", oldVersion, 1319 upgradeVersion); 1320 } finally { 1321 db.endTransaction(); 1322 } 1323 if (upgradeVersion != currentVersion) { 1324 Slogf.i(TAG, "Failed to upgrade database from version %d to %d. " 1325 + "Attempting to recreate database.", oldVersion, currentVersion); 1326 recreateDatabase(db); 1327 } 1328 } 1329 1330 /** 1331 * Upgrades the given {@code db} to version 2. 1332 * 1333 * <p>Database version 2 replaces the primary key in {@link UserPackageSettingsTable} with 1334 * an auto-incrementing integer ID and uses the ID (instead of its rowid) as one of 1335 * the primary keys in {@link IoUsageStatsTable} along with a foreign key dependency. 1336 * 1337 * <p>Only the entries from {@link UserPackageSettingsTable} are migrated to the version 2 1338 * database because in version 1 only the current day's entries in {@link IoUsageStatsTable} 1339 * are mappable to the former table and dropping these entries is tolerable. 1340 */ upgradeToVersion2(SQLiteDatabase db)1341 private void upgradeToVersion2(SQLiteDatabase db) { 1342 String oldUserPackageSettingsTable = UserPackageSettingsTable.TABLE_NAME + "_old_v1"; 1343 StringBuilder execSql = new StringBuilder("ALTER TABLE ") 1344 .append(UserPackageSettingsTable.TABLE_NAME) 1345 .append(" RENAME TO ").append(oldUserPackageSettingsTable); 1346 db.execSQL(execSql.toString()); 1347 1348 execSql = new StringBuilder("DROP TABLE IF EXISTS ") 1349 .append(IoUsageStatsTable.TABLE_NAME); 1350 db.execSQL(execSql.toString()); 1351 1352 UserPackageSettingsTable.createTable(db); 1353 IoUsageStatsTable.createTable(db); 1354 1355 execSql = new StringBuilder("INSERT INTO ").append(UserPackageSettingsTable.TABLE_NAME) 1356 .append(" (").append(UserPackageSettingsTable.COLUMN_PACKAGE_NAME).append(", ") 1357 .append(UserPackageSettingsTable.COLUMN_USER_ID).append(", ") 1358 .append(UserPackageSettingsTable.COLUMN_KILLABLE_STATE).append(") ") 1359 .append("SELECT ").append(UserPackageSettingsTable.COLUMN_PACKAGE_NAME) 1360 .append(", ").append(UserPackageSettingsTable.COLUMN_USER_ID).append(", ") 1361 .append(UserPackageSettingsTable.COLUMN_KILLABLE_STATE).append(" FROM ") 1362 .append(oldUserPackageSettingsTable); 1363 db.execSQL(execSql.toString()); 1364 1365 execSql = new StringBuilder("DROP TABLE IF EXISTS ") 1366 .append(oldUserPackageSettingsTable); 1367 db.execSQL(execSql.toString()); 1368 } 1369 recreateDatabase(SQLiteDatabase db)1370 private void recreateDatabase(SQLiteDatabase db) { 1371 db.execSQL(new StringBuilder("DROP TABLE IF EXISTS ") 1372 .append(UserPackageSettingsTable.TABLE_NAME).toString()); 1373 db.execSQL(new StringBuilder("DROP TABLE IF EXISTS ") 1374 .append(IoUsageStatsTable.TABLE_NAME).toString()); 1375 1376 onCreate(db); 1377 Slogf.e(TAG, "Successfully recreated database version %d", DATABASE_VERSION); 1378 } 1379 } 1380 1381 1382 private static final class UserPackage { 1383 public final String userPackageId; 1384 public final @UserIdInt int userId; 1385 public final String packageName; 1386 UserPackage(String userPackageId, @UserIdInt int userId, String packageName)1387 UserPackage(String userPackageId, @UserIdInt int userId, String packageName) { 1388 this.userPackageId = userPackageId; 1389 this.userId = userId; 1390 this.packageName = packageName; 1391 } 1392 getKey()1393 public String getKey() { 1394 return getKey(userId, packageName); 1395 } 1396 getKey(int userId, String packageName)1397 public static String getKey(int userId, String packageName) { 1398 return String.format(Locale.ENGLISH, "%d:%s", userId, packageName); 1399 } 1400 1401 @Override toString()1402 public String toString() { 1403 return new StringBuilder("UserPackage{userPackageId: ").append(userPackageId) 1404 .append(", userId: ").append(userId) 1405 .append(", packageName: ").append(packageName).append("}").toString(); 1406 } 1407 } 1408 } 1409