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.utcToLocalTimeForLogging;
20 
21 import android.content.Context;
22 import android.os.BatteryConsumer;
23 
24 import androidx.annotation.NonNull;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
28 import com.android.settings.overlay.FeatureFactory;
29 
30 import java.util.Collections;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Set;
34 
35 /** Wraps the battery usage diff data for each entry used for battery usage app list. */
36 public class BatteryDiffData {
37     static final double SMALL_PERCENTAGE_THRESHOLD = 1f;
38 
39     private final long mStartTimestamp;
40     private final long mEndTimestamp;
41     private final int mStartBatteryLevel;
42     private final int mEndBatteryLevel;
43     private final long mScreenOnTime;
44     private final List<BatteryDiffEntry> mAppEntries;
45     private final List<BatteryDiffEntry> mSystemEntries;
46 
47     /** Constructor for the diff entries. */
BatteryDiffData( final Context context, final long startTimestamp, final long endTimestamp, final int startBatteryLevel, final int endBatteryLevel, final long screenOnTime, final @NonNull List<BatteryDiffEntry> appDiffEntries, final @NonNull List<BatteryDiffEntry> systemDiffEntries, final @NonNull Set<String> systemAppsPackageNames, final @NonNull Set<Integer> systemAppsUids, final boolean isAccumulated)48     public BatteryDiffData(
49             final Context context,
50             final long startTimestamp,
51             final long endTimestamp,
52             final int startBatteryLevel,
53             final int endBatteryLevel,
54             final long screenOnTime,
55             final @NonNull List<BatteryDiffEntry> appDiffEntries,
56             final @NonNull List<BatteryDiffEntry> systemDiffEntries,
57             final @NonNull Set<String> systemAppsPackageNames,
58             final @NonNull Set<Integer> systemAppsUids,
59             final boolean isAccumulated) {
60         mStartTimestamp = startTimestamp;
61         mEndTimestamp = endTimestamp;
62         mStartBatteryLevel = startBatteryLevel;
63         mEndBatteryLevel = endBatteryLevel;
64         mScreenOnTime = screenOnTime;
65         mAppEntries = appDiffEntries;
66         mSystemEntries = systemDiffEntries;
67 
68         if (!isAccumulated) {
69             final PowerUsageFeatureProvider featureProvider =
70                     FeatureFactory.getFeatureFactory().getPowerUsageFeatureProvider();
71             purgeBatteryDiffData(featureProvider);
72             combineBatteryDiffEntry(
73                     context, featureProvider, systemAppsPackageNames, systemAppsUids);
74         }
75 
76         processAndSortEntries(mAppEntries);
77         processAndSortEntries(mSystemEntries);
78     }
79 
80     /** Gets the start timestamp. */
getStartTimestamp()81     public long getStartTimestamp() {
82         return mStartTimestamp;
83     }
84 
85     /** Gets the end timestamp. */
getEndTimestamp()86     public long getEndTimestamp() {
87         return mEndTimestamp;
88     }
89 
getStartBatteryLevel()90     int getStartBatteryLevel() {
91         return mStartBatteryLevel;
92     }
93 
getEndBatteryLevel()94     int getEndBatteryLevel() {
95         return mEndBatteryLevel;
96     }
97 
getScreenOnTime()98     long getScreenOnTime() {
99         return mScreenOnTime;
100     }
101 
102     /** Gets the {@link BatteryDiffEntry} list for apps. */
getAppDiffEntryList()103     public List<BatteryDiffEntry> getAppDiffEntryList() {
104         return mAppEntries;
105     }
106 
getSystemDiffEntryList()107     List<BatteryDiffEntry> getSystemDiffEntryList() {
108         return mSystemEntries;
109     }
110 
111     @Override
toString()112     public String toString() {
113         return new StringBuilder("BatteryDiffData{")
114                 .append("startTimestamp:" + utcToLocalTimeForLogging(mStartTimestamp))
115                 .append("|endTimestamp:" + utcToLocalTimeForLogging(mEndTimestamp))
116                 .append("|startLevel:" + mStartBatteryLevel)
117                 .append("|endLevel:" + mEndBatteryLevel)
118                 .append("|screenOnTime:" + mScreenOnTime)
119                 .append("|appEntries.size:" + mAppEntries.size())
120                 .append("|systemEntries.size:" + mSystemEntries.size())
121                 .append("}")
122                 .toString();
123     }
124 
125     /** Removes fake usage data and hidden packages. */
purgeBatteryDiffData(final PowerUsageFeatureProvider featureProvider)126     private void purgeBatteryDiffData(final PowerUsageFeatureProvider featureProvider) {
127         purgeBatteryDiffData(featureProvider, mAppEntries);
128         purgeBatteryDiffData(featureProvider, mSystemEntries);
129     }
130 
131     /** Combines into SystemAppsBatteryDiffEntry and OthersBatteryDiffEntry. */
combineBatteryDiffEntry( final Context context, final PowerUsageFeatureProvider featureProvider, final @NonNull Set<String> systemAppsPackageNames, final @NonNull Set<Integer> systemAppsUids)132     private void combineBatteryDiffEntry(
133             final Context context,
134             final PowerUsageFeatureProvider featureProvider,
135             final @NonNull Set<String> systemAppsPackageNames,
136             final @NonNull Set<Integer> systemAppsUids) {
137         combineIntoUninstalledApps(context, mAppEntries);
138         combineIntoSystemApps(
139                 context, featureProvider, systemAppsPackageNames, systemAppsUids, mAppEntries);
140         combineSystemItemsIntoOthers(context, featureProvider, mSystemEntries);
141     }
142 
purgeBatteryDiffData( final PowerUsageFeatureProvider featureProvider, final List<BatteryDiffEntry> entries)143     private static void purgeBatteryDiffData(
144             final PowerUsageFeatureProvider featureProvider, final List<BatteryDiffEntry> entries) {
145         final double screenOnTimeThresholdInMs =
146                 featureProvider.getBatteryUsageListScreenOnTimeThresholdInMs();
147         final double consumePowerThreshold =
148                 featureProvider.getBatteryUsageListConsumePowerThreshold();
149         final Set<Integer> hideSystemComponentSet = featureProvider.getHideSystemComponentSet();
150         final Set<String> hideBackgroundUsageTimeSet =
151                 featureProvider.getHideBackgroundUsageTimeSet();
152         final Set<String> hideApplicationSet = featureProvider.getHideApplicationSet();
153         final Iterator<BatteryDiffEntry> iterator = entries.iterator();
154         while (iterator.hasNext()) {
155             final BatteryDiffEntry entry = iterator.next();
156             final long screenOnTimeInMs =
157                     entry.isSystemEntry()
158                             ? entry.mForegroundUsageTimeInMs
159                             : entry.mScreenOnTimeInMs;
160             final double comsumePower = entry.mConsumePower;
161             final String packageName = entry.getPackageName();
162             final Integer componentId = entry.mComponentId;
163             if ((screenOnTimeInMs < screenOnTimeThresholdInMs
164                             && comsumePower < consumePowerThreshold)
165                     || ConvertUtils.FAKE_PACKAGE_NAME.equals(packageName)
166                     || hideSystemComponentSet.contains(componentId)
167                     || (packageName != null && hideApplicationSet.contains(packageName))) {
168                 iterator.remove();
169             }
170             if (packageName != null && hideBackgroundUsageTimeSet.contains(packageName)) {
171                 entry.mBackgroundUsageTimeInMs = 0;
172             }
173         }
174     }
175 
combineIntoSystemApps( final Context context, final PowerUsageFeatureProvider featureProvider, final @NonNull Set<String> systemAppsPackageNames, final @NonNull Set<Integer> systemAppsUids, final @NonNull List<BatteryDiffEntry> appEntries)176     private static void combineIntoSystemApps(
177             final Context context,
178             final PowerUsageFeatureProvider featureProvider,
179             final @NonNull Set<String> systemAppsPackageNames,
180             final @NonNull Set<Integer> systemAppsUids,
181             final @NonNull List<BatteryDiffEntry> appEntries) {
182         final List<String> systemAppsAllowlist = featureProvider.getSystemAppsAllowlist();
183         BatteryDiffEntry systemAppsDiffEntry = null;
184         final Iterator<BatteryDiffEntry> appListIterator = appEntries.iterator();
185         while (appListIterator.hasNext()) {
186             final BatteryDiffEntry batteryDiffEntry = appListIterator.next();
187             if (needsCombineInSystemApp(
188                     batteryDiffEntry,
189                     systemAppsAllowlist,
190                     systemAppsPackageNames,
191                     systemAppsUids)) {
192                 if (systemAppsDiffEntry == null) {
193                     systemAppsDiffEntry =
194                             new BatteryDiffEntry(
195                                     context,
196                                     BatteryDiffEntry.SYSTEM_APPS_KEY,
197                                     BatteryDiffEntry.SYSTEM_APPS_KEY,
198                                     ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
199                 }
200                 systemAppsDiffEntry.mConsumePower += batteryDiffEntry.mConsumePower;
201                 systemAppsDiffEntry.mForegroundUsageTimeInMs +=
202                         batteryDiffEntry.mForegroundUsageTimeInMs;
203                 systemAppsDiffEntry.setTotalConsumePower(batteryDiffEntry.getTotalConsumePower());
204                 appListIterator.remove();
205             }
206         }
207         if (systemAppsDiffEntry != null) {
208             appEntries.add(systemAppsDiffEntry);
209         }
210     }
211 
combineIntoUninstalledApps( final Context context, final @NonNull List<BatteryDiffEntry> appEntries)212     private static void combineIntoUninstalledApps(
213             final Context context, final @NonNull List<BatteryDiffEntry> appEntries) {
214         BatteryDiffEntry uninstalledAppDiffEntry = null;
215         final Iterator<BatteryDiffEntry> appListIterator = appEntries.iterator();
216         while (appListIterator.hasNext()) {
217             final BatteryDiffEntry batteryDiffEntry = appListIterator.next();
218             if (!batteryDiffEntry.isUninstalledEntry()) {
219                 continue;
220             }
221 
222             if (uninstalledAppDiffEntry == null) {
223                 uninstalledAppDiffEntry =
224                         new BatteryDiffEntry(
225                                 context,
226                                 BatteryDiffEntry.UNINSTALLED_APPS_KEY,
227                                 BatteryDiffEntry.UNINSTALLED_APPS_KEY,
228                                 ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
229             }
230             uninstalledAppDiffEntry.mConsumePower += batteryDiffEntry.mConsumePower;
231             uninstalledAppDiffEntry.mForegroundUsageTimeInMs +=
232                     batteryDiffEntry.mForegroundUsageTimeInMs;
233             uninstalledAppDiffEntry.setTotalConsumePower(batteryDiffEntry.getTotalConsumePower());
234             appListIterator.remove();
235         }
236         if (uninstalledAppDiffEntry != null) {
237             appEntries.add(uninstalledAppDiffEntry);
238         }
239     }
240 
combineSystemItemsIntoOthers( final Context context, final PowerUsageFeatureProvider featureProvider, final List<BatteryDiffEntry> systemEntries)241     private static void combineSystemItemsIntoOthers(
242             final Context context,
243             final PowerUsageFeatureProvider featureProvider,
244             final List<BatteryDiffEntry> systemEntries) {
245         final Set<Integer> othersSystemComponentSet = featureProvider.getOthersSystemComponentSet();
246         final Set<String> othersCustomComponentNameSet =
247                 featureProvider.getOthersCustomComponentNameSet();
248         BatteryDiffEntry othersDiffEntry = null;
249         final Iterator<BatteryDiffEntry> systemListIterator = systemEntries.iterator();
250         while (systemListIterator.hasNext()) {
251             final BatteryDiffEntry batteryDiffEntry = systemListIterator.next();
252             final int componentId = batteryDiffEntry.mComponentId;
253             if (othersSystemComponentSet.contains(componentId)
254                     || (componentId >= BatteryConsumer.FIRST_CUSTOM_POWER_COMPONENT_ID
255                             && othersCustomComponentNameSet.contains(
256                                     batteryDiffEntry.getAppLabel()))) {
257                 if (othersDiffEntry == null) {
258                     othersDiffEntry =
259                             new BatteryDiffEntry(
260                                     context,
261                                     BatteryDiffEntry.OTHERS_KEY,
262                                     BatteryDiffEntry.OTHERS_KEY,
263                                     ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
264                 }
265                 othersDiffEntry.mConsumePower += batteryDiffEntry.mConsumePower;
266                 othersDiffEntry.setTotalConsumePower(batteryDiffEntry.getTotalConsumePower());
267                 systemListIterator.remove();
268             }
269         }
270         if (othersDiffEntry != null) {
271             systemEntries.add(othersDiffEntry);
272         }
273     }
274 
275     @VisibleForTesting
needsCombineInSystemApp( final BatteryDiffEntry batteryDiffEntry, final @NonNull List<String> systemAppsAllowlist, final @NonNull Set<String> systemAppsPackageNames, final @NonNull Set<Integer> systemAppsUids)276     static boolean needsCombineInSystemApp(
277             final BatteryDiffEntry batteryDiffEntry,
278             final @NonNull List<String> systemAppsAllowlist,
279             final @NonNull Set<String> systemAppsPackageNames,
280             final @NonNull Set<Integer> systemAppsUids) {
281         if (batteryDiffEntry.mIsHidden) {
282             return true;
283         }
284 
285         final String packageName = batteryDiffEntry.getPackageName();
286         if (packageName == null || packageName.isEmpty()) {
287             return false;
288         }
289 
290         if (systemAppsAllowlist.contains(packageName)) {
291             return true;
292         }
293 
294         int uid = (int) batteryDiffEntry.mUid;
295         return systemAppsPackageNames.contains(packageName) || systemAppsUids.contains(uid);
296     }
297 
298     /**
299      * Sets total consume power, and adjusts the percentages to ensure the total round percentage
300      * could be 100%, and then sorts entries based on the sorting key.
301      */
processAndSortEntries(final List<BatteryDiffEntry> batteryDiffEntries)302     public static void processAndSortEntries(final List<BatteryDiffEntry> batteryDiffEntries) {
303         if (batteryDiffEntries.isEmpty()) {
304             return;
305         }
306 
307         // Sets total consume power.
308         double totalConsumePower = 0.0;
309         for (BatteryDiffEntry batteryDiffEntry : batteryDiffEntries) {
310             totalConsumePower += batteryDiffEntry.mConsumePower;
311         }
312         for (BatteryDiffEntry batteryDiffEntry : batteryDiffEntries) {
313             batteryDiffEntry.setTotalConsumePower(totalConsumePower);
314         }
315 
316         // Adjusts percentages to show.
317         // The lower bound is treating all the small percentages as 0.
318         // The upper bound is treating all the small percentages as 1.
319         int totalLowerBound = 0;
320         int totalUpperBound = 0;
321         for (BatteryDiffEntry entry : batteryDiffEntries) {
322             if (entry.getPercentage() < SMALL_PERCENTAGE_THRESHOLD) {
323                 totalUpperBound += 1;
324             } else {
325                 int roundPercentage = Math.round((float) entry.getPercentage());
326                 totalLowerBound += roundPercentage;
327                 totalUpperBound += roundPercentage;
328             }
329         }
330         if (totalLowerBound > 100 || totalUpperBound < 100) {
331             Collections.sort(batteryDiffEntries, BatteryDiffEntry.COMPARATOR);
332             for (int i = 0; i < totalLowerBound - 100 && i < batteryDiffEntries.size(); i++) {
333                 batteryDiffEntries.get(i).setAdjustPercentageOffset(-1);
334             }
335             for (int i = 0; i < 100 - totalUpperBound && i < batteryDiffEntries.size(); i++) {
336                 batteryDiffEntries.get(i).setAdjustPercentageOffset(1);
337             }
338         }
339 
340         // Sorts entries.
341         Collections.sort(batteryDiffEntries, BatteryDiffEntry.COMPARATOR);
342     }
343 }
344