• 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.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