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