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