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