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