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