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