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 android.app.usage.UsageEvents; 20 import android.content.Context; 21 import android.util.ArrayMap; 22 import android.util.Log; 23 24 import androidx.annotation.NonNull; 25 import androidx.annotation.Nullable; 26 import androidx.lifecycle.Lifecycle; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 import com.android.settings.fuelgauge.PowerUsageFeatureProvider; 30 import com.android.settings.overlay.FeatureFactory; 31 32 import java.util.ArrayList; 33 import java.util.Calendar; 34 import java.util.Collections; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 39 /** 40 * Manages the async tasks to process battery and app usage data. 41 * 42 * <p>For now, there exist 4 async tasks in this manager: 43 * 44 * <ul> 45 * <li>loadCurrentBatteryHistoryMap: load the latest battery history data from battery stats 46 * service. 47 * <li>loadCurrentAppUsageList: load the latest app usage data (last timestamp in database - now) 48 * from usage stats service. 49 * <li>loadDatabaseAppUsageList: load the necessary app usage data (after last full charge) from 50 * database 51 * <li>loadAndApplyBatteryMapFromServiceOnly: load all the battery history data (should be after 52 * last full charge) from battery stats service and apply the callback function directly 53 * </ul> 54 * 55 * If there is battery level data, the first 3 async tasks will be started at the same time. 56 * 57 * <ul> 58 * <li>After loadCurrentAppUsageList and loadDatabaseAppUsageList complete, which means all app 59 * usage data has been loaded, the intermediate usage result will be generated. 60 * <li>Then after all 3 async tasks complete, the battery history data and app usage data will be 61 * combined to generate final data used for UI rendering. And the callback function will be 62 * applied. 63 * <li>If current user is locked, which means we couldn't get the latest app usage data, screen-on 64 * time will not be shown in the UI and empty screen-on time data will be returned. 65 * </ul> 66 * 67 * If there is no battery level data, the 4th async task will be started only and the usage map 68 * callback function will be applied directly to show the app list on the UI. 69 */ 70 public class DataProcessManager { 71 private static final String TAG = "DataProcessManager"; 72 private static final List<BatteryEventType> POWER_CONNECTION_EVENTS = 73 List.of(BatteryEventType.POWER_CONNECTED, BatteryEventType.POWER_DISCONNECTED); 74 75 // For testing only. 76 @VisibleForTesting static Map<Long, Map<String, BatteryHistEntry>> sFakeBatteryHistoryMap; 77 78 // Raw start timestamp with round to the nearest hour. 79 private final long mRawStartTimestamp; 80 private final long mLastFullChargeTimestamp; 81 private final boolean mIsFromPeriodJob; 82 private final Context mContext; 83 private final @Nullable Lifecycle mLifecycle; 84 private final UserIdsSeries mUserIdsSeries; 85 private final OnBatteryDiffDataMapLoadedListener mCallbackFunction; 86 private final List<AppUsageEvent> mAppUsageEventList = new ArrayList<>(); 87 private final List<BatteryEvent> mBatteryEventList = new ArrayList<>(); 88 private final List<BatteryUsageSlot> mBatteryUsageSlotList = new ArrayList<>(); 89 private final List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay; 90 private final Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap; 91 92 private boolean mIsCurrentBatteryHistoryLoaded = false; 93 private boolean mIsCurrentAppUsageLoaded = false; 94 private boolean mIsDatabaseAppUsageLoaded = false; 95 private boolean mIsBatteryEventLoaded = false; 96 private boolean mIsBatteryUsageSlotLoaded = false; 97 // Used to identify whether screen-on time data should be shown in the UI. 98 private boolean mShowScreenOnTime = true; 99 private Set<String> mSystemAppsPackageNames = null; 100 private Set<Integer> mSystemAppsUids = null; 101 102 /** 103 * The indexed {@link AppUsagePeriod} list data for each corresponding time slot. 104 * 105 * <p>{@code Long} stands for the userId. 106 * 107 * <p>{@code String} stands for the packageName. 108 */ 109 private Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> 110 mAppUsagePeriodMap; 111 112 /** 113 * A callback listener when all the data is processed. This happens when all the async tasks 114 * complete and generate the final callback. 115 */ 116 public interface OnBatteryDiffDataMapLoadedListener { 117 /** The callback function when all the data is processed. */ onBatteryDiffDataMapLoaded(Map<Long, BatteryDiffData> batteryDiffDataMap)118 void onBatteryDiffDataMapLoaded(Map<Long, BatteryDiffData> batteryDiffDataMap); 119 } 120 121 /** Constructor when there exists battery level data. */ DataProcessManager( Context context, @Nullable Lifecycle lifecycle, final UserIdsSeries userIdsSeries, final boolean isFromPeriodJob, final long rawStartTimestamp, final long lastFullChargeTimestamp, @NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction, @NonNull final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay, @NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap)122 DataProcessManager( 123 Context context, 124 @Nullable Lifecycle lifecycle, 125 final UserIdsSeries userIdsSeries, 126 final boolean isFromPeriodJob, 127 final long rawStartTimestamp, 128 final long lastFullChargeTimestamp, 129 @NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction, 130 @NonNull final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay, 131 @NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) { 132 mContext = context.getApplicationContext(); 133 mLifecycle = lifecycle; 134 mUserIdsSeries = userIdsSeries; 135 mIsFromPeriodJob = isFromPeriodJob; 136 mRawStartTimestamp = rawStartTimestamp; 137 mLastFullChargeTimestamp = lastFullChargeTimestamp; 138 mCallbackFunction = callbackFunction; 139 mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay; 140 mBatteryHistoryMap = batteryHistoryMap; 141 } 142 143 /** Constructor when there is no battery level data. */ DataProcessManager( Context context, @Nullable Lifecycle lifecycle, final UserIdsSeries userIdsSeries, @NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction)144 DataProcessManager( 145 Context context, 146 @Nullable Lifecycle lifecycle, 147 final UserIdsSeries userIdsSeries, 148 @NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction) { 149 mContext = context.getApplicationContext(); 150 mLifecycle = lifecycle; 151 mUserIdsSeries = userIdsSeries; 152 mCallbackFunction = callbackFunction; 153 mIsFromPeriodJob = false; 154 mRawStartTimestamp = 0L; 155 mLastFullChargeTimestamp = 0L; 156 mHourlyBatteryLevelsPerDay = null; 157 mBatteryHistoryMap = null; 158 // When there is no battery level data, don't show screen-on time and battery level chart on 159 // the UI. 160 mShowScreenOnTime = false; 161 } 162 163 /** Starts the async tasks to load battery history data and app usage data. */ start()164 public void start() { 165 // If we have battery level data, load the battery history map and app usage simultaneously. 166 if (mHourlyBatteryLevelsPerDay != null) { 167 if (mIsFromPeriodJob) { 168 mIsCurrentBatteryHistoryLoaded = true; 169 mIsCurrentAppUsageLoaded = true; 170 mIsBatteryUsageSlotLoaded = true; 171 } else { 172 // Loads the latest battery history data from the service. 173 loadCurrentBatteryHistoryMap(); 174 // Loads the latest app usage list from the service. 175 loadCurrentAppUsageList(); 176 // Loads existing battery usage slots from database. 177 if (mUserIdsSeries.isMainUserProfileOnly()) { 178 loadBatteryUsageSlotList(); 179 } else { 180 mIsBatteryUsageSlotLoaded = true; 181 } 182 } 183 // Loads app usage list from database. 184 loadDatabaseAppUsageList(); 185 // Loads the battery event list from database. 186 loadPowerConnectionBatteryEventList(); 187 } else { 188 // If there is no battery level data, only load the battery history data from service 189 // and show it as the app list directly. 190 loadAndApplyBatteryMapFromServiceOnly(); 191 } 192 } 193 194 @VisibleForTesting getAppUsageEventList()195 List<AppUsageEvent> getAppUsageEventList() { 196 return mAppUsageEventList; 197 } 198 199 @VisibleForTesting 200 Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> getAppUsagePeriodMap()201 getAppUsagePeriodMap() { 202 return mAppUsagePeriodMap; 203 } 204 205 @VisibleForTesting getIsCurrentAppUsageLoaded()206 boolean getIsCurrentAppUsageLoaded() { 207 return mIsCurrentAppUsageLoaded; 208 } 209 210 @VisibleForTesting getIsDatabaseAppUsageLoaded()211 boolean getIsDatabaseAppUsageLoaded() { 212 return mIsDatabaseAppUsageLoaded; 213 } 214 215 @VisibleForTesting getIsBatteryEventLoaded()216 boolean getIsBatteryEventLoaded() { 217 return mIsBatteryEventLoaded; 218 } 219 220 @VisibleForTesting getIsCurrentBatteryHistoryLoaded()221 boolean getIsCurrentBatteryHistoryLoaded() { 222 return mIsCurrentBatteryHistoryLoaded; 223 } 224 225 @VisibleForTesting getShowScreenOnTime()226 boolean getShowScreenOnTime() { 227 return mShowScreenOnTime; 228 } 229 loadCurrentBatteryHistoryMap()230 private void loadCurrentBatteryHistoryMap() { 231 new LifecycleAwareAsyncTask<Map<String, BatteryHistEntry>>(mLifecycle) { 232 @Override 233 protected Map<String, BatteryHistEntry> doInBackground(Void... voids) { 234 final long startTime = System.currentTimeMillis(); 235 // Loads the current battery usage data from the battery stats service. 236 final Map<String, BatteryHistEntry> currentBatteryHistoryMap = 237 DataProcessor.getCurrentBatteryHistoryMapFromStatsService(mContext); 238 Log.d( 239 TAG, 240 String.format( 241 "execute loadCurrentBatteryHistoryMap size=%d in %d/ms", 242 currentBatteryHistoryMap.size(), 243 (System.currentTimeMillis() - startTime))); 244 return currentBatteryHistoryMap; 245 } 246 247 @Override 248 protected void onPostExecute( 249 final Map<String, BatteryHistEntry> currentBatteryHistoryMap) { 250 super.onPostExecute(currentBatteryHistoryMap); 251 if (mBatteryHistoryMap != null) { 252 // Replaces the placeholder in mBatteryHistoryMap. 253 for (Map.Entry<Long, Map<String, BatteryHistEntry>> mapEntry : 254 mBatteryHistoryMap.entrySet()) { 255 if (mapEntry.getValue() 256 .containsKey( 257 DataProcessor.CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) { 258 mapEntry.setValue(currentBatteryHistoryMap); 259 } 260 } 261 } 262 mIsCurrentBatteryHistoryLoaded = true; 263 tryToGenerateFinalDataAndApplyCallback(); 264 } 265 }.start(); 266 } 267 loadCurrentAppUsageList()268 private void loadCurrentAppUsageList() { 269 new LifecycleAwareAsyncTask<List<AppUsageEvent>>(mLifecycle) { 270 @Override 271 @Nullable 272 protected List<AppUsageEvent> doInBackground(Void... voids) { 273 if (!shouldLoadAppUsageData()) { 274 Log.d(TAG, "not loadCurrentAppUsageList"); 275 return null; 276 } 277 final long startTime = System.currentTimeMillis(); 278 // Loads the current battery usage data from the battery stats service. 279 final Map<Long, UsageEvents> usageEventsMap = new ArrayMap<>(); 280 for (int userId : mUserIdsSeries.getVisibleUserIds()) { 281 final UsageEvents usageEventsForCurrentUser = 282 DataProcessor.getCurrentAppUsageEventsForUser( 283 mContext, mUserIdsSeries, userId, mRawStartTimestamp); 284 if (usageEventsForCurrentUser == null) { 285 // If fail to load usage events for any user, return null directly and 286 // screen-on time will not be shown in the UI. 287 if (userId == mUserIdsSeries.getCurrentUserId()) { 288 return null; 289 } 290 } else { 291 usageEventsMap.put(Long.valueOf(userId), usageEventsForCurrentUser); 292 } 293 } 294 final List<AppUsageEvent> appUsageEventList = 295 DataProcessor.generateAppUsageEventListFromUsageEvents( 296 mContext, usageEventsMap); 297 Log.d( 298 TAG, 299 String.format( 300 "execute loadCurrentAppUsageList size=%d in %d/ms", 301 appUsageEventList.size(), 302 (System.currentTimeMillis() - startTime))); 303 return appUsageEventList; 304 } 305 306 @Override 307 protected void onPostExecute(final List<AppUsageEvent> currentAppUsageList) { 308 super.onPostExecute(currentAppUsageList); 309 if (currentAppUsageList == null || currentAppUsageList.isEmpty()) { 310 Log.d(TAG, "currentAppUsageList is null or empty"); 311 } else { 312 mAppUsageEventList.addAll(currentAppUsageList); 313 } 314 mIsCurrentAppUsageLoaded = true; 315 tryToProcessAppUsageData(); 316 } 317 }.start(); 318 } 319 loadDatabaseAppUsageList()320 private void loadDatabaseAppUsageList() { 321 new LifecycleAwareAsyncTask<List<AppUsageEvent>>(mLifecycle) { 322 @Override 323 protected List<AppUsageEvent> doInBackground(Void... voids) { 324 if (!shouldLoadAppUsageData()) { 325 Log.d(TAG, "not loadDatabaseAppUsageList"); 326 return null; 327 } 328 final long startTime = System.currentTimeMillis(); 329 // Loads the app usage data from the database. 330 final List<AppUsageEvent> appUsageEventList = 331 DatabaseUtils.getAppUsageEventForUsers( 332 mContext, 333 Calendar.getInstance(), 334 mUserIdsSeries.getVisibleUserIds(), 335 mRawStartTimestamp); 336 Log.d( 337 TAG, 338 String.format( 339 "execute loadDatabaseAppUsageList size=%d in %d/ms", 340 appUsageEventList.size(), 341 (System.currentTimeMillis() - startTime))); 342 return appUsageEventList; 343 } 344 345 @Override 346 protected void onPostExecute(final List<AppUsageEvent> databaseAppUsageList) { 347 super.onPostExecute(databaseAppUsageList); 348 if (databaseAppUsageList == null || databaseAppUsageList.isEmpty()) { 349 Log.d(TAG, "databaseAppUsageList is null or empty"); 350 } else { 351 mAppUsageEventList.addAll(databaseAppUsageList); 352 } 353 mIsDatabaseAppUsageLoaded = true; 354 tryToProcessAppUsageData(); 355 } 356 }.start(); 357 } 358 loadPowerConnectionBatteryEventList()359 private void loadPowerConnectionBatteryEventList() { 360 new LifecycleAwareAsyncTask<List<BatteryEvent>>(mLifecycle) { 361 @Override 362 protected List<BatteryEvent> doInBackground(Void... voids) { 363 final long startTime = System.currentTimeMillis(); 364 // Loads the battery event data from the database. 365 final List<BatteryEvent> batteryEventList = 366 DatabaseUtils.getBatteryEvents( 367 mContext, 368 Calendar.getInstance(), 369 mRawStartTimestamp, 370 POWER_CONNECTION_EVENTS); 371 Log.d( 372 TAG, 373 String.format( 374 "execute loadPowerConnectionBatteryEventList size=%d in %d/ms", 375 batteryEventList.size(), (System.currentTimeMillis() - startTime))); 376 return batteryEventList; 377 } 378 379 @Override 380 protected void onPostExecute(final List<BatteryEvent> batteryEventList) { 381 super.onPostExecute(batteryEventList); 382 if (batteryEventList == null || batteryEventList.isEmpty()) { 383 Log.d(TAG, "batteryEventList is null or empty"); 384 } else { 385 mBatteryEventList.clear(); 386 mBatteryEventList.addAll(batteryEventList); 387 } 388 mIsBatteryEventLoaded = true; 389 tryToProcessAppUsageData(); 390 } 391 }.start(); 392 } 393 loadBatteryUsageSlotList()394 private void loadBatteryUsageSlotList() { 395 new LifecycleAwareAsyncTask<List<BatteryUsageSlot>>(mLifecycle) { 396 @Override 397 protected List<BatteryUsageSlot> doInBackground(Void... voids) { 398 final long startTime = System.currentTimeMillis(); 399 // Loads the battery usage slot data from the database. 400 final List<BatteryUsageSlot> batteryUsageSlotList = 401 DatabaseUtils.getBatteryUsageSlots( 402 mContext, Calendar.getInstance(), mLastFullChargeTimestamp); 403 Log.d( 404 TAG, 405 String.format( 406 "execute loadBatteryUsageSlotList size=%d in %d/ms", 407 batteryUsageSlotList.size(), 408 (System.currentTimeMillis() - startTime))); 409 return batteryUsageSlotList; 410 } 411 412 @Override 413 protected void onPostExecute(final List<BatteryUsageSlot> batteryUsageSlotList) { 414 super.onPostExecute(batteryUsageSlotList); 415 if (batteryUsageSlotList == null || batteryUsageSlotList.isEmpty()) { 416 Log.d(TAG, "batteryUsageSlotList is null or empty"); 417 } else { 418 mBatteryUsageSlotList.clear(); 419 mBatteryUsageSlotList.addAll(batteryUsageSlotList); 420 } 421 mIsBatteryUsageSlotLoaded = true; 422 tryToGenerateFinalDataAndApplyCallback(); 423 } 424 }.start(); 425 } 426 loadAndApplyBatteryMapFromServiceOnly()427 private void loadAndApplyBatteryMapFromServiceOnly() { 428 new LifecycleAwareAsyncTask<Map<Long, BatteryDiffData>>(mLifecycle) { 429 @Override 430 protected Map<Long, BatteryDiffData> doInBackground(Void... voids) { 431 final long startTime = System.currentTimeMillis(); 432 final Map<Long, BatteryDiffData> batteryDiffDataMap = 433 DataProcessor.getBatteryDiffDataMapFromStatsService( 434 mContext, 435 mUserIdsSeries, 436 mRawStartTimestamp, 437 getSystemAppsPackageNames(), 438 getSystemAppsUids()); 439 Log.d( 440 TAG, 441 String.format( 442 "execute loadAndApplyBatteryMapFromServiceOnly size=%d in %d/ms", 443 batteryDiffDataMap.size(), 444 (System.currentTimeMillis() - startTime))); 445 return batteryDiffDataMap; 446 } 447 448 @Override 449 protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) { 450 super.onPostExecute(batteryDiffDataMap); 451 if (mCallbackFunction != null) { 452 mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap); 453 } 454 } 455 }.start(); 456 } 457 tryToProcessAppUsageData()458 private void tryToProcessAppUsageData() { 459 // Ignore processing the data if any required data is not loaded. 460 if (!mIsCurrentAppUsageLoaded || !mIsDatabaseAppUsageLoaded || !mIsBatteryEventLoaded) { 461 return; 462 } 463 processAppUsageData(); 464 tryToGenerateFinalDataAndApplyCallback(); 465 } 466 processAppUsageData()467 private void processAppUsageData() { 468 // If there is no screen-on time data, no need to process. 469 if (!mShowScreenOnTime) { 470 return; 471 } 472 // Generates the indexed AppUsagePeriod list data for each corresponding time slot for 473 // further use. 474 mAppUsagePeriodMap = 475 DataProcessor.generateAppUsagePeriodMap( 476 mContext, 477 mHourlyBatteryLevelsPerDay, 478 mAppUsageEventList, 479 mBatteryEventList); 480 } 481 tryToGenerateFinalDataAndApplyCallback()482 private void tryToGenerateFinalDataAndApplyCallback() { 483 // Ignore processing the data if any required data is not loaded. 484 if (!mIsCurrentBatteryHistoryLoaded 485 || !mIsCurrentAppUsageLoaded 486 || !mIsDatabaseAppUsageLoaded 487 || !mIsBatteryEventLoaded 488 || !mIsBatteryUsageSlotLoaded) { 489 return; 490 } 491 generateFinalDataAndApplyCallback(); 492 } 493 generateFinalDataAndApplyCallback()494 private synchronized void generateFinalDataAndApplyCallback() { 495 new LifecycleAwareAsyncTask<Map<Long, BatteryDiffData>>(mLifecycle) { 496 @Override 497 protected Map<Long, BatteryDiffData> doInBackground(Void... voids) { 498 final long startTime = System.currentTimeMillis(); 499 final Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>(); 500 for (BatteryUsageSlot batteryUsageSlot : mBatteryUsageSlotList) { 501 batteryDiffDataMap.put( 502 batteryUsageSlot.getStartTimestamp(), 503 ConvertUtils.convertToBatteryDiffData( 504 mContext, 505 batteryUsageSlot, 506 getSystemAppsPackageNames(), 507 getSystemAppsUids())); 508 } 509 batteryDiffDataMap.putAll( 510 DataProcessor.getBatteryDiffDataMap( 511 mContext, 512 mUserIdsSeries, 513 mHourlyBatteryLevelsPerDay, 514 mBatteryHistoryMap, 515 mAppUsagePeriodMap, 516 getSystemAppsPackageNames(), 517 getSystemAppsUids())); 518 // Process the reattributate data for the following two cases: 519 // 1) the latest slot for the timestamp "until now" 520 // 2) walkthrough all BatteryDiffData again to handle "re-compute" case 521 final PowerUsageFeatureProvider featureProvider = 522 FeatureFactory.getFeatureFactory() 523 .getPowerUsageFeatureProvider(); 524 featureProvider.processBatteryReattributeData( 525 mContext, batteryDiffDataMap, mBatteryEventList, mIsFromPeriodJob); 526 527 Log.d( 528 TAG, 529 String.format( 530 "execute generateFinalDataAndApplyCallback size=%d in %d/ms", 531 batteryDiffDataMap.size(), System.currentTimeMillis() - startTime)); 532 return batteryDiffDataMap; 533 } 534 535 @Override 536 protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) { 537 super.onPostExecute(batteryDiffDataMap); 538 if (mCallbackFunction != null) { 539 mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap); 540 } 541 } 542 }.start(); 543 } 544 545 // Whether we should load app usage data from service or database. shouldLoadAppUsageData()546 private synchronized boolean shouldLoadAppUsageData() { 547 if (!mShowScreenOnTime) { 548 return false; 549 } 550 // If current user is locked, no need to load app usage data from service or database. 551 if (mUserIdsSeries.isCurrentUserLocked()) { 552 Log.d(TAG, "shouldLoadAppUsageData: false, current user is locked"); 553 mShowScreenOnTime = false; 554 return false; 555 } 556 return true; 557 } 558 getSystemAppsPackageNames()559 private synchronized Set<String> getSystemAppsPackageNames() { 560 if (mSystemAppsPackageNames == null) { 561 mSystemAppsPackageNames = DataProcessor.getSystemAppsPackageNames(mContext); 562 } 563 return mSystemAppsPackageNames; 564 } 565 getSystemAppsUids()566 private synchronized Set<Integer> getSystemAppsUids() { 567 if (mSystemAppsUids == null) { 568 mSystemAppsUids = DataProcessor.getSystemAppsUids(mContext); 569 } 570 return mSystemAppsUids; 571 } 572 573 /** 574 * @return Returns battery level data and start async task to compute battery diff usage data 575 * and load app labels + icons. Returns null if the input is invalid or not having at least 576 * 2 hours data. 577 */ 578 @Nullable getBatteryLevelData( Context context, @Nullable Lifecycle lifecycle, final UserIdsSeries userIdsSeries, final boolean isFromPeriodJob, final OnBatteryDiffDataMapLoadedListener onBatteryUsageMapLoadedListener)579 public static BatteryLevelData getBatteryLevelData( 580 Context context, 581 @Nullable Lifecycle lifecycle, 582 final UserIdsSeries userIdsSeries, 583 final boolean isFromPeriodJob, 584 final OnBatteryDiffDataMapLoadedListener onBatteryUsageMapLoadedListener) { 585 final long start = System.currentTimeMillis(); 586 final long lastFullChargeTime = DatabaseUtils.getLastFullChargeTime(context); 587 final List<BatteryEvent> batteryLevelRecordEvents = 588 DatabaseUtils.getBatteryEvents( 589 context, 590 Calendar.getInstance(), 591 lastFullChargeTime, 592 DatabaseUtils.BATTERY_LEVEL_RECORD_EVENTS); 593 final long startTimestamp = 594 (batteryLevelRecordEvents.isEmpty() 595 || (!isFromPeriodJob && !userIdsSeries.isMainUserProfileOnly())) 596 ? lastFullChargeTime 597 : batteryLevelRecordEvents.get(0).getTimestamp(); 598 final BatteryLevelData batteryLevelData = 599 getPeriodBatteryLevelData( 600 context, 601 lifecycle, 602 userIdsSeries, 603 startTimestamp, 604 lastFullChargeTime, 605 isFromPeriodJob, 606 onBatteryUsageMapLoadedListener); 607 Log.d( 608 TAG, 609 String.format( 610 "execute getBatteryLevelData in %d/ms," 611 + " batteryLevelRecordEvents.size=%d", 612 (System.currentTimeMillis() - start), batteryLevelRecordEvents.size())); 613 614 return isFromPeriodJob 615 ? batteryLevelData 616 : BatteryLevelData.combine(batteryLevelData, batteryLevelRecordEvents); 617 } 618 getPeriodBatteryLevelData( Context context, @Nullable Lifecycle lifecycle, final UserIdsSeries userIdsSeries, final long startTimestamp, final long lastFullChargeTime, final boolean isFromPeriodJob, final OnBatteryDiffDataMapLoadedListener onBatteryDiffDataMapLoadedListener)619 private static BatteryLevelData getPeriodBatteryLevelData( 620 Context context, 621 @Nullable Lifecycle lifecycle, 622 final UserIdsSeries userIdsSeries, 623 final long startTimestamp, 624 final long lastFullChargeTime, 625 final boolean isFromPeriodJob, 626 final OnBatteryDiffDataMapLoadedListener onBatteryDiffDataMapLoadedListener) { 627 final long currentTime = System.currentTimeMillis(); 628 Log.d( 629 TAG, 630 String.format( 631 "getPeriodBatteryLevelData() startTimestamp=%s", 632 ConvertUtils.utcToLocalTimeForLogging(startTimestamp))); 633 if (isFromPeriodJob 634 && startTimestamp >= TimestampUtils.getLastEvenHourTimestamp(currentTime)) { 635 // Nothing needs to be loaded for period job. 636 return null; 637 } 638 639 final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = 640 sFakeBatteryHistoryMap != null 641 ? sFakeBatteryHistoryMap 642 : DatabaseUtils.getHistoryMapSinceLatestRecordBeforeQueryTimestamp( 643 context, 644 Calendar.getInstance(), 645 startTimestamp, 646 lastFullChargeTime); 647 if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { 648 Log.d(TAG, "batteryHistoryMap is null in getPeriodBatteryLevelData()"); 649 new DataProcessManager(context, lifecycle, userIdsSeries, 650 onBatteryDiffDataMapLoadedListener) 651 .start(); 652 return null; 653 } 654 655 // Process raw history map data into hourly timestamps. 656 final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap = 657 DataProcessor.getHistoryMapWithExpectedTimestamps(context, batteryHistoryMap); 658 if (isFromPeriodJob && !processedBatteryHistoryMap.isEmpty()) { 659 // For periodic job, only generate battery usage data between even-hour timestamps. 660 // Remove the timestamps: 661 // 1) earlier than the latest completed period job (startTimestamp) 662 // 2) later than current scheduled even-hour job (lastEvenHourTimestamp). 663 final long lastEvenHourTimestamp = TimestampUtils.getLastEvenHourTimestamp(currentTime); 664 final Set<Long> batteryHistMapKeySet = processedBatteryHistoryMap.keySet(); 665 final long minTimestamp = Collections.min(batteryHistMapKeySet); 666 final long maxTimestamp = Collections.max(batteryHistMapKeySet); 667 if (minTimestamp < startTimestamp) { 668 processedBatteryHistoryMap.remove(minTimestamp); 669 } 670 if (maxTimestamp > lastEvenHourTimestamp) { 671 processedBatteryHistoryMap.remove(maxTimestamp); 672 } 673 } 674 // Wrap and processed history map into easy-to-use format for UI rendering. 675 final BatteryLevelData batteryLevelData = 676 DataProcessor.getLevelDataThroughProcessedHistoryMap( 677 context, processedBatteryHistoryMap); 678 if (batteryLevelData == null) { 679 new DataProcessManager(context, lifecycle, userIdsSeries, 680 onBatteryDiffDataMapLoadedListener) 681 .start(); 682 Log.d(TAG, "getBatteryLevelData() returns null"); 683 return null; 684 } 685 686 // Start the async task to compute diff usage data and load labels and icons. 687 new DataProcessManager( 688 context, 689 lifecycle, 690 userIdsSeries, 691 isFromPeriodJob, 692 startTimestamp, 693 lastFullChargeTime, 694 onBatteryDiffDataMapLoadedListener, 695 batteryLevelData.getHourlyBatteryLevelsPerDay(), 696 processedBatteryHistoryMap) 697 .start(); 698 699 return batteryLevelData; 700 } 701 } 702