1 /* 2 * Copyright (C) 2022 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.settings.fuelgauge.batteryusage; 18 19 import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTimeForLogging; 20 21 import android.app.usage.IUsageStatsManager; 22 import android.app.usage.UsageStatsManager; 23 import android.content.ContentResolver; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.SharedPreferences; 28 import android.content.pm.PackageManager; 29 import android.database.Cursor; 30 import android.net.Uri; 31 import android.os.AsyncTask; 32 import android.os.BatteryManager; 33 import android.os.BatteryUsageStats; 34 import android.os.RemoteException; 35 import android.os.SystemClock; 36 import android.os.UserManager; 37 import android.util.ArrayMap; 38 import android.util.ArraySet; 39 import android.util.Log; 40 41 import androidx.annotation.Nullable; 42 import androidx.annotation.VisibleForTesting; 43 44 import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action; 45 import com.android.settings.fuelgauge.BatteryUtils; 46 import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils; 47 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase; 48 import com.android.settings.overlay.FeatureFactory; 49 import com.android.settingslib.fuelgauge.BatteryStatus; 50 51 import java.io.PrintWriter; 52 import java.time.Clock; 53 import java.time.Duration; 54 import java.util.ArrayList; 55 import java.util.Calendar; 56 import java.util.List; 57 import java.util.Locale; 58 import java.util.Map; 59 import java.util.Set; 60 import java.util.TimeZone; 61 import java.util.function.Function; 62 import java.util.function.Supplier; 63 import java.util.stream.Collectors; 64 65 /** A utility class to operate battery usage database. */ 66 public final class DatabaseUtils { 67 private static final String TAG = "DatabaseUtils"; 68 private static final String SHARED_PREFS_FILE = "battery_usage_shared_prefs"; 69 private static final long INVALID_TIMESTAMP = 0L; 70 71 static final String KEY_LAST_LOAD_FULL_CHARGE_TIME = "last_load_full_charge_time"; 72 static final String KEY_LAST_UPLOAD_FULL_CHARGE_TIME = "last_upload_full_charge_time"; 73 static final String KEY_LAST_USAGE_SOURCE = "last_usage_source"; 74 static final String KEY_DISMISSED_POWER_ANOMALY_KEYS = "dismissed_power_anomaly_keys"; 75 76 /** An authority name of the battery content provider. */ 77 public static final String AUTHORITY = "com.android.settings.battery.usage.provider"; 78 79 /** A table name for app usage events. */ 80 public static final String APP_USAGE_EVENT_TABLE = "AppUsageEvent"; 81 82 /** A table name for battery events. */ 83 public static final String BATTERY_EVENT_TABLE = "BatteryEvent"; 84 85 /** A table name for battery usage history. */ 86 public static final String BATTERY_STATE_TABLE = "BatteryState"; 87 88 /** A table name for battery usage slot. */ 89 public static final String BATTERY_USAGE_SLOT_TABLE = "BatteryUsageSlot"; 90 91 /** A path name for last full charge time query. */ 92 public static final String LAST_FULL_CHARGE_TIMESTAMP_PATH = "lastFullChargeTimestamp"; 93 94 /** A path name for querying the latest record timestamp in battery state table. */ 95 public static final String BATTERY_STATE_LATEST_TIMESTAMP_PATH = "batteryStateLatestTimestamp"; 96 97 /** A path name for app usage latest timestamp query. */ 98 public static final String APP_USAGE_LATEST_TIMESTAMP_PATH = "appUsageLatestTimestamp"; 99 100 /** Key for query parameter timestamp used in BATTERY_CONTENT_URI */ 101 public static final String QUERY_KEY_TIMESTAMP = "timestamp"; 102 103 /** Key for query parameter userid used in APP_USAGE_EVENT_URI */ 104 public static final String QUERY_KEY_USERID = "userid"; 105 106 /** Key for query parameter battery event type used in BATTERY_EVENT_URI */ 107 public static final String QUERY_BATTERY_EVENT_TYPE = "batteryEventType"; 108 109 public static final long INVALID_USER_ID = Integer.MIN_VALUE; 110 111 /** 112 * The buffer hours to query app usage events that may have begun or ended out of the final 113 * desired time frame. 114 */ 115 public static final long USAGE_QUERY_BUFFER_HOURS = Duration.ofHours(3).toMillis(); 116 117 /** A content URI to access app usage events data. */ 118 public static final Uri APP_USAGE_EVENT_URI = 119 new Uri.Builder() 120 .scheme(ContentResolver.SCHEME_CONTENT) 121 .authority(AUTHORITY) 122 .appendPath(APP_USAGE_EVENT_TABLE) 123 .build(); 124 125 /** A content URI to access battery events data. */ 126 public static final Uri BATTERY_EVENT_URI = 127 new Uri.Builder() 128 .scheme(ContentResolver.SCHEME_CONTENT) 129 .authority(AUTHORITY) 130 .appendPath(BATTERY_EVENT_TABLE) 131 .build(); 132 133 /** A content URI to access battery usage states data. */ 134 public static final Uri BATTERY_CONTENT_URI = 135 new Uri.Builder() 136 .scheme(ContentResolver.SCHEME_CONTENT) 137 .authority(AUTHORITY) 138 .appendPath(BATTERY_STATE_TABLE) 139 .build(); 140 141 /** A content URI to access battery usage slots data. */ 142 public static final Uri BATTERY_USAGE_SLOT_URI = 143 new Uri.Builder() 144 .scheme(ContentResolver.SCHEME_CONTENT) 145 .authority(AUTHORITY) 146 .appendPath(BATTERY_USAGE_SLOT_TABLE) 147 .build(); 148 149 /** A list of level record event types to access battery usage data. */ 150 public static final List<BatteryEventType> BATTERY_LEVEL_RECORD_EVENTS = 151 List.of(BatteryEventType.FULL_CHARGED, BatteryEventType.EVEN_HOUR); 152 153 // For testing only. 154 @VisibleForTesting static Supplier<Cursor> sFakeSupplier; 155 DatabaseUtils()156 private DatabaseUtils() {} 157 158 /** Returns the latest timestamp current user data in app usage event table. */ getAppUsageStartTimestampOfUser( Context context, final long userId, final long earliestTimestamp)159 public static long getAppUsageStartTimestampOfUser( 160 Context context, final long userId, final long earliestTimestamp) { 161 final long startTime = System.currentTimeMillis(); 162 // Builds the content uri everytime to avoid cache. 163 final Uri appUsageLatestTimestampUri = 164 new Uri.Builder() 165 .scheme(ContentResolver.SCHEME_CONTENT) 166 .authority(AUTHORITY) 167 .appendPath(APP_USAGE_LATEST_TIMESTAMP_PATH) 168 .appendQueryParameter(QUERY_KEY_USERID, Long.toString(userId)) 169 .build(); 170 final long latestTimestamp = 171 loadLongFromContentProvider( 172 context, appUsageLatestTimestampUri, /* defaultValue= */ INVALID_TIMESTAMP); 173 final String latestTimestampString = utcToLocalTimeForLogging(latestTimestamp); 174 Log.d( 175 TAG, 176 String.format( 177 "getAppUsageStartTimestampOfUser() userId=%d latestTimestamp=%s in %d/ms", 178 userId, latestTimestampString, (System.currentTimeMillis() - startTime))); 179 // Use (latestTimestamp + 1) here to avoid loading the events of the latestTimestamp 180 // repeatedly. 181 return Math.max(latestTimestamp + 1, earliestTimestamp); 182 } 183 184 /** Returns the current user data in app usage event table. */ getAppUsageEventForUsers( Context context, final Calendar calendar, final List<Integer> userIds, final long rawStartTimestamp)185 public static List<AppUsageEvent> getAppUsageEventForUsers( 186 Context context, 187 final Calendar calendar, 188 final List<Integer> userIds, 189 final long rawStartTimestamp) { 190 final long startTime = System.currentTimeMillis(); 191 final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar); 192 // Query a longer time period and then trim to the original time period in order to make 193 // sure the app usage calculation near the boundaries is correct. 194 final long queryTimestamp = 195 Math.max(rawStartTimestamp, sixDaysAgoTimestamp) - USAGE_QUERY_BUFFER_HOURS; 196 Log.d(TAG, "sixDaysAgoTimestamp: " + utcToLocalTimeForLogging(sixDaysAgoTimestamp)); 197 final String queryUserIdString = 198 userIds.stream() 199 .map(userId -> String.valueOf(userId)) 200 .collect(Collectors.joining(",")); 201 // Builds the content uri everytime to avoid cache. 202 final Uri appUsageEventUri = 203 new Uri.Builder() 204 .scheme(ContentResolver.SCHEME_CONTENT) 205 .authority(AUTHORITY) 206 .appendPath(APP_USAGE_EVENT_TABLE) 207 .appendQueryParameter(QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp)) 208 .appendQueryParameter(QUERY_KEY_USERID, queryUserIdString) 209 .build(); 210 211 final List<AppUsageEvent> appUsageEventList = 212 loadListFromContentProvider( 213 context, appUsageEventUri, ConvertUtils::convertToAppUsageEvent); 214 Log.d( 215 TAG, 216 String.format( 217 "getAppUsageEventForUser userId=%s size=%d in %d/ms", 218 queryUserIdString, 219 appUsageEventList.size(), 220 (System.currentTimeMillis() - startTime))); 221 return appUsageEventList; 222 } 223 224 /** Returns the battery event data since the query timestamp in battery event table. */ getBatteryEvents( Context context, final Calendar calendar, final long rawStartTimestamp, final List<BatteryEventType> queryBatteryEventTypes)225 public static List<BatteryEvent> getBatteryEvents( 226 Context context, 227 final Calendar calendar, 228 final long rawStartTimestamp, 229 final List<BatteryEventType> queryBatteryEventTypes) { 230 final long startTime = System.currentTimeMillis(); 231 final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar); 232 final long queryTimestamp = Math.max(rawStartTimestamp, sixDaysAgoTimestamp); 233 Log.d(TAG, "getBatteryEvents for timestamp: " + queryTimestamp); 234 final String queryBatteryEventTypesString = 235 queryBatteryEventTypes.stream() 236 .map(type -> String.valueOf(type.getNumber())) 237 .collect(Collectors.joining(",")); 238 // Builds the content uri everytime to avoid cache. 239 final Uri batteryEventUri = 240 new Uri.Builder() 241 .scheme(ContentResolver.SCHEME_CONTENT) 242 .authority(AUTHORITY) 243 .appendPath(BATTERY_EVENT_TABLE) 244 .appendQueryParameter(QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp)) 245 .appendQueryParameter( 246 QUERY_BATTERY_EVENT_TYPE, queryBatteryEventTypesString) 247 .build(); 248 249 final List<BatteryEvent> batteryEventList = 250 loadListFromContentProvider( 251 context, batteryEventUri, ConvertUtils::convertToBatteryEvent); 252 Log.d( 253 TAG, 254 String.format( 255 "getBatteryEvents size=%d in %d/ms", 256 batteryEventList.size(), (System.currentTimeMillis() - startTime))); 257 return batteryEventList; 258 } 259 260 /** 261 * Returns the battery usage slot data after {@code rawStartTimestamp} in battery event table. 262 */ getBatteryUsageSlots( Context context, final Calendar calendar, final long rawStartTimestamp)263 public static List<BatteryUsageSlot> getBatteryUsageSlots( 264 Context context, final Calendar calendar, final long rawStartTimestamp) { 265 final long startTime = System.currentTimeMillis(); 266 final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar); 267 final long queryTimestamp = Math.max(rawStartTimestamp, sixDaysAgoTimestamp); 268 Log.d(TAG, "getBatteryUsageSlots for timestamp: " + queryTimestamp); 269 // Builds the content uri everytime to avoid cache. 270 final Uri batteryUsageSlotUri = 271 new Uri.Builder() 272 .scheme(ContentResolver.SCHEME_CONTENT) 273 .authority(AUTHORITY) 274 .appendPath(BATTERY_USAGE_SLOT_TABLE) 275 .appendQueryParameter(QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp)) 276 .build(); 277 278 final List<BatteryUsageSlot> batteryUsageSlotList = 279 loadListFromContentProvider( 280 context, batteryUsageSlotUri, ConvertUtils::convertToBatteryUsageSlot); 281 Log.d( 282 TAG, 283 String.format( 284 "getBatteryUsageSlots size=%d in %d/ms", 285 batteryUsageSlotList.size(), (System.currentTimeMillis() - startTime))); 286 return batteryUsageSlotList; 287 } 288 289 /** Returns the last full charge time. */ getLastFullChargeTime(Context context)290 public static long getLastFullChargeTime(Context context) { 291 final long startTime = System.currentTimeMillis(); 292 // Builds the content uri everytime to avoid cache. 293 final Uri lastFullChargeTimeUri = 294 new Uri.Builder() 295 .scheme(ContentResolver.SCHEME_CONTENT) 296 .authority(AUTHORITY) 297 .appendPath(LAST_FULL_CHARGE_TIMESTAMP_PATH) 298 .build(); 299 final long lastFullChargeTime = 300 loadLongFromContentProvider( 301 context, lastFullChargeTimeUri, /* defaultValue= */ INVALID_TIMESTAMP); 302 final String lastFullChargeTimeString = utcToLocalTimeForLogging(lastFullChargeTime); 303 Log.d( 304 TAG, 305 String.format( 306 "getLastFullChargeTime() lastFullChargeTime=%s in %d/ms", 307 lastFullChargeTimeString, (System.currentTimeMillis() - startTime))); 308 return lastFullChargeTime; 309 } 310 311 /** Returns the first battery state timestamp no later than the {@code queryTimestamp}. */ 312 @VisibleForTesting getBatteryStateLatestTimestampBeforeQueryTimestamp( Context context, final long queryTimestamp)313 static long getBatteryStateLatestTimestampBeforeQueryTimestamp( 314 Context context, final long queryTimestamp) { 315 final long startTime = System.currentTimeMillis(); 316 // Builds the content uri everytime to avoid cache. 317 final Uri batteryStateLatestTimestampUri = 318 new Uri.Builder() 319 .scheme(ContentResolver.SCHEME_CONTENT) 320 .authority(AUTHORITY) 321 .appendPath(BATTERY_STATE_LATEST_TIMESTAMP_PATH) 322 .appendQueryParameter(QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp)) 323 .build(); 324 final long batteryStateLatestTimestamp = 325 loadLongFromContentProvider( 326 context, 327 batteryStateLatestTimestampUri, 328 /* defaultValue= */ INVALID_TIMESTAMP); 329 final String batteryStateLatestTimestampString = 330 utcToLocalTimeForLogging(batteryStateLatestTimestamp); 331 Log.d( 332 TAG, 333 String.format( 334 "getBatteryStateLatestTimestamp() batteryStateLatestTimestamp=%s in %d/ms", 335 batteryStateLatestTimestampString, 336 (System.currentTimeMillis() - startTime))); 337 return batteryStateLatestTimestamp; 338 } 339 340 /** Returns the battery history map after the given timestamp. */ 341 @VisibleForTesting getHistoryMapSinceQueryTimestamp( Context context, final long queryTimestamp)342 static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceQueryTimestamp( 343 Context context, final long queryTimestamp) { 344 final long startTime = System.currentTimeMillis(); 345 // Builds the content uri everytime to avoid cache. 346 final Uri batteryStateUri = 347 new Uri.Builder() 348 .scheme(ContentResolver.SCHEME_CONTENT) 349 .authority(AUTHORITY) 350 .appendPath(BATTERY_STATE_TABLE) 351 .appendQueryParameter(QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp)) 352 .build(); 353 354 final List<BatteryHistEntry> batteryHistEntryList = 355 loadListFromContentProvider( 356 context, batteryStateUri, cursor -> new BatteryHistEntry(cursor)); 357 final Map<Long, Map<String, BatteryHistEntry>> resultMap = new ArrayMap(); 358 for (final BatteryHistEntry entry : batteryHistEntryList) { 359 final long timestamp = entry.mTimestamp; 360 final String key = entry.getKey(); 361 Map batteryHistEntryMap = resultMap.get(timestamp); 362 // Creates new one if there is no corresponding map. 363 if (batteryHistEntryMap == null) { 364 batteryHistEntryMap = new ArrayMap(); 365 resultMap.put(timestamp, batteryHistEntryMap); 366 } 367 batteryHistEntryMap.put(key, entry); 368 } 369 370 if (resultMap == null || resultMap.isEmpty()) { 371 Log.d(TAG, "getBatteryHistoryMap() returns empty or null"); 372 } else { 373 Log.d( 374 TAG, 375 String.format( 376 "getBatteryHistoryMap() size=%d in %d/ms", 377 resultMap.size(), (System.currentTimeMillis() - startTime))); 378 } 379 return resultMap; 380 } 381 382 /** 383 * Returns the battery history map since the latest record no later than the given timestamp. If 384 * there is no record before the given timestamp or the given timestamp is before last full 385 * charge time, returns the history map since last full charge time. 386 */ 387 public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLatestRecordBeforeQueryTimestamp( Context context, Calendar calendar, final long queryTimestamp, final long lastFullChargeTime)388 getHistoryMapSinceLatestRecordBeforeQueryTimestamp( 389 Context context, 390 Calendar calendar, 391 final long queryTimestamp, 392 final long lastFullChargeTime) { 393 final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar); 394 Log.d(TAG, "sixDaysAgoTimestamp: " + utcToLocalTimeForLogging(sixDaysAgoTimestamp)); 395 final long batteryStateLatestTimestamp = 396 queryTimestamp == 0L 397 ? 0L 398 : getBatteryStateLatestTimestampBeforeQueryTimestamp( 399 context, queryTimestamp); 400 final long maxTimestamp = 401 Math.max( 402 Math.max(sixDaysAgoTimestamp, lastFullChargeTime), 403 batteryStateLatestTimestamp); 404 return getHistoryMapSinceQueryTimestamp(context, maxTimestamp); 405 } 406 407 /** Returns the history map since last full charge time. */ getHistoryMapSinceLastFullCharge( Context context, Calendar calendar)408 public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge( 409 Context context, Calendar calendar) { 410 final long lastFullChargeTime = getLastFullChargeTime(context); 411 return getHistoryMapSinceLatestRecordBeforeQueryTimestamp( 412 context, calendar, 0, lastFullChargeTime); 413 } 414 415 /** Clears all data in the battery usage database. */ clearAll(Context context)416 public static void clearAll(Context context) { 417 AsyncTask.execute( 418 () -> { 419 try { 420 final BatteryStateDatabase database = 421 BatteryStateDatabase.getInstance(context.getApplicationContext()); 422 database.appUsageEventDao().clearAll(); 423 database.batteryEventDao().clearAll(); 424 database.batteryStateDao().clearAll(); 425 database.batteryUsageSlotDao().clearAll(); 426 database.batteryReattributeDao().clearAll(); 427 } catch (RuntimeException e) { 428 Log.e(TAG, "clearAll() failed", e); 429 } 430 }); 431 } 432 433 /** Clears data after a specific startTimestamp in the battery usage database. */ clearAllAfter(Context context, long startTimestamp)434 public static void clearAllAfter(Context context, long startTimestamp) { 435 AsyncTask.execute( 436 () -> { 437 try { 438 final BatteryStateDatabase database = 439 BatteryStateDatabase.getInstance(context.getApplicationContext()); 440 database.appUsageEventDao().clearAllAfter(startTimestamp); 441 database.batteryEventDao().clearAllAfter(startTimestamp); 442 database.batteryStateDao().clearAllAfter(startTimestamp); 443 database.batteryUsageSlotDao().clearAllAfter(startTimestamp); 444 database.batteryReattributeDao().clearAllAfter(startTimestamp); 445 } catch (RuntimeException e) { 446 Log.e(TAG, "clearAllAfter() failed", e); 447 } 448 }); 449 } 450 451 /** Clears generated cache data in the battery usage database. */ clearEvenHourCacheData(Context context)452 public static void clearEvenHourCacheData(Context context) { 453 AsyncTask.execute( 454 () -> { 455 try { 456 final BatteryStateDatabase database = 457 BatteryStateDatabase.getInstance(context.getApplicationContext()); 458 database.batteryEventDao().clearEvenHourEvent(); 459 database.batteryUsageSlotDao().clearAll(); 460 } catch (RuntimeException e) { 461 Log.e(TAG, "clearEvenHourCacheData() failed", e); 462 } 463 }); 464 } 465 466 /** Clears all out-of-date data in the battery usage database. */ clearExpiredDataIfNeeded(Context context)467 public static void clearExpiredDataIfNeeded(Context context) { 468 AsyncTask.execute( 469 () -> { 470 try { 471 final int dataRetentionDays = 472 FeatureFactory.getFeatureFactory() 473 .getPowerUsageFeatureProvider().getDataRetentionDays(); 474 final BatteryStateDatabase database = 475 BatteryStateDatabase.getInstance(context.getApplicationContext()); 476 final long earliestTimestamp = 477 Clock.systemUTC().millis() 478 - Duration.ofDays(dataRetentionDays).toMillis(); 479 database.appUsageEventDao().clearAllBefore(earliestTimestamp); 480 database.batteryEventDao().clearAllBefore(earliestTimestamp); 481 database.batteryStateDao().clearAllBefore(earliestTimestamp); 482 database.batteryUsageSlotDao().clearAllBefore(earliestTimestamp); 483 database.batteryReattributeDao().clearAllBefore(earliestTimestamp); 484 } catch (RuntimeException e) { 485 Log.e(TAG, "clearAllBefore() failed", e); 486 } 487 }); 488 } 489 490 /** Clears data after new updated time and refresh periodic job. */ clearDataAfterTimeChangedIfNeeded(Context context, Intent intent)491 public static void clearDataAfterTimeChangedIfNeeded(Context context, Intent intent) { 492 if ((intent.hasExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT))) { 493 BatteryUsageLogUtils.writeLog( 494 context, 495 Action.TIME_UPDATED, 496 "Database is not cleared because the time change intent is" 497 + " for time format change"); 498 return; 499 } 500 AsyncTask.execute( 501 () -> { 502 try { 503 clearDataAfterTimeChangedIfNeededInternal(context); 504 } catch (RuntimeException e) { 505 Log.e(TAG, "clearDataAfterTimeChangedIfNeeded() failed", e); 506 BatteryUsageLogUtils.writeLog( 507 context, 508 Action.TIME_UPDATED, 509 "clearDataAfterTimeChangedIfNeeded() failed" + e); 510 } 511 }); 512 } 513 514 /** Clears all data and reset jobs if timezone changed. */ clearDataAfterTimeZoneChangedIfNeeded(Context context)515 public static void clearDataAfterTimeZoneChangedIfNeeded(Context context) { 516 AsyncTask.execute( 517 () -> { 518 try { 519 clearDataAfterTimeZoneChangedIfNeededInternal(context); 520 } catch (RuntimeException e) { 521 Log.e(TAG, "clearDataAfterTimeZoneChangedIfNeeded() failed", e); 522 BatteryUsageLogUtils.writeLog( 523 context, 524 Action.TIMEZONE_UPDATED, 525 "clearDataAfterTimeZoneChangedIfNeeded() failed" + e); 526 } 527 }); 528 } 529 530 /** Returns the timestamp for 00:00 6 days before the calendar date. */ getTimestampSixDaysAgo(Calendar calendar)531 public static long getTimestampSixDaysAgo(Calendar calendar) { 532 Calendar startCalendar = 533 calendar == null ? Calendar.getInstance() : (Calendar) calendar.clone(); 534 startCalendar.add(Calendar.DAY_OF_YEAR, -6); 535 startCalendar.set(Calendar.HOUR_OF_DAY, 0); 536 startCalendar.set(Calendar.MINUTE, 0); 537 startCalendar.set(Calendar.SECOND, 0); 538 startCalendar.set(Calendar.MILLISECOND, 0); 539 return startCalendar.getTimeInMillis(); 540 } 541 542 /** 543 * Returns the context with profile parent identity when current user is an additional profile. 544 */ getParentContext(Context context)545 public static Context getParentContext(Context context) { 546 if (com.android.settingslib.fuelgauge.BatteryUtils.isAdditionalProfile(context)) { 547 try { 548 return context.createPackageContextAsUser( 549 /* packageName= */ context.getPackageName(), 550 /* flags= */ 0, 551 /* user= */ context.getSystemService(UserManager.class) 552 .getProfileParent(context.getUser())); 553 } catch (PackageManager.NameNotFoundException e) { 554 Log.e(TAG, "context.createPackageContextAsUser() fail:", e); 555 return null; 556 } 557 } 558 return context; 559 } 560 sendAppUsageEventData( final Context context, final List<AppUsageEvent> appUsageEventList)561 static List<ContentValues> sendAppUsageEventData( 562 final Context context, final List<AppUsageEvent> appUsageEventList) { 563 final long startTime = System.currentTimeMillis(); 564 // Creates the ContentValues list to insert them into provider. 565 final List<ContentValues> valuesList = new ArrayList<>(); 566 appUsageEventList.stream() 567 .filter(appUsageEvent -> appUsageEvent.hasUid()) 568 .forEach( 569 appUsageEvent -> 570 valuesList.add( 571 ConvertUtils.convertAppUsageEventToContentValues( 572 appUsageEvent))); 573 int size = 0; 574 final ContentResolver resolver = context.getContentResolver(); 575 // Inserts all ContentValues into battery provider. 576 if (!valuesList.isEmpty()) { 577 final ContentValues[] valuesArray = new ContentValues[valuesList.size()]; 578 valuesList.toArray(valuesArray); 579 try { 580 size = resolver.bulkInsert(APP_USAGE_EVENT_URI, valuesArray); 581 resolver.notifyChange(APP_USAGE_EVENT_URI, /* observer= */ null); 582 Log.d(TAG, "insert() app usage events data into database"); 583 } catch (Exception e) { 584 Log.e(TAG, "bulkInsert() app usage data into database error:", e); 585 } 586 } 587 Log.d( 588 TAG, 589 String.format( 590 "sendAppUsageEventData() size=%d in %d/ms", 591 size, (System.currentTimeMillis() - startTime))); 592 return valuesList; 593 } 594 sendBatteryEventData( final Context context, final BatteryEvent batteryEvent)595 static ContentValues sendBatteryEventData( 596 final Context context, final BatteryEvent batteryEvent) { 597 final long startTime = System.currentTimeMillis(); 598 ContentValues contentValues = ConvertUtils.convertBatteryEventToContentValues(batteryEvent); 599 final ContentResolver resolver = context.getContentResolver(); 600 try { 601 resolver.insert(BATTERY_EVENT_URI, contentValues); 602 Log.d(TAG, "insert() battery event data into database: " + batteryEvent.toString()); 603 } catch (Exception e) { 604 Log.e(TAG, "insert() battery event data into database error:", e); 605 } 606 Log.d( 607 TAG, 608 String.format( 609 "sendBatteryEventData() in %d/ms", 610 (System.currentTimeMillis() - startTime))); 611 return contentValues; 612 } 613 sendBatteryEventData( final Context context, final List<BatteryEvent> batteryEventList)614 static List<ContentValues> sendBatteryEventData( 615 final Context context, final List<BatteryEvent> batteryEventList) { 616 final long startTime = System.currentTimeMillis(); 617 // Creates the ContentValues list to insert them into provider. 618 final List<ContentValues> valuesList = new ArrayList<>(); 619 batteryEventList.stream() 620 .forEach( 621 batteryEvent -> 622 valuesList.add( 623 ConvertUtils.convertBatteryEventToContentValues( 624 batteryEvent))); 625 int size = 0; 626 final ContentResolver resolver = context.getContentResolver(); 627 // Inserts all ContentValues into battery provider. 628 if (!valuesList.isEmpty()) { 629 final ContentValues[] valuesArray = new ContentValues[valuesList.size()]; 630 valuesList.toArray(valuesArray); 631 try { 632 size = resolver.bulkInsert(BATTERY_EVENT_URI, valuesArray); 633 resolver.notifyChange(BATTERY_EVENT_URI, /* observer= */ null); 634 Log.d(TAG, "insert() battery event data into database"); 635 } catch (Exception e) { 636 Log.e(TAG, "bulkInsert() battery event data into database error:", e); 637 } 638 } 639 Log.d( 640 TAG, 641 String.format( 642 "sendBatteryEventData() size=%d in %d/ms", 643 size, (System.currentTimeMillis() - startTime))); 644 return valuesList; 645 } 646 sendBatteryUsageSlotData( final Context context, final List<BatteryUsageSlot> batteryUsageSlotList)647 static List<ContentValues> sendBatteryUsageSlotData( 648 final Context context, final List<BatteryUsageSlot> batteryUsageSlotList) { 649 final long startTime = System.currentTimeMillis(); 650 // Creates the ContentValues list to insert them into provider. 651 final List<ContentValues> valuesList = new ArrayList<>(); 652 batteryUsageSlotList.stream() 653 .forEach( 654 batteryUsageSlot -> 655 valuesList.add( 656 ConvertUtils.convertBatteryUsageSlotToContentValues( 657 batteryUsageSlot))); 658 int size = 0; 659 final ContentResolver resolver = context.getContentResolver(); 660 // Inserts all ContentValues into battery provider. 661 if (!valuesList.isEmpty()) { 662 final ContentValues[] valuesArray = new ContentValues[valuesList.size()]; 663 valuesList.toArray(valuesArray); 664 try { 665 size = resolver.bulkInsert(BATTERY_USAGE_SLOT_URI, valuesArray); 666 resolver.notifyChange(BATTERY_USAGE_SLOT_URI, /* observer= */ null); 667 Log.d(TAG, "insert() battery usage slots data into database"); 668 } catch (Exception e) { 669 Log.e(TAG, "bulkInsert() battery usage slots data into database error:", e); 670 } 671 } 672 Log.d( 673 TAG, 674 String.format( 675 "sendBatteryUsageSlotData() size=%d in %d/ms", 676 size, (System.currentTimeMillis() - startTime))); 677 return valuesList; 678 } 679 sendBatteryEntryData( final Context context, final long snapshotTimestamp, @Nullable final List<BatteryEntry> batteryEntryList, final BatteryUsageStats batteryUsageStats, final boolean isFullChargeStart)680 static List<ContentValues> sendBatteryEntryData( 681 final Context context, 682 final long snapshotTimestamp, 683 @Nullable final List<BatteryEntry> batteryEntryList, 684 final BatteryUsageStats batteryUsageStats, 685 final boolean isFullChargeStart) { 686 final long startTime = System.currentTimeMillis(); 687 final Intent intent = BatteryUtils.getBatteryIntent(context); 688 if (intent == null) { 689 Log.e(TAG, "sendBatteryEntryData(): cannot fetch battery intent"); 690 return null; 691 } 692 final int batteryLevel = BatteryStatus.getBatteryLevel(intent); 693 final int batteryStatus = 694 intent.getIntExtra( 695 BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN); 696 final int batteryHealth = 697 intent.getIntExtra( 698 BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN); 699 // We should use the same timestamp for each data snapshot. 700 final long snapshotBootTimestamp = SystemClock.elapsedRealtime(); 701 702 // Creates the ContentValues list to insert them into provider. 703 final List<ContentValues> valuesList = new ArrayList<>(); 704 if (batteryEntryList != null) { 705 for (BatteryEntry entry : batteryEntryList) { 706 final long foregroundMs = entry.getTimeInForegroundMs(); 707 final long foregroundServiceMs = entry.getTimeInForegroundServiceMs(); 708 final long backgroundMs = entry.getTimeInBackgroundMs(); 709 if (entry.getConsumedPower() == 0 710 && (foregroundMs != 0 || foregroundServiceMs != 0 || backgroundMs != 0)) { 711 Log.w( 712 TAG, 713 String.format( 714 "no consumed power but has running time for %s" 715 + " time=%d|%d|%d", 716 entry.getLabel(), 717 foregroundMs, 718 foregroundServiceMs, 719 backgroundMs)); 720 } 721 if (entry.getConsumedPower() == 0 722 && foregroundMs == 0 723 && foregroundServiceMs == 0 724 && backgroundMs == 0) { 725 continue; 726 } 727 valuesList.add( 728 ConvertUtils.convertBatteryEntryToContentValues( 729 entry, 730 batteryUsageStats, 731 batteryLevel, 732 batteryStatus, 733 batteryHealth, 734 snapshotBootTimestamp, 735 snapshotTimestamp, 736 isFullChargeStart)); 737 } 738 } 739 740 int size = 1; 741 final ContentResolver resolver = context.getContentResolver(); 742 String errorMessage = ""; 743 // Inserts all ContentValues into battery provider. 744 if (!valuesList.isEmpty()) { 745 final ContentValues[] valuesArray = new ContentValues[valuesList.size()]; 746 valuesList.toArray(valuesArray); 747 try { 748 size = resolver.bulkInsert(BATTERY_CONTENT_URI, valuesArray); 749 Log.d( 750 TAG, 751 "insert() battery states data into database with isFullChargeStart:" 752 + isFullChargeStart); 753 } catch (Exception e) { 754 Log.e(TAG, "bulkInsert() data into database error:", e); 755 } 756 } else { 757 // Inserts one fake data into battery provider. 758 final ContentValues contentValues = 759 ConvertUtils.convertBatteryEntryToContentValues( 760 /* entry= */ null, 761 /* batteryUsageStats= */ null, 762 batteryLevel, 763 batteryStatus, 764 batteryHealth, 765 snapshotBootTimestamp, 766 snapshotTimestamp, 767 isFullChargeStart); 768 try { 769 resolver.insert(BATTERY_CONTENT_URI, contentValues); 770 Log.d( 771 TAG, 772 "insert() data into database with isFullChargeStart:" + isFullChargeStart); 773 774 } catch (Exception e) { 775 Log.e(TAG, "insert() data into database error:", e); 776 } 777 valuesList.add(contentValues); 778 } 779 resolver.notifyChange(BATTERY_CONTENT_URI, /* observer= */ null); 780 BatteryUsageLogUtils.writeLog( 781 context, Action.INSERT_USAGE_DATA, "size=" + size + " " + errorMessage); 782 Log.d( 783 TAG, 784 String.format( 785 "sendBatteryEntryData() size=%d in %d/ms", 786 size, (System.currentTimeMillis() - startTime))); 787 if (isFullChargeStart) { 788 recordDateTime(context, KEY_LAST_UPLOAD_FULL_CHARGE_TIME); 789 } 790 return valuesList; 791 } 792 793 /** Dump all required data into {@link PrintWriter}. */ dump(Context context, PrintWriter writer)794 public static void dump(Context context, PrintWriter writer) { 795 writeString(context, writer, "BatteryLevelChanged", Intent.ACTION_BATTERY_LEVEL_CHANGED); 796 writeString( 797 context, 798 writer, 799 "BatteryPlugging", 800 BatteryUsageBroadcastReceiver.ACTION_BATTERY_PLUGGING); 801 writeString( 802 context, 803 writer, 804 "BatteryUnplugging", 805 BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING); 806 writeString( 807 context, 808 writer, 809 "ClearBatteryCacheData", 810 BatteryUsageBroadcastReceiver.ACTION_CLEAR_BATTERY_CACHE_DATA); 811 writeString(context, writer, "LastLoadFullChargeTime", KEY_LAST_LOAD_FULL_CHARGE_TIME); 812 writeString(context, writer, "LastUploadFullChargeTime", KEY_LAST_UPLOAD_FULL_CHARGE_TIME); 813 writeStringSet( 814 context, writer, "DismissedPowerAnomalyKeys", KEY_DISMISSED_POWER_ANOMALY_KEYS); 815 } 816 getSharedPreferences(Context context)817 static SharedPreferences getSharedPreferences(Context context) { 818 return context.getApplicationContext() 819 .getSharedPreferences(SHARED_PREFS_FILE, Context.MODE_PRIVATE); 820 } 821 removeUsageSource(Context context)822 static void removeUsageSource(Context context) { 823 final SharedPreferences sharedPreferences = getSharedPreferences(context); 824 if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) { 825 sharedPreferences.edit().remove(KEY_LAST_USAGE_SOURCE).apply(); 826 } 827 } 828 829 /** 830 * Returns what App Usage Observers will consider the source of usage for an activity. 831 * 832 * @see UsageStatsManager#getUsageSource() 833 */ getUsageSource(Context context, IUsageStatsManager usageStatsManager)834 static int getUsageSource(Context context, IUsageStatsManager usageStatsManager) { 835 final SharedPreferences sharedPreferences = getSharedPreferences(context); 836 if (sharedPreferences != null && sharedPreferences.contains(KEY_LAST_USAGE_SOURCE)) { 837 return sharedPreferences.getInt( 838 KEY_LAST_USAGE_SOURCE, ConvertUtils.DEFAULT_USAGE_SOURCE); 839 } 840 int usageSource = ConvertUtils.DEFAULT_USAGE_SOURCE; 841 842 try { 843 usageSource = usageStatsManager.getUsageSource(); 844 } catch (RemoteException e) { 845 Log.e(TAG, "Failed to getUsageSource", e); 846 } 847 if (sharedPreferences != null) { 848 sharedPreferences.edit().putInt(KEY_LAST_USAGE_SOURCE, usageSource).apply(); 849 } 850 return usageSource; 851 } 852 removeDismissedPowerAnomalyKeys(Context context)853 static void removeDismissedPowerAnomalyKeys(Context context) { 854 final SharedPreferences sharedPreferences = getSharedPreferences(context); 855 if (sharedPreferences != null 856 && sharedPreferences.contains(KEY_DISMISSED_POWER_ANOMALY_KEYS)) { 857 sharedPreferences.edit().remove(KEY_DISMISSED_POWER_ANOMALY_KEYS).apply(); 858 } 859 } 860 getDismissedPowerAnomalyKeys(Context context)861 static Set<String> getDismissedPowerAnomalyKeys(Context context) { 862 final SharedPreferences sharedPreferences = getSharedPreferences(context); 863 return sharedPreferences != null 864 ? sharedPreferences.getStringSet(KEY_DISMISSED_POWER_ANOMALY_KEYS, new ArraySet<>()) 865 : new ArraySet<>(); 866 } 867 setDismissedPowerAnomalyKeys(Context context, String dismissedPowerAnomalyKey)868 static void setDismissedPowerAnomalyKeys(Context context, String dismissedPowerAnomalyKey) { 869 final SharedPreferences sharedPreferences = getSharedPreferences(context); 870 if (sharedPreferences != null) { 871 final Set<String> dismissedPowerAnomalyKeys = getDismissedPowerAnomalyKeys(context); 872 dismissedPowerAnomalyKeys.add(dismissedPowerAnomalyKey); 873 sharedPreferences 874 .edit() 875 .putStringSet(KEY_DISMISSED_POWER_ANOMALY_KEYS, dismissedPowerAnomalyKeys) 876 .apply(); 877 } 878 } 879 recordDateTime(Context context, String preferenceKey)880 static void recordDateTime(Context context, String preferenceKey) { 881 final SharedPreferences sharedPreferences = getSharedPreferences(context); 882 if (sharedPreferences != null) { 883 final String currentTime = utcToLocalTimeForLogging(System.currentTimeMillis()); 884 sharedPreferences.edit().putString(preferenceKey, currentTime).apply(); 885 } 886 } 887 888 @VisibleForTesting loadFromContentProvider( Context context, Uri uri, T defaultValue, Function<Cursor, T> cursorReader)889 static <T> T loadFromContentProvider( 890 Context context, Uri uri, T defaultValue, Function<Cursor, T> cursorReader) { 891 // Transfer work profile to user profile. Please see b/297036263. 892 context = getParentContext(context); 893 if (context == null) { 894 return defaultValue; 895 } 896 try (Cursor cursor = 897 sFakeSupplier != null 898 ? sFakeSupplier.get() 899 : context.getContentResolver().query(uri, null, null, null)) { 900 return (cursor == null || cursor.getCount() == 0) 901 ? defaultValue 902 : cursorReader.apply(cursor); 903 } 904 } 905 clearDataAfterTimeChangedIfNeededInternal(Context context)906 private static void clearDataAfterTimeChangedIfNeededInternal(Context context) { 907 final long currentTime = System.currentTimeMillis(); 908 final String logInfo = 909 String.format(Locale.ENGLISH, "clear data after current time = %d", currentTime); 910 Log.d(TAG, logInfo); 911 BatteryUsageLogUtils.writeLog(context, Action.TIME_UPDATED, logInfo); 912 DatabaseUtils.clearAllAfter(context, currentTime); 913 PeriodicJobManager.getInstance(context).refreshJob(/* fromBoot= */ false); 914 915 final List<BatteryEvent> batteryLevelRecordEvents = 916 DatabaseUtils.getBatteryEvents( 917 context, 918 Calendar.getInstance(), 919 getLastFullChargeTime(context), 920 BATTERY_LEVEL_RECORD_EVENTS); 921 if (batteryLevelRecordEvents.isEmpty()) { 922 // Take a snapshot of battery usage data immediately if there's no battery events. 923 BatteryUsageDataLoader.enqueueWork(context, /* isFullChargeStart= */ true); 924 } 925 } 926 clearDataAfterTimeZoneChangedIfNeededInternal(Context context)927 private static void clearDataAfterTimeZoneChangedIfNeededInternal(Context context) { 928 final String logInfo = 929 String.format( 930 Locale.ENGLISH, 931 "clear database cache for new time zone = %s", 932 TimeZone.getDefault().toString()); 933 BatteryUsageLogUtils.writeLog(context, Action.TIMEZONE_UPDATED, logInfo); 934 Log.d(TAG, logInfo); 935 DatabaseUtils.clearEvenHourCacheData(context); 936 PeriodicJobManager.getInstance(context).refreshJob(/* fromBoot= */ false); 937 } 938 loadLongFromContentProvider( Context context, Uri uri, final long defaultValue)939 private static long loadLongFromContentProvider( 940 Context context, Uri uri, final long defaultValue) { 941 return loadFromContentProvider( 942 context, 943 uri, 944 defaultValue, 945 cursor -> 946 cursor.moveToFirst() ? cursor.getLong(/* columnIndex= */ 0) : defaultValue); 947 } 948 loadListFromContentProvider( Context context, Uri uri, Function<Cursor, E> converter)949 private static <E> List<E> loadListFromContentProvider( 950 Context context, Uri uri, Function<Cursor, E> converter) { 951 return loadFromContentProvider( 952 context, 953 uri, 954 new ArrayList<>(), 955 cursor -> { 956 final List<E> list = new ArrayList<>(); 957 while (cursor.moveToNext()) { 958 list.add(converter.apply(cursor)); 959 } 960 return list; 961 }); 962 } 963 writeString( Context context, PrintWriter writer, String prefix, String key)964 private static void writeString( 965 Context context, PrintWriter writer, String prefix, String key) { 966 final SharedPreferences sharedPreferences = getSharedPreferences(context); 967 if (sharedPreferences == null) { 968 return; 969 } 970 final String content = sharedPreferences.getString(key, ""); 971 writer.println(String.format("\t\t%s: %s", prefix, content)); 972 } 973 writeStringSet( Context context, PrintWriter writer, String prefix, String key)974 private static void writeStringSet( 975 Context context, PrintWriter writer, String prefix, String key) { 976 final SharedPreferences sharedPreferences = getSharedPreferences(context); 977 if (sharedPreferences == null) { 978 return; 979 } 980 final Set<String> results = sharedPreferences.getStringSet(key, new ArraySet<>()); 981 if (results != null) { 982 writer.println(String.format("\t\t%s: %s", prefix, results.toString())); 983 } 984 } 985 } 986