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 static com.android.settings.fuelgauge.batteryusage.ConvertUtils.getEffectivePackageName;
20 import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isSystemConsumer;
21 import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.isUidConsumer;
22 import static com.android.settingslib.fuelgauge.BatteryStatus.BATTERY_LEVEL_UNKNOWN;
23 
24 import android.app.usage.IUsageStatsManager;
25 import android.app.usage.UsageEvents;
26 import android.app.usage.UsageEvents.Event;
27 import android.content.ContentValues;
28 import android.content.Context;
29 import android.content.Intent;
30 import android.content.pm.PackageManager;
31 import android.os.BatteryConsumer;
32 import android.os.BatteryStatsManager;
33 import android.os.BatteryUsageStats;
34 import android.os.BatteryUsageStatsQuery;
35 import android.os.Process;
36 import android.os.RemoteException;
37 import android.os.ServiceManager;
38 import android.os.UidBatteryConsumer;
39 import android.os.UserBatteryConsumer;
40 import android.os.UserHandle;
41 import android.os.UserManager;
42 import android.text.TextUtils;
43 import android.text.format.DateUtils;
44 import android.util.ArrayMap;
45 import android.util.ArraySet;
46 import android.util.Log;
47 import android.util.SparseArray;
48 
49 import androidx.annotation.NonNull;
50 import androidx.annotation.Nullable;
51 
52 import com.android.internal.annotations.VisibleForTesting;
53 import com.android.internal.os.PowerProfile;
54 import com.android.settings.fuelgauge.BatteryUtils;
55 import com.android.settings.overlay.FeatureFactory;
56 import com.android.settingslib.fuelgauge.BatteryStatus;
57 import com.android.settingslib.spaprivileged.model.app.AppListRepositoryUtil;
58 
59 import com.google.common.base.Preconditions;
60 
61 import java.time.Duration;
62 import java.util.ArrayList;
63 import java.util.Calendar;
64 import java.util.Collection;
65 import java.util.Collections;
66 import java.util.Comparator;
67 import java.util.List;
68 import java.util.Map;
69 import java.util.Set;
70 import java.util.stream.Collectors;
71 import java.util.stream.Stream;
72 
73 /**
74  * A utility class to process data loaded from database and make the data easy to use for battery
75  * usage UI.
76  */
77 public final class DataProcessor {
78     private static final String TAG = "DataProcessor";
79     private static final int POWER_COMPONENT_SYSTEM_SERVICES = 7;
80     private static final int POWER_COMPONENT_WAKELOCK = 12;
81     private static final int MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP = 10;
82     private static final int MIN_DAILY_DATA_SIZE = 2;
83     private static final int MAX_DIFF_SECONDS_OF_UPPER_TIMESTAMP = 5;
84     private static final String MEDIASERVER_PACKAGE_NAME = "mediaserver";
85     private static final String ANDROID_CORE_APPS_SHARED_USER_ID = "android.uid.shared";
86     private static final Map<String, BatteryHistEntry> EMPTY_BATTERY_MAP = new ArrayMap<>();
87     private static final BatteryHistEntry EMPTY_BATTERY_HIST_ENTRY =
88             new BatteryHistEntry(new ContentValues());
89 
90     @VisibleForTesting
91     static final long DEFAULT_USAGE_DURATION_FOR_INCOMPLETE_INTERVAL =
92             DateUtils.SECOND_IN_MILLIS * 30;
93 
94     @VisibleForTesting
95     static final int SELECTED_INDEX_ALL = BatteryChartViewModel.SELECTED_INDEX_ALL;
96 
97     @VisibleForTesting
98     static final Comparator<AppUsageEvent> APP_USAGE_EVENT_TIMESTAMP_COMPARATOR =
99             Comparator.comparing(AppUsageEvent::getTimestamp);
100 
101     @VisibleForTesting
102     static final Comparator<BatteryEvent> BATTERY_EVENT_TIMESTAMP_COMPARATOR =
103             Comparator.comparing(BatteryEvent::getTimestamp);
104 
105     @VisibleForTesting static boolean sDebug = false;
106 
107     @VisibleForTesting static long sTestCurrentTimeMillis = 0;
108 
109     @VisibleForTesting static Set<String> sTestSystemAppsPackageNames;
110 
111     @VisibleForTesting
112     static IUsageStatsManager sUsageStatsManager =
113             IUsageStatsManager.Stub.asInterface(
114                     ServiceManager.getService(Context.USAGE_STATS_SERVICE));
115 
116     public static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER =
117             "CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER";
118 
119     /** A callback listener when battery usage loading async task is executed. */
120     public interface UsageMapAsyncResponse {
121         /** The callback function when batteryUsageMap is loaded. */
onBatteryCallbackDataLoaded( Map<Integer, Map<Integer, BatteryDiffData>> batteryCallbackData)122         void onBatteryCallbackDataLoaded(
123                 Map<Integer, Map<Integer, BatteryDiffData>> batteryCallbackData);
124     }
125 
DataProcessor()126     private DataProcessor() {}
127 
128     /**
129      * @return Returns battery usage data of different entries. <br>
130      *     Returns null if the input is invalid or there is no enough data.
131      */
132     @Nullable
getBatteryUsageData( Context context, UserIdsSeries userIdsSeries, @Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap)133     public static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageData(
134             Context context,
135             UserIdsSeries userIdsSeries,
136             @Nullable final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
137         if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
138             Log.d(TAG, "getBatteryLevelData() returns null");
139             return null;
140         }
141         // Process raw history map data into hourly timestamps.
142         final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap =
143                 getHistoryMapWithExpectedTimestamps(context, batteryHistoryMap);
144         // Wrap and processed history map into easy-to-use format for UI rendering.
145         final BatteryLevelData batteryLevelData =
146                 getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap);
147         // Loads the current battery usage data from the battery stats service.
148         final Map<String, BatteryHistEntry> currentBatteryHistoryMap =
149                 getCurrentBatteryHistoryMapFromStatsService(context);
150         // Replaces the placeholder in processedBatteryHistoryMap.
151         for (Map.Entry<Long, Map<String, BatteryHistEntry>> mapEntry :
152                 processedBatteryHistoryMap.entrySet()) {
153             if (mapEntry.getValue().containsKey(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) {
154                 mapEntry.setValue(currentBatteryHistoryMap);
155             }
156         }
157         return batteryLevelData == null
158                 ? null
159                 : generateBatteryUsageMap(
160                         context,
161                         getBatteryDiffDataMap(
162                                 context,
163                                 userIdsSeries,
164                                 batteryLevelData.getHourlyBatteryLevelsPerDay(),
165                                 processedBatteryHistoryMap,
166                                 /* appUsagePeriodMap= */ null,
167                                 getSystemAppsPackageNames(context),
168                                 getSystemAppsUids(context)),
169                         batteryLevelData);
170     }
171 
172     /** Gets the {@link BatteryUsageStats} from system service. */
173     @Nullable
getBatteryUsageStats(final Context context)174     public static BatteryUsageStats getBatteryUsageStats(final Context context) {
175         final BatteryUsageStatsQuery batteryUsageStatsQuery =
176                 new BatteryUsageStatsQuery.Builder()
177                         .includeBatteryHistory()
178                         .includeProcessStateData()
179                         .build();
180         return context.getSystemService(BatteryStatsManager.class)
181                 .getBatteryUsageStats(batteryUsageStatsQuery);
182     }
183 
184     /** Gets the {@link UsageEvents} from system service for all unlocked users. */
185     @Nullable
getAppUsageEvents( Context context, UserIdsSeries userIdsSeries)186     public static Map<Long, UsageEvents> getAppUsageEvents(
187             Context context, UserIdsSeries userIdsSeries) {
188         final long start = System.currentTimeMillis();
189         context = DatabaseUtils.getParentContext(context);
190         if (context == null) {
191             return null;
192         }
193         final Map<Long, UsageEvents> resultMap = new ArrayMap();
194         final long sixDaysAgoTimestamp =
195                 DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
196         for (final int userId : userIdsSeries.getVisibleUserIds()) {
197             final UsageEvents events =
198                     getAppUsageEventsForUser(context, userIdsSeries, userId, sixDaysAgoTimestamp);
199             if (events != null) {
200                 resultMap.put(Long.valueOf(userId), events);
201             }
202         }
203         final long elapsedTime = System.currentTimeMillis() - start;
204         Log.d(
205                 TAG,
206                 String.format("getAppUsageEvents() for all unlocked users in %d/ms", elapsedTime));
207         return resultMap.isEmpty() ? null : resultMap;
208     }
209 
210     /** Gets the {@link UsageEvents} from system service for the specific user. */
211     @Nullable
getCurrentAppUsageEventsForUser( Context context, final UserIdsSeries userIdsSeries, final int userID, final long startTimestampOfLevelData)212     public static UsageEvents getCurrentAppUsageEventsForUser(
213             Context context,
214             final UserIdsSeries userIdsSeries,
215             final int userID,
216             final long startTimestampOfLevelData) {
217         final long start = System.currentTimeMillis();
218         context = DatabaseUtils.getParentContext(context);
219         if (context == null) {
220             return null;
221         }
222         final long sixDaysAgoTimestamp =
223                 DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
224         final long earliestTimestamp = Math.max(sixDaysAgoTimestamp, startTimestampOfLevelData);
225         final UsageEvents events =
226                 getAppUsageEventsForUser(context, userIdsSeries, userID, earliestTimestamp);
227         final long elapsedTime = System.currentTimeMillis() - start;
228         Log.d(
229                 TAG,
230                 String.format(
231                         "getAppUsageEventsForUser() for user %d in %d/ms", userID, elapsedTime));
232         return events;
233     }
234 
235     /** Closes the {@link BatteryUsageStats} after using it. */
closeBatteryUsageStats(BatteryUsageStats batteryUsageStats)236     public static void closeBatteryUsageStats(BatteryUsageStats batteryUsageStats) {
237         if (batteryUsageStats != null) {
238             try {
239                 batteryUsageStats.close();
240             } catch (Exception e) {
241                 Log.e(TAG, "BatteryUsageStats.close() failed", e);
242             }
243         }
244     }
245 
246     /**
247      * Generates the indexed {@link AppUsagePeriod} list data for each corresponding time slot.
248      * Attributes the list of {@link AppUsageEvent} into hourly time slots and reformat them into
249      * {@link AppUsagePeriod} for easier use in the following process.
250      *
251      * <p>There could be 2 cases of the returned value:
252      *
253      * <ul>
254      *   <li>null: empty or invalid data.
255      *   <li>non-null: must be a 2d map and composed by:
256      *       <p>[0][0] ~ [maxDailyIndex][maxHourlyIndex]
257      * </ul>
258      *
259      * <p>The structure is consistent with the battery usage map returned by {@code
260      * generateBatteryUsageMap}.
261      *
262      * <p>{@code Long} stands for the userId.
263      *
264      * <p>{@code String} stands for the packageName.
265      */
266     @Nullable
267     public static Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
generateAppUsagePeriodMap( Context context, final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay, final List<AppUsageEvent> appUsageEventList, final List<BatteryEvent> batteryEventList)268             generateAppUsagePeriodMap(
269                     Context context,
270                     final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
271                     final List<AppUsageEvent> appUsageEventList,
272                     final List<BatteryEvent> batteryEventList) {
273         if (appUsageEventList.isEmpty()) {
274             Log.w(TAG, "appUsageEventList is empty");
275             return null;
276         }
277         // Sorts the appUsageEventList and batteryEventList in ascending order based on the
278         // timestamp before distribution.
279         Collections.sort(appUsageEventList, APP_USAGE_EVENT_TIMESTAMP_COMPARATOR);
280         Collections.sort(batteryEventList, BATTERY_EVENT_TIMESTAMP_COMPARATOR);
281         final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> resultMap =
282                 new ArrayMap<>();
283 
284         for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
285             final Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>> dailyMap =
286                     new ArrayMap<>();
287             resultMap.put(dailyIndex, dailyMap);
288             if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
289                 continue;
290             }
291             final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
292             for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
293                 final long startTimestamp = timestamps.get(hourlyIndex);
294                 final long endTimestamp = timestamps.get(hourlyIndex + 1);
295                 // Gets the app usage event list for this hourly slot first.
296                 final List<AppUsageEvent> hourlyAppUsageEventList =
297                         getAppUsageEventListWithinTimeRangeWithBuffer(
298                                 appUsageEventList, startTimestamp, endTimestamp);
299 
300                 // The value could be null when there is no data in the hourly slot.
301                 dailyMap.put(
302                         hourlyIndex,
303                         buildAppUsagePeriodList(
304                                 context,
305                                 hourlyAppUsageEventList,
306                                 batteryEventList,
307                                 startTimestamp,
308                                 endTimestamp));
309             }
310         }
311         return resultMap;
312     }
313 
314     /** Generates the list of {@link AppUsageEvent} from the supplied {@link UsageEvents}. */
generateAppUsageEventListFromUsageEvents( Context context, Map<Long, UsageEvents> usageEventsMap)315     public static List<AppUsageEvent> generateAppUsageEventListFromUsageEvents(
316             Context context, Map<Long, UsageEvents> usageEventsMap) {
317         final List<AppUsageEvent> appUsageEventList = new ArrayList<>();
318         long numEventsFetched = 0;
319         long numAllEventsFetched = 0;
320         final Set<String> ignoreScreenOnTimeTaskRootSet =
321                 FeatureFactory.getFeatureFactory()
322                         .getPowerUsageFeatureProvider()
323                         .getIgnoreScreenOnTimeTaskRootSet();
324         for (final long userId : usageEventsMap.keySet()) {
325             final UsageEvents usageEvents = usageEventsMap.get(userId);
326             while (usageEvents.hasNextEvent()) {
327                 final Event event = new Event();
328                 usageEvents.getNextEvent(event);
329                 numAllEventsFetched++;
330                 switch (event.getEventType()) {
331                     case Event.ACTIVITY_RESUMED:
332                     case Event.ACTIVITY_STOPPED:
333                     case Event.DEVICE_SHUTDOWN:
334                         final String taskRootClassName = event.getTaskRootClassName();
335                         if (!TextUtils.isEmpty(taskRootClassName)
336                                 && ignoreScreenOnTimeTaskRootSet.contains(taskRootClassName)) {
337                             Log.w(
338                                     TAG,
339                                     String.format(
340                                             "Ignoring a usage event with task root class name %s, "
341                                                     + "(timestamp=%d, type=%d)",
342                                             taskRootClassName,
343                                             event.getTimeStamp(),
344                                             event.getEventType()));
345                             break;
346                         }
347                         final AppUsageEvent appUsageEvent =
348                                 ConvertUtils.convertToAppUsageEvent(
349                                         context, sUsageStatsManager, event, userId);
350                         if (appUsageEvent != null) {
351                             numEventsFetched++;
352                             appUsageEventList.add(appUsageEvent);
353                         }
354                         break;
355                     default:
356                         break;
357                 }
358             }
359         }
360         Log.w(
361                 TAG,
362                 String.format(
363                         "Read %d relevant events (%d total) from UsageStatsManager",
364                         numEventsFetched, numAllEventsFetched));
365         return appUsageEventList;
366     }
367 
368     /** Generates the list of {@link BatteryEntry} from the supplied {@link BatteryUsageStats}. */
369     @Nullable
generateBatteryEntryListFromBatteryUsageStats( final Context context, @Nullable final BatteryUsageStats batteryUsageStats)370     public static List<BatteryEntry> generateBatteryEntryListFromBatteryUsageStats(
371             final Context context, @Nullable final BatteryUsageStats batteryUsageStats) {
372         if (batteryUsageStats == null) {
373             Log.w(TAG, "batteryUsageStats is null content");
374             return null;
375         }
376         if (!shouldShowBatteryAttributionList(context)) {
377             return null;
378         }
379         final BatteryUtils batteryUtils = BatteryUtils.getInstance(context);
380         final int dischargePercentage = Math.max(0, batteryUsageStats.getDischargePercentage());
381         final List<BatteryEntry> usageList =
382                 getCoalescedUsageList(
383                         context,
384                         batteryUtils,
385                         batteryUsageStats,
386                         /* loadDataInBackground= */ false);
387         final double totalPower = batteryUsageStats.getConsumedPower();
388         for (int i = 0; i < usageList.size(); i++) {
389             final BatteryEntry entry = usageList.get(i);
390             final double percentOfTotal =
391                     batteryUtils.calculateBatteryPercent(
392                             entry.getConsumedPower(), totalPower, dischargePercentage);
393             entry.mPercent = percentOfTotal;
394         }
395         return usageList;
396     }
397 
398     /**
399      * @return Returns the latest battery history map loaded from the battery stats service.
400      */
getCurrentBatteryHistoryMapFromStatsService( final Context context)401     public static Map<String, BatteryHistEntry> getCurrentBatteryHistoryMapFromStatsService(
402             final Context context) {
403         final List<BatteryHistEntry> batteryHistEntryList =
404                 getBatteryHistListFromFromStatsService(context);
405         return batteryHistEntryList == null
406                 ? new ArrayMap<>()
407                 : batteryHistEntryList.stream().collect(Collectors.toMap(e -> e.getKey(), e -> e));
408     }
409 
410     /**
411      * @return Returns the processed history map which has interpolated to every hour data.
412      *     <p>The start timestamp is the first timestamp in batteryHistoryMap. The end timestamp is
413      *     current time. The keys of processed history map should contain every hour between the
414      *     start and end timestamp. If there's no data in some key, the value will be the empty map.
415      */
getHistoryMapWithExpectedTimestamps( Context context, final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap)416     static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapWithExpectedTimestamps(
417             Context context, final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
418         final long startTime = System.currentTimeMillis();
419         final List<Long> rawTimestampList = new ArrayList<>(batteryHistoryMap.keySet());
420         final Map<Long, Map<String, BatteryHistEntry>> resultMap = new ArrayMap();
421         if (rawTimestampList.isEmpty()) {
422             Log.d(TAG, "empty batteryHistoryMap in getHistoryMapWithExpectedTimestamps()");
423             return resultMap;
424         }
425         Collections.sort(rawTimestampList);
426         final long currentTime = getCurrentTimeMillis();
427         final List<Long> expectedTimestampList = getTimestampSlots(rawTimestampList, currentTime);
428         interpolateHistory(
429                 context, rawTimestampList, expectedTimestampList, batteryHistoryMap, resultMap);
430         Log.d(
431                 TAG,
432                 String.format(
433                         "getHistoryMapWithExpectedTimestamps() size=%d in %d/ms",
434                         resultMap.size(), (System.currentTimeMillis() - startTime)));
435         return resultMap;
436     }
437 
438     @Nullable
getLevelDataThroughProcessedHistoryMap( Context context, final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap)439     static BatteryLevelData getLevelDataThroughProcessedHistoryMap(
440             Context context,
441             final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap) {
442         // There should be at least the start and end timestamps. Otherwise, return null to not show
443         // data in usage chart.
444         if (processedBatteryHistoryMap.size() < MIN_DAILY_DATA_SIZE) {
445             return null;
446         }
447         Map<Long, Integer> batteryLevelMap = new ArrayMap<>();
448         for (Long timestamp : processedBatteryHistoryMap.keySet()) {
449             batteryLevelMap.put(
450                     timestamp, getLevel(context, processedBatteryHistoryMap, timestamp));
451         }
452         return new BatteryLevelData(batteryLevelMap);
453     }
454 
455     /**
456      * Computes expected timestamp slots. The start timestamp is the first timestamp in
457      * rawTimestampList. The end timestamp is current time. The middle timestamps are the sharp hour
458      * timestamps between the start and end timestamps.
459      */
460     @VisibleForTesting
getTimestampSlots(final List<Long> rawTimestampList, final long currentTime)461     static List<Long> getTimestampSlots(final List<Long> rawTimestampList, final long currentTime) {
462         final List<Long> timestampSlots = new ArrayList<>();
463         if (rawTimestampList.isEmpty()) {
464             return timestampSlots;
465         }
466         final long startTimestamp = rawTimestampList.get(0);
467         final long endTimestamp = currentTime;
468         // If the start timestamp is later or equal the end one, return the empty list.
469         if (startTimestamp >= endTimestamp) {
470             return timestampSlots;
471         }
472         timestampSlots.add(startTimestamp);
473         for (long timestamp = TimestampUtils.getNextHourTimestamp(startTimestamp);
474                 timestamp < endTimestamp;
475                 timestamp += DateUtils.HOUR_IN_MILLIS) {
476             timestampSlots.add(timestamp);
477         }
478         timestampSlots.add(endTimestamp);
479         return timestampSlots;
480     }
481 
482     @VisibleForTesting
isFromFullCharge(@ullable final Map<String, BatteryHistEntry> entryList)483     static boolean isFromFullCharge(@Nullable final Map<String, BatteryHistEntry> entryList) {
484         if (entryList == null) {
485             Log.d(TAG, "entryList is null in isFromFullCharge()");
486             return false;
487         }
488         final List<String> entryKeys = new ArrayList<>(entryList.keySet());
489         if (entryKeys.isEmpty()) {
490             Log.d(TAG, "empty entryList in isFromFullCharge()");
491             return false;
492         }
493         // The hist entries in the same timestamp should have same battery status and level.
494         // Checking the first one should be enough.
495         final BatteryHistEntry firstHistEntry = entryList.get(entryKeys.get(0));
496         return BatteryStatus.isCharged(firstHistEntry.mBatteryStatus, firstHistEntry.mBatteryLevel);
497     }
498 
499     @VisibleForTesting
findNearestTimestamp(final List<Long> timestamps, final long target)500     static long[] findNearestTimestamp(final List<Long> timestamps, final long target) {
501         final long[] results = new long[] {Long.MIN_VALUE, Long.MAX_VALUE};
502         // Searches the nearest lower and upper timestamp value.
503         timestamps.forEach(
504                 timestamp -> {
505                     if (timestamp <= target && timestamp > results[0]) {
506                         results[0] = timestamp;
507                     }
508                     if (timestamp >= target && timestamp < results[1]) {
509                         results[1] = timestamp;
510                     }
511                 });
512         // Uses zero value to represent invalid searching result.
513         results[0] = results[0] == Long.MIN_VALUE ? 0 : results[0];
514         results[1] = results[1] == Long.MAX_VALUE ? 0 : results[1];
515         return results;
516     }
517 
getBatteryDiffDataMap( Context context, final UserIdsSeries userIdsSeries, final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay, final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap, final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> appUsagePeriodMap, final @NonNull Set<String> systemAppsPackageNames, final @NonNull Set<Integer> systemAppsUids)518     static Map<Long, BatteryDiffData> getBatteryDiffDataMap(
519             Context context,
520             final UserIdsSeries userIdsSeries,
521             final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
522             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
523             final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
524                     appUsagePeriodMap,
525             final @NonNull Set<String> systemAppsPackageNames,
526             final @NonNull Set<Integer> systemAppsUids) {
527         final Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>();
528         // Each time slot usage diff data =
529         //     sum(Math.abs(timestamp[i+1] data - timestamp[i] data));
530         // since we want to aggregate every hour usage diff data into a single time slot.
531         for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
532             if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
533                 continue;
534             }
535             final List<Long> hourlyTimestamps =
536                     hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
537             for (int hourlyIndex = 0; hourlyIndex < hourlyTimestamps.size() - 1; hourlyIndex++) {
538                 final Long startTimestamp = hourlyTimestamps.get(hourlyIndex);
539                 final Long endTimestamp = hourlyTimestamps.get(hourlyIndex + 1);
540                 final int startBatteryLevel =
541                         hourlyBatteryLevelsPerDay.get(dailyIndex).getLevels().get(hourlyIndex);
542                 final int endBatteryLevel =
543                         hourlyBatteryLevelsPerDay.get(dailyIndex).getLevels().get(hourlyIndex + 1);
544                 final long slotDuration = endTimestamp - startTimestamp;
545                 List<Map<String, BatteryHistEntry>> slotBatteryHistoryList = new ArrayList<>();
546                 slotBatteryHistoryList.add(
547                         batteryHistoryMap.getOrDefault(startTimestamp, EMPTY_BATTERY_MAP));
548                 for (Long timestamp = TimestampUtils.getNextHourTimestamp(startTimestamp);
549                         timestamp < endTimestamp;
550                         timestamp += DateUtils.HOUR_IN_MILLIS) {
551                     slotBatteryHistoryList.add(
552                             batteryHistoryMap.getOrDefault(timestamp, EMPTY_BATTERY_MAP));
553                 }
554                 slotBatteryHistoryList.add(
555                         batteryHistoryMap.getOrDefault(endTimestamp, EMPTY_BATTERY_MAP));
556 
557                 final BatteryDiffData hourlyBatteryDiffData =
558                         insertHourlyUsageDiffDataPerSlot(
559                                 context,
560                                 startTimestamp,
561                                 endTimestamp,
562                                 startBatteryLevel,
563                                 endBatteryLevel,
564                                 userIdsSeries,
565                                 slotDuration,
566                                 systemAppsPackageNames,
567                                 systemAppsUids,
568                                 appUsagePeriodMap == null
569                                                 || appUsagePeriodMap.get(dailyIndex) == null
570                                         ? null
571                                         : appUsagePeriodMap.get(dailyIndex).get(hourlyIndex),
572                                 slotBatteryHistoryList);
573                 batteryDiffDataMap.put(startTimestamp, hourlyBatteryDiffData);
574             }
575         }
576         return batteryDiffDataMap;
577     }
578 
579     /**
580      * @return Returns the indexed battery usage data for each corresponding time slot.
581      *     <p>There could be 2 cases of the returned value:
582      *     <ul>
583      *       <li>null: empty or invalid data.
584      *       <li>1 part: if batteryLevelData is null.
585      *           <p>[SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]
586      *       <li>3 parts: if batteryLevelData is not null.
587      *           <p>1 - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]
588      *           <p>2 - [0][SELECTED_INDEX_ALL] ~ [maxDailyIndex][SELECTED_INDEX_ALL]
589      *           <p>3 - [0][0] ~ [maxDailyIndex][maxHourlyIndex]
590      *     </ul>
591      */
generateBatteryUsageMap( final Context context, final Map<Long, BatteryDiffData> batteryDiffDataMap, final @Nullable BatteryLevelData batteryLevelData)592     static Map<Integer, Map<Integer, BatteryDiffData>> generateBatteryUsageMap(
593             final Context context,
594             final Map<Long, BatteryDiffData> batteryDiffDataMap,
595             final @Nullable BatteryLevelData batteryLevelData) {
596         final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new ArrayMap<>();
597         if (batteryLevelData == null) {
598             Preconditions.checkArgument(batteryDiffDataMap.size() == 1);
599             BatteryDiffData batteryDiffData = batteryDiffDataMap.values().stream().toList().get(0);
600             final Map<Integer, BatteryDiffData> allUsageMap = new ArrayMap<>();
601             allUsageMap.put(SELECTED_INDEX_ALL, batteryDiffData);
602             resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
603             return resultMap;
604         }
605         List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
606                 batteryLevelData.getHourlyBatteryLevelsPerDay();
607         // Insert diff data from [0][0] to [maxDailyIndex][maxHourlyIndex].
608         insertHourlyUsageDiffData(hourlyBatteryLevelsPerDay, batteryDiffDataMap, resultMap);
609         // Insert diff data from [0][SELECTED_INDEX_ALL] to [maxDailyIndex][SELECTED_INDEX_ALL].
610         insertDailyUsageDiffData(context, hourlyBatteryLevelsPerDay, resultMap);
611         // Insert diff data [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL].
612         insertAllUsageDiffData(context, resultMap);
613         if (!isUsageMapValid(resultMap, hourlyBatteryLevelsPerDay)) {
614             return null;
615         }
616         return resultMap;
617     }
618 
619     @VisibleForTesting
620     @Nullable
generateBatteryDiffData( final Context context, final UserIdsSeries userIdsSeries, final long startTimestamp, final List<BatteryHistEntry> batteryHistEntryList, final @NonNull Set<String> systemAppsPackageNames, final @NonNull Set<Integer> systemAppsUids)621     static BatteryDiffData generateBatteryDiffData(
622             final Context context,
623             final UserIdsSeries userIdsSeries,
624             final long startTimestamp,
625             final List<BatteryHistEntry> batteryHistEntryList,
626             final @NonNull Set<String> systemAppsPackageNames,
627             final @NonNull Set<Integer> systemAppsUids) {
628         final List<BatteryDiffEntry> appEntries = new ArrayList<>();
629         final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
630         if (batteryHistEntryList == null || batteryHistEntryList.isEmpty()) {
631             Log.w(TAG, "batteryHistEntryList is null or empty in generateBatteryDiffData()");
632             return new BatteryDiffData(
633                     context,
634                     startTimestamp,
635                     getCurrentTimeMillis(),
636                     /* startBatteryLevel= */ 100,
637                     getCurrentLevel(context),
638                     /* screenOnTime= */ 0L,
639                     appEntries,
640                     systemEntries,
641                     systemAppsPackageNames,
642                     systemAppsUids,
643                     /* isAccumulated= */ false);
644         }
645         for (BatteryHistEntry entry : batteryHistEntryList) {
646             final boolean isFromOtherUsers =
647                     isConsumedFromOtherUsers(userIdsSeries, entry);
648             // Not show other users' battery usage data.
649             if (isFromOtherUsers) {
650                 continue;
651             } else {
652                 final BatteryDiffEntry currentBatteryDiffEntry =
653                         new BatteryDiffEntry(
654                                 context,
655                                 entry.mUid,
656                                 entry.mUserId,
657                                 entry.getKey(),
658                                 entry.mIsHidden,
659                                 entry.mDrainType,
660                                 entry.mPackageName,
661                                 entry.mAppLabel,
662                                 entry.mConsumerType,
663                                 entry.mForegroundUsageTimeInMs,
664                                 entry.mForegroundServiceUsageTimeInMs,
665                                 entry.mBackgroundUsageTimeInMs,
666                                 /* screenOnTimeInMs= */ 0,
667                                 entry.mConsumePower,
668                                 entry.mForegroundUsageConsumePower,
669                                 entry.mForegroundServiceUsageConsumePower,
670                                 entry.mBackgroundUsageConsumePower,
671                                 entry.mCachedUsageConsumePower);
672                 if (currentBatteryDiffEntry.isSystemEntry()) {
673                     systemEntries.add(currentBatteryDiffEntry);
674                 } else {
675                     appEntries.add(currentBatteryDiffEntry);
676                 }
677             }
678         }
679         return new BatteryDiffData(
680                 context,
681                 startTimestamp,
682                 getCurrentTimeMillis(),
683                 /* startBatteryLevel= */ 100,
684                 getCurrentLevel(context),
685                 /* screenOnTime= */ 0L,
686                 appEntries,
687                 systemEntries,
688                 systemAppsPackageNames,
689                 systemAppsUids,
690                 /* isAccumulated= */ false);
691     }
692 
693     /**
694      * {@code Long} stands for the userId.
695      *
696      * <p>{@code String} stands for the packageName.
697      */
698     @VisibleForTesting
699     @Nullable
buildAppUsagePeriodList( Context context, final List<AppUsageEvent> appUsageEvents, final List<BatteryEvent> batteryEventList, final long startTime, final long endTime)700     static Map<Long, Map<String, List<AppUsagePeriod>>> buildAppUsagePeriodList(
701             Context context,
702             final List<AppUsageEvent> appUsageEvents,
703             final List<BatteryEvent> batteryEventList,
704             final long startTime,
705             final long endTime) {
706         if (appUsageEvents.isEmpty()) {
707             return null;
708         }
709 
710         // Attributes the list of AppUsagePeriod into device events and instance events for further
711         // use.
712         final List<AppUsageEvent> deviceEvents = new ArrayList<>();
713         final ArrayMap<Integer, List<AppUsageEvent>> usageEventsByInstanceId = new ArrayMap<>();
714         for (final AppUsageEvent event : appUsageEvents) {
715             final AppUsageEventType eventType = event.getType();
716             if (eventType == AppUsageEventType.ACTIVITY_RESUMED
717                     || eventType == AppUsageEventType.ACTIVITY_STOPPED) {
718                 final int instanceId = event.getInstanceId();
719                 if (usageEventsByInstanceId.get(instanceId) == null) {
720                     usageEventsByInstanceId.put(instanceId, new ArrayList<>());
721                 }
722                 usageEventsByInstanceId.get(instanceId).add(event);
723             } else if (eventType == AppUsageEventType.DEVICE_SHUTDOWN) {
724                 // Track device-wide events in their own list as they affect any app.
725                 deviceEvents.add(event);
726             }
727         }
728         if (usageEventsByInstanceId.isEmpty()) {
729             return null;
730         }
731 
732         final Map<Long, Map<String, List<AppUsagePeriod>>> allUsagePeriods = new ArrayMap<>();
733 
734         for (int i = 0; i < usageEventsByInstanceId.size(); i++) {
735             // The usage periods for an instance are determined by the usage events with its
736             // instance id and any device-wide events such as device shutdown.
737             final List<AppUsageEvent> usageEvents = usageEventsByInstanceId.valueAt(i);
738             if (usageEvents == null || usageEvents.isEmpty()) {
739                 continue;
740             }
741             // The same instance must have same userId and packageName.
742             final AppUsageEvent firstEvent = usageEvents.get(0);
743             final long eventUserId = firstEvent.getUserId();
744             final String packageName =
745                     getEffectivePackageName(
746                             context,
747                             sUsageStatsManager,
748                             firstEvent.getPackageName(),
749                             firstEvent.getTaskRootPackageName());
750             usageEvents.addAll(deviceEvents);
751             // Sorts the usageEvents in ascending order based on the timestamp before computing the
752             // period.
753             Collections.sort(usageEvents, APP_USAGE_EVENT_TIMESTAMP_COMPARATOR);
754 
755             // A package might have multiple instances. Computes the usage period per instance id
756             // and then merges them into the same user-package map.
757             final List<AppUsagePeriod> usagePeriodList =
758                     excludePowerConnectedTimeFromAppUsagePeriodList(
759                             buildAppUsagePeriodListPerInstance(usageEvents, startTime, endTime),
760                             batteryEventList);
761             if (!usagePeriodList.isEmpty()) {
762                 addToUsagePeriodMap(allUsagePeriods, usagePeriodList, eventUserId, packageName);
763             }
764         }
765 
766         // Sorts all usage periods by start time.
767         for (final long userId : allUsagePeriods.keySet()) {
768             if (allUsagePeriods.get(userId) == null) {
769                 continue;
770             }
771             for (final String packageName : allUsagePeriods.get(userId).keySet()) {
772                 Collections.sort(
773                         allUsagePeriods.get(userId).get(packageName),
774                         Comparator.comparing(AppUsagePeriod::getStartTime));
775             }
776         }
777         return allUsagePeriods.isEmpty() ? null : allUsagePeriods;
778     }
779 
780     @VisibleForTesting
buildAppUsagePeriodListPerInstance( final List<AppUsageEvent> usageEvents, final long startTime, final long endTime)781     static List<AppUsagePeriod> buildAppUsagePeriodListPerInstance(
782             final List<AppUsageEvent> usageEvents, final long startTime, final long endTime) {
783         final List<AppUsagePeriod> usagePeriodList = new ArrayList<>();
784         AppUsagePeriod.Builder pendingUsagePeriod = AppUsagePeriod.newBuilder();
785 
786         for (final AppUsageEvent event : usageEvents) {
787             final long eventTime = event.getTimestamp();
788 
789             if (event.getType() == AppUsageEventType.ACTIVITY_RESUMED) {
790                 // If there is an existing start time, simply ignore this start event.
791                 // If there was no start time, then start a new period.
792                 if (!pendingUsagePeriod.hasStartTime()) {
793                     pendingUsagePeriod.setStartTime(eventTime);
794                 }
795             } else if (event.getType() == AppUsageEventType.ACTIVITY_STOPPED) {
796                 pendingUsagePeriod.setEndTime(eventTime);
797                 if (!pendingUsagePeriod.hasStartTime()) {
798                     pendingUsagePeriod.setStartTime(
799                             getStartTimeForIncompleteUsagePeriod(pendingUsagePeriod));
800                 }
801                 // If we already have start time, add it directly.
802                 validateAndAddToPeriodList(
803                         usagePeriodList, pendingUsagePeriod.build(), startTime, endTime);
804                 pendingUsagePeriod.clear();
805             } else if (event.getType() == AppUsageEventType.DEVICE_SHUTDOWN) {
806                 // The end event might be lost when device is shutdown. Use the estimated end
807                 // time for the period.
808                 if (pendingUsagePeriod.hasStartTime()) {
809                     pendingUsagePeriod.setEndTime(
810                             getEndTimeForIncompleteUsagePeriod(pendingUsagePeriod, eventTime));
811                     validateAndAddToPeriodList(
812                             usagePeriodList, pendingUsagePeriod.build(), startTime, endTime);
813                     pendingUsagePeriod.clear();
814                 }
815             }
816         }
817         // If there exists unclosed period, the stop event might happen in the next time
818         // slot. Use the endTime for the period.
819         if (pendingUsagePeriod.hasStartTime() && pendingUsagePeriod.getStartTime() < endTime) {
820             pendingUsagePeriod.setEndTime(endTime);
821             validateAndAddToPeriodList(
822                     usagePeriodList, pendingUsagePeriod.build(), startTime, endTime);
823             pendingUsagePeriod.clear();
824         }
825         return usagePeriodList;
826     }
827 
828     @VisibleForTesting
excludePowerConnectedTimeFromAppUsagePeriodList( final List<AppUsagePeriod> usagePeriodList, final List<BatteryEvent> batteryEventList)829     static List<AppUsagePeriod> excludePowerConnectedTimeFromAppUsagePeriodList(
830             final List<AppUsagePeriod> usagePeriodList, final List<BatteryEvent> batteryEventList) {
831         final List<AppUsagePeriod> resultList = new ArrayList<>();
832         int index = 0;
833         for (AppUsagePeriod inputPeriod : usagePeriodList) {
834             long lastStartTime = inputPeriod.getStartTime();
835             while (index < batteryEventList.size()) {
836                 BatteryEvent batteryEvent = batteryEventList.get(index);
837                 if (batteryEvent.getTimestamp() < inputPeriod.getStartTime()) {
838                     // Because the batteryEventList has been sorted, here is to mark the power
839                     // connection state when the usage period starts. If power is connected when
840                     // the usage period starts, the starting period will be ignored; otherwise it
841                     // will be added.
842                     if (batteryEvent.getType() == BatteryEventType.POWER_CONNECTED) {
843                         lastStartTime = 0;
844                     } else if (batteryEvent.getType() == BatteryEventType.POWER_DISCONNECTED) {
845                         lastStartTime = inputPeriod.getStartTime();
846                     }
847                     index++;
848                     continue;
849                 }
850                 if (batteryEvent.getTimestamp() > inputPeriod.getEndTime()) {
851                     // Because the batteryEventList has been sorted, if any event is already after
852                     // the end time, all the following events should be able to drop directly.
853                     break;
854                 }
855 
856                 if (batteryEvent.getType() == BatteryEventType.POWER_CONNECTED
857                         && lastStartTime != 0) {
858                     resultList.add(
859                             AppUsagePeriod.newBuilder()
860                                     .setStartTime(lastStartTime)
861                                     .setEndTime(batteryEvent.getTimestamp())
862                                     .build());
863                     lastStartTime = 0;
864                 } else if (batteryEvent.getType() == BatteryEventType.POWER_DISCONNECTED) {
865                     lastStartTime = batteryEvent.getTimestamp();
866                 }
867                 index++;
868             }
869             if (lastStartTime != 0) {
870                 resultList.add(
871                         AppUsagePeriod.newBuilder()
872                                 .setStartTime(lastStartTime)
873                                 .setEndTime(inputPeriod.getEndTime())
874                                 .build());
875             }
876         }
877         return resultList;
878     }
879 
880     @VisibleForTesting
getScreenOnTime( final Map<Long, Map<String, List<AppUsagePeriod>>> appUsageMap, final long userId, final String packageName)881     static long getScreenOnTime(
882             final Map<Long, Map<String, List<AppUsagePeriod>>> appUsageMap,
883             final long userId,
884             final String packageName) {
885         if (appUsageMap == null || appUsageMap.get(userId) == null) {
886             return 0;
887         }
888 
889         return getScreenOnTime(appUsageMap.get(userId).get(packageName));
890     }
891 
getBatteryDiffDataMapFromStatsService( final Context context, final UserIdsSeries userIdsSeries, final long startTimestamp, @NonNull final Set<String> systemAppsPackageNames, @NonNull final Set<Integer> systemAppsUids)892     static Map<Long, BatteryDiffData> getBatteryDiffDataMapFromStatsService(
893             final Context context,
894             final UserIdsSeries userIdsSeries,
895             final long startTimestamp,
896             @NonNull final Set<String> systemAppsPackageNames,
897             @NonNull final Set<Integer> systemAppsUids) {
898         Map<Long, BatteryDiffData> batteryDiffDataMap = new ArrayMap<>(1);
899         batteryDiffDataMap.put(
900                 startTimestamp,
901                 generateBatteryDiffData(
902                         context,
903                         userIdsSeries,
904                         startTimestamp,
905                         getBatteryHistListFromFromStatsService(context),
906                         systemAppsPackageNames,
907                         systemAppsUids));
908         return batteryDiffDataMap;
909     }
910 
loadLabelAndIcon( @ullable final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap)911     static void loadLabelAndIcon(
912             @Nullable final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap) {
913         if (batteryUsageMap == null) {
914             return;
915         }
916         // Pre-loads each BatteryDiffEntry relative icon and label for all slots.
917         final BatteryDiffData batteryUsageMapForAll =
918                 batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL);
919         if (batteryUsageMapForAll != null) {
920             batteryUsageMapForAll.getAppDiffEntryList().forEach(entry -> entry.loadLabelAndIcon());
921             batteryUsageMapForAll
922                     .getSystemDiffEntryList()
923                     .forEach(entry -> entry.loadLabelAndIcon());
924         }
925     }
926 
getSystemAppsPackageNames(Context context)927     static Set<String> getSystemAppsPackageNames(Context context) {
928         return sTestSystemAppsPackageNames != null
929                 ? sTestSystemAppsPackageNames
930                 : AppListRepositoryUtil.getSystemPackageNames(context, context.getUserId());
931     }
932 
getSystemAppsUids(Context context)933     static Set<Integer> getSystemAppsUids(Context context) {
934         Set<Integer> result = new ArraySet<>(1);
935         try {
936             result.add(
937                     context.getPackageManager()
938                             .getUidForSharedUser(ANDROID_CORE_APPS_SHARED_USER_ID));
939         } catch (PackageManager.NameNotFoundException e) {
940             // No Android Core Apps
941         }
942         return result;
943     }
944 
945     /**
946      * Generates the list of {@link AppUsageEvent} within the specific time range. The buffer is
947      * added to make sure the app usage calculation near the boundaries is correct.
948      *
949      * <p>Note: The appUsageEventList should have been sorted when calling this function.
950      */
getAppUsageEventListWithinTimeRangeWithBuffer( final List<AppUsageEvent> appUsageEventList, final long startTime, final long endTime)951     private static List<AppUsageEvent> getAppUsageEventListWithinTimeRangeWithBuffer(
952             final List<AppUsageEvent> appUsageEventList, final long startTime, final long endTime) {
953         final long start = startTime - DatabaseUtils.USAGE_QUERY_BUFFER_HOURS;
954         final long end = endTime + DatabaseUtils.USAGE_QUERY_BUFFER_HOURS;
955         final List<AppUsageEvent> resultList = new ArrayList<>();
956         for (final AppUsageEvent event : appUsageEventList) {
957             final long eventTime = event.getTimestamp();
958             // Because the appUsageEventList has been sorted, if any event is already after the end
959             // time, all the following events should be able to drop directly.
960             if (eventTime > end) {
961                 break;
962             }
963             // If the event timestamp is in [start, end], add it into the result list.
964             if (eventTime >= start) {
965                 resultList.add(event);
966             }
967         }
968         return resultList;
969     }
970 
validateAndAddToPeriodList( final List<AppUsagePeriod> appUsagePeriodList, final AppUsagePeriod appUsagePeriod, final long startTime, final long endTime)971     private static void validateAndAddToPeriodList(
972             final List<AppUsagePeriod> appUsagePeriodList,
973             final AppUsagePeriod appUsagePeriod,
974             final long startTime,
975             final long endTime) {
976         final long periodStartTime =
977                 trimPeriodTime(appUsagePeriod.getStartTime(), startTime, endTime);
978         final long periodEndTime = trimPeriodTime(appUsagePeriod.getEndTime(), startTime, endTime);
979         // Only when the period is valid, add it into the list.
980         if (periodStartTime < periodEndTime) {
981             final AppUsagePeriod period =
982                     AppUsagePeriod.newBuilder()
983                             .setStartTime(periodStartTime)
984                             .setEndTime(periodEndTime)
985                             .build();
986             appUsagePeriodList.add(period);
987         }
988     }
989 
trimPeriodTime( final long originalTime, final long startTime, final long endTime)990     private static long trimPeriodTime(
991             final long originalTime, final long startTime, final long endTime) {
992         long finalTime = Math.max(originalTime, startTime);
993         finalTime = Math.min(finalTime, endTime);
994         return finalTime;
995     }
996 
addToUsagePeriodMap( final Map<Long, Map<String, List<AppUsagePeriod>>> usagePeriodMap, final List<AppUsagePeriod> usagePeriodList, final long userId, final String packageName)997     private static void addToUsagePeriodMap(
998             final Map<Long, Map<String, List<AppUsagePeriod>>> usagePeriodMap,
999             final List<AppUsagePeriod> usagePeriodList,
1000             final long userId,
1001             final String packageName) {
1002         usagePeriodMap.computeIfAbsent(userId, key -> new ArrayMap<>());
1003         final Map<String, List<AppUsagePeriod>> packageNameMap = usagePeriodMap.get(userId);
1004         packageNameMap.computeIfAbsent(packageName, key -> new ArrayList<>());
1005         packageNameMap.get(packageName).addAll(usagePeriodList);
1006     }
1007 
1008     /** Returns the start time that gives {@code usagePeriod} the default usage duration. */
getStartTimeForIncompleteUsagePeriod( final AppUsagePeriodOrBuilder usagePeriod)1009     private static long getStartTimeForIncompleteUsagePeriod(
1010             final AppUsagePeriodOrBuilder usagePeriod) {
1011         return usagePeriod.getEndTime() - DEFAULT_USAGE_DURATION_FOR_INCOMPLETE_INTERVAL;
1012     }
1013 
1014     /** Returns the end time that gives {@code usagePeriod} the default usage duration. */
getEndTimeForIncompleteUsagePeriod( final AppUsagePeriodOrBuilder usagePeriod, final long eventTime)1015     private static long getEndTimeForIncompleteUsagePeriod(
1016             final AppUsagePeriodOrBuilder usagePeriod, final long eventTime) {
1017         return Math.min(
1018                 usagePeriod.getStartTime() + DEFAULT_USAGE_DURATION_FOR_INCOMPLETE_INTERVAL,
1019                 eventTime);
1020     }
1021 
1022     @Nullable
getAppUsageEventsForUser( Context context, final UserIdsSeries userIdsSeries, final int userID, final long earliestTimestamp)1023     private static UsageEvents getAppUsageEventsForUser(
1024             Context context,
1025             final UserIdsSeries userIdsSeries,
1026             final int userID,
1027             final long earliestTimestamp) {
1028         final String callingPackage = context.getPackageName();
1029         final long now = System.currentTimeMillis();
1030         // When the user is not unlocked, UsageStatsManager will return null, so bypass the
1031         // following data loading logics directly.
1032         if (userIdsSeries.isUserLocked(userID)) {
1033             Log.w(TAG, "fail to load app usage event for user :" + userID + " because locked");
1034             return null;
1035         }
1036         final long startTime =
1037                 DatabaseUtils.getAppUsageStartTimestampOfUser(context, userID, earliestTimestamp);
1038         return loadAppUsageEventsForUserFromService(
1039                 sUsageStatsManager, startTime, now, userID, callingPackage);
1040     }
1041 
1042     @Nullable
loadAppUsageEventsForUserFromService( final IUsageStatsManager usageStatsManager, final long startTime, final long endTime, final int userId, final String callingPackage)1043     private static UsageEvents loadAppUsageEventsForUserFromService(
1044             final IUsageStatsManager usageStatsManager,
1045             final long startTime,
1046             final long endTime,
1047             final int userId,
1048             final String callingPackage) {
1049         final long start = System.currentTimeMillis();
1050         UsageEvents events = null;
1051         try {
1052             events =
1053                     usageStatsManager.queryEventsForUser(
1054                             startTime, endTime, userId, callingPackage);
1055         } catch (RemoteException e) {
1056             Log.e(TAG, "Error fetching usage events: ", e);
1057         }
1058         final long elapsedTime = System.currentTimeMillis() - start;
1059         Log.d(
1060                 TAG,
1061                 String.format(
1062                         "getAppUsageEventsForUser(): %d from %d to %d in %d/ms",
1063                         userId, startTime, endTime, elapsedTime));
1064         return events;
1065     }
1066 
1067     @Nullable
getBatteryHistListFromFromStatsService( final Context context)1068     private static List<BatteryHistEntry> getBatteryHistListFromFromStatsService(
1069             final Context context) {
1070         List<BatteryHistEntry> batteryHistEntryList = null;
1071         try {
1072             final BatteryUsageStats batteryUsageStats = getBatteryUsageStats(context);
1073             final List<BatteryEntry> batteryEntryList =
1074                     generateBatteryEntryListFromBatteryUsageStats(context, batteryUsageStats);
1075             batteryHistEntryList = convertToBatteryHistEntry(batteryEntryList, batteryUsageStats);
1076             closeBatteryUsageStats(batteryUsageStats);
1077         } catch (RuntimeException e) {
1078             Log.e(TAG, "load batteryUsageStats:", e);
1079         }
1080 
1081         return batteryHistEntryList;
1082     }
1083 
1084     @VisibleForTesting
1085     @Nullable
convertToBatteryHistEntry( @ullable final List<BatteryEntry> batteryEntryList, final BatteryUsageStats batteryUsageStats)1086     static List<BatteryHistEntry> convertToBatteryHistEntry(
1087             @Nullable final List<BatteryEntry> batteryEntryList,
1088             final BatteryUsageStats batteryUsageStats) {
1089         if (batteryEntryList == null || batteryEntryList.isEmpty()) {
1090             Log.w(TAG, "batteryEntryList is null or empty in convertToBatteryHistEntry()");
1091             return null;
1092         }
1093         return batteryEntryList.stream()
1094                 .filter(
1095                         entry -> {
1096                             final long foregroundMs = entry.getTimeInForegroundMs();
1097                             final long backgroundMs = entry.getTimeInBackgroundMs();
1098                             return entry.getConsumedPower() > 0
1099                                     || (entry.getConsumedPower() == 0
1100                                             && (foregroundMs != 0 || backgroundMs != 0));
1101                         })
1102                 .map(entry -> ConvertUtils.convertToBatteryHistEntry(entry, batteryUsageStats))
1103                 .collect(Collectors.toList());
1104     }
1105 
1106     /**
1107      * Interpolates history map based on expected timestamp slots and processes the corner case when
1108      * the expected start timestamp is earlier than what we have.
1109      */
interpolateHistory( Context context, final List<Long> rawTimestampList, final List<Long> expectedTimestampSlots, final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap, final Map<Long, Map<String, BatteryHistEntry>> resultMap)1110     private static void interpolateHistory(
1111             Context context,
1112             final List<Long> rawTimestampList,
1113             final List<Long> expectedTimestampSlots,
1114             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
1115             final Map<Long, Map<String, BatteryHistEntry>> resultMap) {
1116         if (rawTimestampList.isEmpty() || expectedTimestampSlots.isEmpty()) {
1117             return;
1118         }
1119         final int expectedTimestampSlotsSize = expectedTimestampSlots.size();
1120         final long startTimestamp = expectedTimestampSlots.get(0);
1121         final long endTimestamp = expectedTimestampSlots.get(expectedTimestampSlotsSize - 1);
1122 
1123         resultMap.put(startTimestamp, batteryHistoryMap.get(startTimestamp));
1124         for (int index = 1; index < expectedTimestampSlotsSize - 1; index++) {
1125             interpolateHistoryForSlot(
1126                     context,
1127                     expectedTimestampSlots.get(index),
1128                     rawTimestampList,
1129                     batteryHistoryMap,
1130                     resultMap);
1131         }
1132         resultMap.put(
1133                 endTimestamp,
1134                 Map.of(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER, EMPTY_BATTERY_HIST_ENTRY));
1135     }
1136 
interpolateHistoryForSlot( Context context, final long currentSlot, final List<Long> rawTimestampList, final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap, final Map<Long, Map<String, BatteryHistEntry>> resultMap)1137     private static void interpolateHistoryForSlot(
1138             Context context,
1139             final long currentSlot,
1140             final List<Long> rawTimestampList,
1141             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
1142             final Map<Long, Map<String, BatteryHistEntry>> resultMap) {
1143         final long[] nearestTimestamps = findNearestTimestamp(rawTimestampList, currentSlot);
1144         final long lowerTimestamp = nearestTimestamps[0];
1145         final long upperTimestamp = nearestTimestamps[1];
1146         // Case 1: upper timestamp is zero since scheduler is delayed!
1147         if (upperTimestamp == 0) {
1148             log(context, "job scheduler is delayed", currentSlot, null);
1149             resultMap.put(currentSlot, new ArrayMap<>());
1150             return;
1151         }
1152         // Case 2: upper timestamp is closed to the current timestamp.
1153         if ((upperTimestamp - currentSlot)
1154                 < MAX_DIFF_SECONDS_OF_UPPER_TIMESTAMP * DateUtils.SECOND_IN_MILLIS) {
1155             log(context, "force align into the nearest slot", currentSlot, null);
1156             resultMap.put(currentSlot, batteryHistoryMap.get(upperTimestamp));
1157             return;
1158         }
1159         // Case 3: lower timestamp is zero before starting to collect data.
1160         if (lowerTimestamp == 0) {
1161             log(context, "no lower timestamp slot data", currentSlot, null);
1162             resultMap.put(currentSlot, new ArrayMap<>());
1163             return;
1164         }
1165         interpolateHistoryForSlot(
1166                 context, currentSlot, lowerTimestamp, upperTimestamp, batteryHistoryMap, resultMap);
1167     }
1168 
interpolateHistoryForSlot( Context context, final long currentSlot, final long lowerTimestamp, final long upperTimestamp, final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap, final Map<Long, Map<String, BatteryHistEntry>> resultMap)1169     private static void interpolateHistoryForSlot(
1170             Context context,
1171             final long currentSlot,
1172             final long lowerTimestamp,
1173             final long upperTimestamp,
1174             final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
1175             final Map<Long, Map<String, BatteryHistEntry>> resultMap) {
1176         final Map<String, BatteryHistEntry> lowerEntryDataMap =
1177                 batteryHistoryMap.get(lowerTimestamp);
1178         final Map<String, BatteryHistEntry> upperEntryDataMap =
1179                 batteryHistoryMap.get(upperTimestamp);
1180         // Verifies whether the lower data is valid to use or not by checking boot time.
1181         final BatteryHistEntry upperEntryDataFirstEntry =
1182                 upperEntryDataMap.values().stream().findFirst().get();
1183         final long upperEntryDataBootTimestamp =
1184                 upperEntryDataFirstEntry.mTimestamp - upperEntryDataFirstEntry.mBootTimestamp;
1185         // Lower data is captured before upper data corresponding device is booting.
1186         // Skips the booting-specific logics and always does interpolation for daily chart level
1187         // data.
1188         if (lowerTimestamp < upperEntryDataBootTimestamp
1189                 && !TimestampUtils.isMidnight(currentSlot)) {
1190             // Provides an opportunity to force align the slot directly.
1191             if ((upperTimestamp - currentSlot) < 10 * DateUtils.MINUTE_IN_MILLIS) {
1192                 log(context, "force align into the nearest slot", currentSlot, null);
1193                 resultMap.put(currentSlot, upperEntryDataMap);
1194             } else {
1195                 log(context, "in the different booting section", currentSlot, null);
1196                 resultMap.put(currentSlot, new ArrayMap<>());
1197             }
1198             return;
1199         }
1200         log(context, "apply interpolation arithmetic", currentSlot, null);
1201         final Map<String, BatteryHistEntry> newHistEntryMap = new ArrayMap<>();
1202         final double timestampLength = upperTimestamp - lowerTimestamp;
1203         final double timestampDiff = currentSlot - lowerTimestamp;
1204         // Applies interpolation arithmetic for each BatteryHistEntry.
1205         for (String entryKey : upperEntryDataMap.keySet()) {
1206             final BatteryHistEntry lowerEntry = lowerEntryDataMap.get(entryKey);
1207             final BatteryHistEntry upperEntry = upperEntryDataMap.get(entryKey);
1208             // Checks whether there is any abnormal battery reset conditions.
1209             if (lowerEntry != null) {
1210                 final boolean invalidForegroundUsageTime =
1211                         lowerEntry.mForegroundUsageTimeInMs > upperEntry.mForegroundUsageTimeInMs;
1212                 final boolean invalidBackgroundUsageTime =
1213                         lowerEntry.mBackgroundUsageTimeInMs > upperEntry.mBackgroundUsageTimeInMs;
1214                 if (invalidForegroundUsageTime || invalidBackgroundUsageTime) {
1215                     newHistEntryMap.put(entryKey, upperEntry);
1216                     log(context, "abnormal reset condition is found", currentSlot, upperEntry);
1217                     continue;
1218                 }
1219             }
1220             final BatteryHistEntry newEntry =
1221                     BatteryHistEntry.interpolate(
1222                             currentSlot,
1223                             upperTimestamp,
1224                             /* ratio= */ timestampDiff / timestampLength,
1225                             lowerEntry,
1226                             upperEntry);
1227             newHistEntryMap.put(entryKey, newEntry);
1228             if (lowerEntry == null) {
1229                 log(context, "cannot find lower entry data", currentSlot, upperEntry);
1230                 continue;
1231             }
1232         }
1233         resultMap.put(currentSlot, newHistEntryMap);
1234     }
1235 
getLevel( Context context, final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap, final long timestamp)1236     private static Integer getLevel(
1237             Context context,
1238             final Map<Long, Map<String, BatteryHistEntry>> processedBatteryHistoryMap,
1239             final long timestamp) {
1240         final Map<String, BatteryHistEntry> entryMap = processedBatteryHistoryMap.get(timestamp);
1241         if (entryMap == null || entryMap.isEmpty()) {
1242             Log.e(
1243                     TAG,
1244                     "abnormal entry list in the timestamp:"
1245                             + ConvertUtils.utcToLocalTimeForLogging(timestamp));
1246             return BATTERY_LEVEL_UNKNOWN;
1247         }
1248         // The current time battery history hasn't been loaded yet, returns the current battery
1249         // level.
1250         if (entryMap.containsKey(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) {
1251             return getCurrentLevel(context);
1252         }
1253         // Averages the battery level in each time slot to avoid corner conditions.
1254         float batteryLevelCounter = 0;
1255         for (BatteryHistEntry entry : entryMap.values()) {
1256             batteryLevelCounter += entry.mBatteryLevel;
1257         }
1258         return Math.round(batteryLevelCounter / entryMap.size());
1259     }
1260 
getCurrentLevel(Context context)1261     private static int getCurrentLevel(Context context) {
1262         final Intent intent = BatteryUtils.getBatteryIntent(context);
1263         return BatteryStatus.getBatteryLevel(intent);
1264     }
1265 
insertHourlyUsageDiffData( final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay, final Map<Long, BatteryDiffData> batteryDiffDataMap, final Map<Integer, Map<Integer, BatteryDiffData>> resultMap)1266     private static void insertHourlyUsageDiffData(
1267             final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
1268             final Map<Long, BatteryDiffData> batteryDiffDataMap,
1269             final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
1270         // Each time slot usage diff data =
1271         //     sum(Math.abs(timestamp[i+1] data - timestamp[i] data));
1272         // since we want to aggregate every hour usage diff data into a single time slot.
1273         for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
1274             final Map<Integer, BatteryDiffData> dailyDiffMap = new ArrayMap<>();
1275             resultMap.put(dailyIndex, dailyDiffMap);
1276             if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
1277                 continue;
1278             }
1279             final List<Long> hourlyTimestamps =
1280                     hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
1281             for (int hourlyIndex = 0; hourlyIndex < hourlyTimestamps.size() - 1; hourlyIndex++) {
1282                 final Long startTimestamp = hourlyTimestamps.get(hourlyIndex);
1283                 dailyDiffMap.put(hourlyIndex, batteryDiffDataMap.get(startTimestamp));
1284             }
1285         }
1286     }
1287 
insertDailyUsageDiffData( final Context context, final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay, final Map<Integer, Map<Integer, BatteryDiffData>> resultMap)1288     private static void insertDailyUsageDiffData(
1289             final Context context,
1290             final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
1291             final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
1292         for (int index = 0; index < hourlyBatteryLevelsPerDay.size(); index++) {
1293             Map<Integer, BatteryDiffData> dailyUsageMap = resultMap.get(index);
1294             if (dailyUsageMap == null) {
1295                 dailyUsageMap = new ArrayMap<>();
1296                 resultMap.put(index, dailyUsageMap);
1297             }
1298             dailyUsageMap.put(
1299                     SELECTED_INDEX_ALL,
1300                     getAccumulatedUsageDiffData(context, dailyUsageMap.values()));
1301         }
1302     }
1303 
insertAllUsageDiffData( final Context context, final Map<Integer, Map<Integer, BatteryDiffData>> resultMap)1304     private static void insertAllUsageDiffData(
1305             final Context context, final Map<Integer, Map<Integer, BatteryDiffData>> resultMap) {
1306         final List<BatteryDiffData> diffDataList = new ArrayList<>();
1307         resultMap
1308                 .keySet()
1309                 .forEach(key -> diffDataList.add(resultMap.get(key).get(SELECTED_INDEX_ALL)));
1310         final Map<Integer, BatteryDiffData> allUsageMap = new ArrayMap<>();
1311         allUsageMap.put(SELECTED_INDEX_ALL, getAccumulatedUsageDiffData(context, diffDataList));
1312         resultMap.put(SELECTED_INDEX_ALL, allUsageMap);
1313     }
1314 
1315     @Nullable
insertHourlyUsageDiffDataPerSlot( final Context context, final long startTimestamp, final long endTimestamp, final int startBatteryLevel, final int endBatteryLevel, final UserIdsSeries userIdsSeries, final long slotDuration, final Set<String> systemAppsPackageNames, final Set<Integer> systemAppsUids, final Map<Long, Map<String, List<AppUsagePeriod>>> appUsageMap, final List<Map<String, BatteryHistEntry>> slotBatteryHistoryList)1316     private static BatteryDiffData insertHourlyUsageDiffDataPerSlot(
1317             final Context context,
1318             final long startTimestamp,
1319             final long endTimestamp,
1320             final int startBatteryLevel,
1321             final int endBatteryLevel,
1322             final UserIdsSeries userIdsSeries,
1323             final long slotDuration,
1324             final Set<String> systemAppsPackageNames,
1325             final Set<Integer> systemAppsUids,
1326             final Map<Long, Map<String, List<AppUsagePeriod>>> appUsageMap,
1327             final List<Map<String, BatteryHistEntry>> slotBatteryHistoryList) {
1328         long slotScreenOnTime = 0L;
1329         if (appUsageMap != null) {
1330             final List<AppUsagePeriod> flatAppUsagePeriodList = new ArrayList<>();
1331             for (final long userId : appUsageMap.keySet()) {
1332                 if (userIdsSeries.isFromOtherUsers(userId) || appUsageMap.get(userId) == null) {
1333                     continue;
1334                 }
1335                 for (final String packageName : appUsageMap.get(userId).keySet()) {
1336                     final List<AppUsagePeriod> appUsagePeriodList =
1337                             appUsageMap.get(userId).get(packageName);
1338                     if (appUsagePeriodList != null) {
1339                         flatAppUsagePeriodList.addAll(appUsagePeriodList);
1340                     }
1341                 }
1342             }
1343             slotScreenOnTime = Math.min(slotDuration, getScreenOnTime(flatAppUsagePeriodList));
1344         }
1345 
1346         final List<BatteryDiffEntry> appEntries = new ArrayList<>();
1347         final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
1348 
1349         // Collects all keys in these three time slot records as all populations.
1350         final Set<String> allBatteryHistEntryKeys = new ArraySet<>();
1351         for (Map<String, BatteryHistEntry> slotBatteryHistMap : slotBatteryHistoryList) {
1352             if (slotBatteryHistMap.isEmpty()) {
1353                 // We should not get the empty list since we have at least one fake data to record
1354                 // the battery level and status in each time slot, the empty list is used to
1355                 // represent there is no enough data to apply interpolation arithmetic.
1356                 return new BatteryDiffData(
1357                         context,
1358                         startTimestamp,
1359                         endTimestamp,
1360                         startBatteryLevel,
1361                         endBatteryLevel,
1362                         /* screenOnTime= */ 0L,
1363                         appEntries,
1364                         systemEntries,
1365                         systemAppsPackageNames,
1366                         systemAppsUids,
1367                         /* isAccumulated= */ false);
1368             }
1369             allBatteryHistEntryKeys.addAll(slotBatteryHistMap.keySet());
1370         }
1371 
1372         // Calculates all packages diff usage data in a specific time slot.
1373         for (String key : allBatteryHistEntryKeys) {
1374             if (key == null) {
1375                 continue;
1376             }
1377 
1378             BatteryHistEntry selectedBatteryEntry = null;
1379             final List<BatteryHistEntry> batteryHistEntries = new ArrayList<>();
1380             for (Map<String, BatteryHistEntry> slotBatteryHistMap : slotBatteryHistoryList) {
1381                 BatteryHistEntry entry =
1382                         slotBatteryHistMap.getOrDefault(key, EMPTY_BATTERY_HIST_ENTRY);
1383                 batteryHistEntries.add(entry);
1384                 if (selectedBatteryEntry == null && entry != EMPTY_BATTERY_HIST_ENTRY) {
1385                     selectedBatteryEntry = entry;
1386                 }
1387             }
1388             if (selectedBatteryEntry == null) {
1389                 continue;
1390             }
1391 
1392             // Not show other users' battery usage data.
1393             final boolean isFromOtherUsers =
1394                     isConsumedFromOtherUsers(userIdsSeries, selectedBatteryEntry);
1395             if (isFromOtherUsers) {
1396                 continue;
1397             }
1398 
1399             // Cumulative values is a specific time slot for a specific app.
1400             long foregroundUsageTimeInMs = 0;
1401             long foregroundServiceUsageTimeInMs = 0;
1402             long backgroundUsageTimeInMs = 0;
1403             double consumePower = 0;
1404             double foregroundUsageConsumePower = 0;
1405             double foregroundServiceUsageConsumePower = 0;
1406             double backgroundUsageConsumePower = 0;
1407             double cachedUsageConsumePower = 0;
1408             for (int i = 0; i < batteryHistEntries.size() - 1; i++) {
1409                 final BatteryHistEntry currentEntry = batteryHistEntries.get(i);
1410                 final BatteryHistEntry nextEntry = batteryHistEntries.get(i + 1);
1411                 foregroundUsageTimeInMs +=
1412                         getDiffValue(
1413                                 currentEntry.mForegroundUsageTimeInMs,
1414                                 nextEntry.mForegroundUsageTimeInMs);
1415                 foregroundServiceUsageTimeInMs +=
1416                         getDiffValue(
1417                                 currentEntry.mForegroundServiceUsageTimeInMs,
1418                                 nextEntry.mForegroundServiceUsageTimeInMs);
1419                 backgroundUsageTimeInMs +=
1420                         getDiffValue(
1421                                 currentEntry.mBackgroundUsageTimeInMs,
1422                                 nextEntry.mBackgroundUsageTimeInMs);
1423                 consumePower += getDiffValue(currentEntry.mConsumePower, nextEntry.mConsumePower);
1424                 foregroundUsageConsumePower +=
1425                         getDiffValue(
1426                                 currentEntry.mForegroundUsageConsumePower,
1427                                 nextEntry.mForegroundUsageConsumePower);
1428                 foregroundServiceUsageConsumePower +=
1429                         getDiffValue(
1430                                 currentEntry.mForegroundServiceUsageConsumePower,
1431                                 nextEntry.mForegroundServiceUsageConsumePower);
1432                 backgroundUsageConsumePower +=
1433                         getDiffValue(
1434                                 currentEntry.mBackgroundUsageConsumePower,
1435                                 nextEntry.mBackgroundUsageConsumePower);
1436                 cachedUsageConsumePower +=
1437                         getDiffValue(
1438                                 currentEntry.mCachedUsageConsumePower,
1439                                 nextEntry.mCachedUsageConsumePower);
1440             }
1441             if (isSystemConsumer(selectedBatteryEntry.mConsumerType)
1442                     && selectedBatteryEntry.mDrainType == BatteryConsumer.POWER_COMPONENT_SCREEN) {
1443                 // Replace Screen system component time with screen on time.
1444                 foregroundUsageTimeInMs = slotScreenOnTime;
1445             }
1446             // Excludes entry since we don't have enough data to calculate.
1447             if (foregroundUsageTimeInMs == 0
1448                     && foregroundServiceUsageTimeInMs == 0
1449                     && backgroundUsageTimeInMs == 0
1450                     && consumePower == 0) {
1451                 continue;
1452             }
1453             // Forces refine the cumulative value since it may introduce deviation error since we
1454             // will apply the interpolation arithmetic.
1455             final float totalUsageTimeInMs =
1456                     foregroundUsageTimeInMs
1457                             + backgroundUsageTimeInMs
1458                             + foregroundServiceUsageTimeInMs;
1459             if (totalUsageTimeInMs > slotDuration) {
1460                 final float ratio = slotDuration / totalUsageTimeInMs;
1461                 if (sDebug) {
1462                     Log.w(
1463                             TAG,
1464                             String.format(
1465                                     "abnormal usage time %d|%d|%d for:\n%s",
1466                                     Duration.ofMillis(foregroundUsageTimeInMs).getSeconds(),
1467                                     Duration.ofMillis(foregroundServiceUsageTimeInMs).getSeconds(),
1468                                     Duration.ofMillis(backgroundUsageTimeInMs).getSeconds(),
1469                                     selectedBatteryEntry));
1470                 }
1471                 foregroundUsageTimeInMs = Math.round(foregroundUsageTimeInMs * ratio);
1472                 foregroundServiceUsageTimeInMs = Math.round(foregroundServiceUsageTimeInMs * ratio);
1473                 backgroundUsageTimeInMs = Math.round(backgroundUsageTimeInMs * ratio);
1474                 consumePower = consumePower * ratio;
1475                 foregroundUsageConsumePower = foregroundUsageConsumePower * ratio;
1476                 foregroundServiceUsageConsumePower = foregroundServiceUsageConsumePower * ratio;
1477                 backgroundUsageConsumePower = backgroundUsageConsumePower * ratio;
1478                 cachedUsageConsumePower = cachedUsageConsumePower * ratio;
1479             }
1480 
1481             // Compute the screen on time and make sure it won't exceed the threshold.
1482             final long screenOnTime =
1483                     Math.min(
1484                             (long) slotDuration,
1485                             getScreenOnTime(
1486                                     appUsageMap,
1487                                     selectedBatteryEntry.mUserId,
1488                                     selectedBatteryEntry.mPackageName));
1489             // Ensure the following value will not exceed the threshold.
1490             // value = background + foregroundService + screen-on
1491             backgroundUsageTimeInMs =
1492                     Math.min(backgroundUsageTimeInMs, (long) slotDuration - screenOnTime);
1493             foregroundServiceUsageTimeInMs =
1494                     Math.min(
1495                             foregroundServiceUsageTimeInMs,
1496                             (long) slotDuration - screenOnTime - backgroundUsageTimeInMs);
1497             final BatteryDiffEntry currentBatteryDiffEntry =
1498                     new BatteryDiffEntry(
1499                             context,
1500                             selectedBatteryEntry.mUid,
1501                             selectedBatteryEntry.mUserId,
1502                             selectedBatteryEntry.getKey(),
1503                             selectedBatteryEntry.mIsHidden,
1504                             selectedBatteryEntry.mDrainType,
1505                             selectedBatteryEntry.mPackageName,
1506                             selectedBatteryEntry.mAppLabel,
1507                             selectedBatteryEntry.mConsumerType,
1508                             foregroundUsageTimeInMs,
1509                             foregroundServiceUsageTimeInMs,
1510                             backgroundUsageTimeInMs,
1511                             screenOnTime,
1512                             consumePower,
1513                             foregroundUsageConsumePower,
1514                             foregroundServiceUsageConsumePower,
1515                             backgroundUsageConsumePower,
1516                             cachedUsageConsumePower);
1517             if (currentBatteryDiffEntry.isSystemEntry()) {
1518                 systemEntries.add(currentBatteryDiffEntry);
1519             } else {
1520                 appEntries.add(currentBatteryDiffEntry);
1521             }
1522         }
1523         return new BatteryDiffData(
1524                 context,
1525                 startTimestamp,
1526                 endTimestamp,
1527                 startBatteryLevel,
1528                 endBatteryLevel,
1529                 slotScreenOnTime,
1530                 appEntries,
1531                 systemEntries,
1532                 systemAppsPackageNames,
1533                 systemAppsUids,
1534                 /* isAccumulated= */ false);
1535     }
1536 
getScreenOnTime(@ullable final List<AppUsagePeriod> appUsagePeriodList)1537     private static long getScreenOnTime(@Nullable final List<AppUsagePeriod> appUsagePeriodList) {
1538         if (appUsagePeriodList == null || appUsagePeriodList.isEmpty()) {
1539             return 0;
1540         }
1541         // Create a list of endpoints (the beginning or the end) of usage periods and order the list
1542         // chronologically.
1543         final List<AppUsageEndPoint> endPoints =
1544                 appUsagePeriodList.stream()
1545                         .flatMap(
1546                                 foregroundUsage ->
1547                                         Stream.of(
1548                                                 AppUsageEndPoint.newBuilder()
1549                                                         .setTimestamp(
1550                                                                 foregroundUsage.getStartTime())
1551                                                         .setType(AppUsageEndPointType.START)
1552                                                         .build(),
1553                                                 AppUsageEndPoint.newBuilder()
1554                                                         .setTimestamp(foregroundUsage.getEndTime())
1555                                                         .setType(AppUsageEndPointType.END)
1556                                                         .build()))
1557                         .sorted((x, y) -> (int) (x.getTimestamp() - y.getTimestamp()))
1558                         .collect(Collectors.toList());
1559 
1560         // Traverse the list of endpoints in order to determine the non-overlapping usage duration.
1561         int numberOfActiveAppUsagePeriods = 0;
1562         long startOfCurrentContiguousAppUsagePeriod = 0;
1563         long totalScreenOnTime = 0;
1564         for (final AppUsageEndPoint endPoint : endPoints) {
1565             if (endPoint.getType() == AppUsageEndPointType.START) {
1566                 if (numberOfActiveAppUsagePeriods++ == 0) {
1567                     startOfCurrentContiguousAppUsagePeriod = endPoint.getTimestamp();
1568                 }
1569             } else {
1570                 if (--numberOfActiveAppUsagePeriods == 0) {
1571                     totalScreenOnTime +=
1572                             (endPoint.getTimestamp() - startOfCurrentContiguousAppUsagePeriod);
1573                 }
1574             }
1575         }
1576 
1577         return totalScreenOnTime;
1578     }
1579 
isConsumedFromOtherUsers( final UserIdsSeries userIdsSeries, final BatteryHistEntry batteryHistEntry)1580     private static boolean isConsumedFromOtherUsers(
1581             final UserIdsSeries userIdsSeries,
1582             final BatteryHistEntry batteryHistEntry) {
1583         return isUidConsumer(batteryHistEntry.mConsumerType)
1584                 && userIdsSeries.isFromOtherUsers(batteryHistEntry.mUserId);
1585     }
1586 
1587     @Nullable
getAccumulatedUsageDiffData( final Context context, final Collection<BatteryDiffData> batteryDiffDataList)1588     private static BatteryDiffData getAccumulatedUsageDiffData(
1589             final Context context, final Collection<BatteryDiffData> batteryDiffDataList) {
1590         final Map<String, BatteryDiffEntry> diffEntryMap = new ArrayMap<>();
1591         final List<BatteryDiffEntry> appEntries = new ArrayList<>();
1592         final List<BatteryDiffEntry> systemEntries = new ArrayList<>();
1593 
1594         long startTimestamp = Long.MAX_VALUE;
1595         long endTimestamp = 0;
1596         int startBatteryLevel = BATTERY_LEVEL_UNKNOWN;
1597         int endBatteryLevel = BATTERY_LEVEL_UNKNOWN;
1598         long totalScreenOnTime = 0;
1599         for (BatteryDiffData batteryDiffData : batteryDiffDataList) {
1600             if (batteryDiffData == null) {
1601                 continue;
1602             }
1603             if (startTimestamp > batteryDiffData.getStartTimestamp()) {
1604                 startTimestamp = batteryDiffData.getStartTimestamp();
1605                 startBatteryLevel = batteryDiffData.getStartBatteryLevel();
1606             }
1607             if (endTimestamp > batteryDiffData.getEndTimestamp()) {
1608                 endTimestamp = batteryDiffData.getEndTimestamp();
1609                 endBatteryLevel = batteryDiffData.getEndBatteryLevel();
1610             }
1611             totalScreenOnTime += batteryDiffData.getScreenOnTime();
1612             for (BatteryDiffEntry entry : batteryDiffData.getAppDiffEntryList()) {
1613                 computeUsageDiffDataPerEntry(entry, diffEntryMap);
1614             }
1615             for (BatteryDiffEntry entry : batteryDiffData.getSystemDiffEntryList()) {
1616                 computeUsageDiffDataPerEntry(entry, diffEntryMap);
1617             }
1618         }
1619 
1620         final Collection<BatteryDiffEntry> diffEntryList = diffEntryMap.values();
1621         for (BatteryDiffEntry entry : diffEntryList) {
1622             if (entry.isSystemEntry()) {
1623                 systemEntries.add(entry);
1624             } else {
1625                 appEntries.add(entry);
1626             }
1627         }
1628 
1629         return new BatteryDiffData(
1630                 context,
1631                 startTimestamp,
1632                 endTimestamp,
1633                 startBatteryLevel,
1634                 endBatteryLevel,
1635                 totalScreenOnTime,
1636                 appEntries,
1637                 systemEntries,
1638                 /* systemAppsPackageNames= */ new ArraySet<>(),
1639                 /* systemAppsUids= */ new ArraySet<>(),
1640                 /* isAccumulated= */ true);
1641     }
1642 
computeUsageDiffDataPerEntry( final BatteryDiffEntry entry, final Map<String, BatteryDiffEntry> diffEntryMap)1643     private static void computeUsageDiffDataPerEntry(
1644             final BatteryDiffEntry entry, final Map<String, BatteryDiffEntry> diffEntryMap) {
1645         final String key = entry.getKey();
1646         final BatteryDiffEntry oldBatteryDiffEntry = diffEntryMap.get(key);
1647         // Creates new BatteryDiffEntry if we don't have it.
1648         if (oldBatteryDiffEntry == null) {
1649             diffEntryMap.put(key, entry.clone());
1650         } else {
1651             // Sums up some field data into the existing one.
1652             oldBatteryDiffEntry.mForegroundUsageTimeInMs += entry.mForegroundUsageTimeInMs;
1653             oldBatteryDiffEntry.mForegroundServiceUsageTimeInMs +=
1654                     entry.mForegroundServiceUsageTimeInMs;
1655             oldBatteryDiffEntry.mBackgroundUsageTimeInMs += entry.mBackgroundUsageTimeInMs;
1656             oldBatteryDiffEntry.mScreenOnTimeInMs += entry.mScreenOnTimeInMs;
1657             oldBatteryDiffEntry.mConsumePower += entry.mConsumePower;
1658             oldBatteryDiffEntry.mForegroundUsageConsumePower += entry.mForegroundUsageConsumePower;
1659             oldBatteryDiffEntry.mForegroundServiceUsageConsumePower +=
1660                     entry.mForegroundServiceUsageConsumePower;
1661             oldBatteryDiffEntry.mBackgroundUsageConsumePower += entry.mBackgroundUsageConsumePower;
1662             oldBatteryDiffEntry.mCachedUsageConsumePower += entry.mCachedUsageConsumePower;
1663         }
1664     }
1665 
shouldShowBatteryAttributionList(final Context context)1666     private static boolean shouldShowBatteryAttributionList(final Context context) {
1667         final PowerProfile powerProfile = new PowerProfile(context);
1668         // Cheap hack to try to figure out if the power_profile.xml was populated.
1669         final double averagePowerForOrdinal =
1670                 powerProfile.getAveragePowerForOrdinal(
1671                         PowerProfile.POWER_GROUP_DISPLAY_SCREEN_FULL, 0);
1672         final boolean shouldShowBatteryAttributionList =
1673                 averagePowerForOrdinal >= MIN_AVERAGE_POWER_THRESHOLD_MILLI_AMP;
1674         if (!shouldShowBatteryAttributionList) {
1675             Log.w(TAG, "shouldShowBatteryAttributionList(): " + averagePowerForOrdinal);
1676         }
1677         return shouldShowBatteryAttributionList;
1678     }
1679 
1680     /**
1681      * We want to coalesce some UIDs. For example, dex2oat runs under a shared gid that exists for
1682      * all users of the same app. We detect this case and merge the power use for dex2oat to the
1683      * device OWNER's use of the app.
1684      *
1685      * @return A sorted list of apps using power.
1686      */
getCoalescedUsageList( final Context context, final BatteryUtils batteryUtils, final BatteryUsageStats batteryUsageStats, final boolean loadDataInBackground)1687     private static List<BatteryEntry> getCoalescedUsageList(
1688             final Context context,
1689             final BatteryUtils batteryUtils,
1690             final BatteryUsageStats batteryUsageStats,
1691             final boolean loadDataInBackground) {
1692         final PackageManager packageManager = context.getPackageManager();
1693         final UserManager userManager = context.getSystemService(UserManager.class);
1694         final SparseArray<BatteryEntry> batteryEntryList = new SparseArray<>();
1695         final ArrayList<BatteryEntry> results = new ArrayList<>();
1696         final List<UidBatteryConsumer> uidBatteryConsumers =
1697                 batteryUsageStats.getUidBatteryConsumers();
1698 
1699         // Sort to have all apps with "real" UIDs first, followed by apps that are supposed
1700         // to be combined with the real ones.
1701         uidBatteryConsumers.sort(
1702                 Comparator.comparingInt(
1703                         consumer -> consumer.getUid() == getRealUid(consumer) ? 0 : 1));
1704 
1705         for (int i = 0, size = uidBatteryConsumers.size(); i < size; i++) {
1706             final UidBatteryConsumer consumer = uidBatteryConsumers.get(i);
1707             final int uid = getRealUid(consumer);
1708 
1709             final String[] packages = packageManager.getPackagesForUid(uid);
1710             if (batteryUtils.shouldHideUidBatteryConsumerUnconditionally(consumer, packages)) {
1711                 continue;
1712             }
1713 
1714             final boolean isHidden = batteryUtils.shouldHideUidBatteryConsumer(consumer, packages);
1715             final int index = batteryEntryList.indexOfKey(uid);
1716             if (index < 0) {
1717                 // New entry.
1718                 batteryEntryList.put(
1719                         uid,
1720                         new BatteryEntry(
1721                                 context,
1722                                 userManager,
1723                                 consumer,
1724                                 isHidden,
1725                                 uid,
1726                                 packages,
1727                                 null,
1728                                 loadDataInBackground));
1729             } else {
1730                 // Combine BatterySippers if we already have one with this UID.
1731                 final BatteryEntry existingSipper = batteryEntryList.valueAt(index);
1732                 existingSipper.add(consumer);
1733             }
1734         }
1735 
1736         final BatteryConsumer deviceConsumer =
1737                 batteryUsageStats.getAggregateBatteryConsumer(
1738                         BatteryUsageStats.AGGREGATE_BATTERY_CONSUMER_SCOPE_DEVICE);
1739 
1740         for (int componentId = 0;
1741                 componentId < BatteryConsumer.POWER_COMPONENT_COUNT;
1742                 componentId++) {
1743             results.add(
1744                     new BatteryEntry(
1745                             context,
1746                             componentId,
1747                             deviceConsumer.getConsumedPower(componentId),
1748                             deviceConsumer.getUsageDurationMillis(componentId),
1749                             componentId == POWER_COMPONENT_SYSTEM_SERVICES
1750                                     || componentId == POWER_COMPONENT_WAKELOCK));
1751         }
1752 
1753         for (int componentId = BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID;
1754                 componentId
1755                         < BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
1756                                 + deviceConsumer.getCustomPowerComponentCount();
1757                 componentId++) {
1758             results.add(
1759                     new BatteryEntry(
1760                             context,
1761                             componentId,
1762                             deviceConsumer.getCustomPowerComponentName(componentId),
1763                             deviceConsumer.getConsumedPowerForCustomComponent(componentId)));
1764         }
1765 
1766         final List<UserBatteryConsumer> userBatteryConsumers =
1767                 batteryUsageStats.getUserBatteryConsumers();
1768         for (int i = 0, size = userBatteryConsumers.size(); i < size; i++) {
1769             final UserBatteryConsumer consumer = userBatteryConsumers.get(i);
1770             results.add(
1771                     new BatteryEntry(
1772                             context,
1773                             userManager,
1774                             consumer, /* isHidden */
1775                             true,
1776                             Process.INVALID_UID,
1777                             null,
1778                             null,
1779                             loadDataInBackground));
1780         }
1781 
1782         final int numUidSippers = batteryEntryList.size();
1783 
1784         for (int i = 0; i < numUidSippers; i++) {
1785             results.add(batteryEntryList.valueAt(i));
1786         }
1787 
1788         // The sort order must have changed, so re-sort based on total power use.
1789         results.sort(BatteryEntry.COMPARATOR);
1790         return results;
1791     }
1792 
getRealUid(final UidBatteryConsumer consumer)1793     private static int getRealUid(final UidBatteryConsumer consumer) {
1794         int realUid = consumer.getUid();
1795 
1796         // Check if this UID is a shared GID. If so, we combine it with the OWNER's
1797         // actual app UID.
1798         if (isSharedGid(consumer.getUid())) {
1799             realUid =
1800                     UserHandle.getUid(
1801                             UserHandle.USER_SYSTEM,
1802                             UserHandle.getAppIdFromSharedAppGid(consumer.getUid()));
1803         }
1804 
1805         // Check if this UID is a system UID (mediaserver, logd, nfc, drm, etc).
1806         if (isSystemUid(realUid)
1807                 && !MEDIASERVER_PACKAGE_NAME.equals(consumer.getPackageWithHighestDrain())) {
1808             // Use the system UID for all UIDs running in their own sandbox that
1809             // are not apps. We exclude mediaserver because we already are expected to
1810             // report that as a separate item.
1811             realUid = Process.SYSTEM_UID;
1812         }
1813         return realUid;
1814     }
1815 
isSharedGid(final int uid)1816     private static boolean isSharedGid(final int uid) {
1817         return UserHandle.getAppIdFromSharedAppGid(uid) > 0;
1818     }
1819 
isSystemUid(final int uid)1820     private static boolean isSystemUid(final int uid) {
1821         final int appUid = UserHandle.getAppId(uid);
1822         return appUid >= Process.SYSTEM_UID && appUid < Process.FIRST_APPLICATION_UID;
1823     }
1824 
isUsageMapValid( final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap, final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay)1825     private static boolean isUsageMapValid(
1826             final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap,
1827             final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) {
1828         if (batteryUsageMap.get(SELECTED_INDEX_ALL) == null
1829                 || !batteryUsageMap.get(SELECTED_INDEX_ALL).containsKey(SELECTED_INDEX_ALL)) {
1830             Log.e(TAG, "no [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL] in batteryUsageMap");
1831             return false;
1832         }
1833         for (int dailyIndex = 0; dailyIndex < hourlyBatteryLevelsPerDay.size(); dailyIndex++) {
1834             if (batteryUsageMap.get(dailyIndex) == null
1835                     || !batteryUsageMap.get(dailyIndex).containsKey(SELECTED_INDEX_ALL)) {
1836                 Log.e(
1837                         TAG,
1838                         "no ["
1839                                 + dailyIndex
1840                                 + "][SELECTED_INDEX_ALL] in batteryUsageMap, "
1841                                 + "daily size is: "
1842                                 + hourlyBatteryLevelsPerDay.size());
1843                 return false;
1844             }
1845             if (hourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
1846                 continue;
1847             }
1848             final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
1849             // Length of hourly usage map should be the length of hourly level data - 1.
1850             for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
1851                 if (!batteryUsageMap.get(dailyIndex).containsKey(hourlyIndex)) {
1852                     Log.e(
1853                             TAG,
1854                             "no ["
1855                                     + dailyIndex
1856                                     + "]["
1857                                     + hourlyIndex
1858                                     + "] in batteryUsageMap, "
1859                                     + "hourly size is: "
1860                                     + (timestamps.size() - 1));
1861                     return false;
1862                 }
1863             }
1864         }
1865         return true;
1866     }
1867 
getDiffValue(long v1, long v2)1868     private static long getDiffValue(long v1, long v2) {
1869         return v2 > v1 ? v2 - v1 : 0;
1870     }
1871 
getDiffValue(double v1, double v2)1872     private static double getDiffValue(double v1, double v2) {
1873         return v2 > v1 ? v2 - v1 : 0;
1874     }
1875 
getCurrentTimeMillis()1876     private static long getCurrentTimeMillis() {
1877         return sTestCurrentTimeMillis > 0 ? sTestCurrentTimeMillis : System.currentTimeMillis();
1878     }
1879 
log( Context context, final String content, final long timestamp, final BatteryHistEntry entry)1880     private static void log(
1881             Context context,
1882             final String content,
1883             final long timestamp,
1884             final BatteryHistEntry entry) {
1885         if (sDebug) {
1886             Log.d(
1887                     TAG,
1888                     String.format(
1889                             entry != null ? "%s %s:\n%s" : "%s %s:%s",
1890                             ConvertUtils.utcToLocalTimeForLogging(timestamp),
1891                             content,
1892                             entry));
1893         }
1894     }
1895 }
1896