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