• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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