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