• 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 package com.android.settings.fuelgauge.batteryusage;
17 
18 import android.content.Context;
19 import android.content.pm.ApplicationInfo;
20 import android.content.pm.PackageManager;
21 import android.content.pm.PackageManager.NameNotFoundException;
22 import android.graphics.drawable.Drawable;
23 import android.os.UserHandle;
24 import android.os.UserManager;
25 import android.util.Log;
26 import android.util.Pair;
27 
28 import androidx.annotation.VisibleForTesting;
29 
30 import com.android.settings.R;
31 import com.android.settings.fuelgauge.BatteryUtils;
32 import com.android.settingslib.utils.StringUtil;
33 
34 import java.util.Comparator;
35 import java.util.HashMap;
36 import java.util.Locale;
37 import java.util.Map;
38 
39 /** A container class to carry battery data in a specific time slot. */
40 public class BatteryDiffEntry {
41     private static final String TAG = "BatteryDiffEntry";
42 
43     static Locale sCurrentLocale = null;
44     // Caches app label and icon to improve loading performance.
45     static final Map<String, BatteryEntry.NameAndIcon> sResourceCache = new HashMap<>();
46     // Whether a specific item is valid to launch restriction page?
47     @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
48     static final Map<String, Boolean> sValidForRestriction = new HashMap<>();
49     /** A comparator for {@link BatteryDiffEntry} based on the sorting key. */
50     static final Comparator<BatteryDiffEntry> COMPARATOR =
51             (a, b) -> Double.compare(b.getSortingKey(), a.getSortingKey());
52     static final String SYSTEM_APPS_KEY = "A|SystemApps";
53     static final String OTHERS_KEY = "S|Others";
54 
55     // key -> (label_id, icon_id)
56     private static final Map<String, Pair<Integer, Integer>> SPECIAL_ENTRY_MAP = Map.of(
57             SYSTEM_APPS_KEY,
58             Pair.create(R.string.battery_usage_system_apps, R.drawable.ic_power_system),
59             OTHERS_KEY,
60             Pair.create(R.string.battery_usage_others,
61                     R.drawable.ic_settings_battery_usage_others));
62 
63     public long mUid;
64     public long mUserId;
65     public String mKey;
66     public boolean mIsHidden;
67     public int mComponentId;
68     public String mLegacyPackageName;
69     public String mLegacyLabel;
70     public int mConsumerType;
71     public long mForegroundUsageTimeInMs;
72     public long mBackgroundUsageTimeInMs;
73     public long mScreenOnTimeInMs;
74     public double mConsumePower;
75     public double mForegroundUsageConsumePower;
76     public double mForegroundServiceUsageConsumePower;
77     public double mBackgroundUsageConsumePower;
78     public double mCachedUsageConsumePower;
79 
80     protected Context mContext;
81 
82     private double mTotalConsumePower;
83     private double mPercentage;
84     private int mAdjustPercentageOffset;
85     private UserManager mUserManager;
86     private String mDefaultPackageName = null;
87 
88     @VisibleForTesting
89     int mAppIconId;
90     @VisibleForTesting
91     String mAppLabel = null;
92     @VisibleForTesting
93     Drawable mAppIcon = null;
94     @VisibleForTesting
95     boolean mIsLoaded = false;
96     @VisibleForTesting
97     boolean mValidForRestriction = true;
98 
BatteryDiffEntry( Context context, long uid, long userId, String key, boolean isHidden, int componentId, String legacyPackageName, String legacyLabel, int consumerType, long foregroundUsageTimeInMs, long backgroundUsageTimeInMs, long screenOnTimeInMs, double consumePower, double foregroundUsageConsumePower, double foregroundServiceUsageConsumePower, double backgroundUsageConsumePower, double cachedUsageConsumePower)99     public BatteryDiffEntry(
100             Context context,
101             long uid,
102             long userId,
103             String key,
104             boolean isHidden,
105             int componentId,
106             String legacyPackageName,
107             String legacyLabel,
108             int consumerType,
109             long foregroundUsageTimeInMs,
110             long backgroundUsageTimeInMs,
111             long screenOnTimeInMs,
112             double consumePower,
113             double foregroundUsageConsumePower,
114             double foregroundServiceUsageConsumePower,
115             double backgroundUsageConsumePower,
116             double cachedUsageConsumePower) {
117         mContext = context;
118         mUid = uid;
119         mUserId = userId;
120         mKey = key;
121         mIsHidden = isHidden;
122         mComponentId = componentId;
123         mLegacyPackageName = legacyPackageName;
124         mLegacyLabel = legacyLabel;
125         mConsumerType = consumerType;
126         mForegroundUsageTimeInMs = foregroundUsageTimeInMs;
127         mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
128         mScreenOnTimeInMs = screenOnTimeInMs;
129         mConsumePower = consumePower;
130         mForegroundUsageConsumePower = foregroundUsageConsumePower;
131         mForegroundServiceUsageConsumePower = foregroundServiceUsageConsumePower;
132         mBackgroundUsageConsumePower = backgroundUsageConsumePower;
133         mCachedUsageConsumePower = cachedUsageConsumePower;
134         mUserManager = context.getSystemService(UserManager.class);
135     }
136 
BatteryDiffEntry(Context context, String key, String legacyLabel, int consumerType)137     public BatteryDiffEntry(Context context, String key, String legacyLabel, int consumerType) {
138         this(context, /*uid=*/ 0, /*userId=*/ 0, key, /*isHidden=*/ false, /*componentId=*/ -1,
139                 /*legacyPackageName=*/ null, legacyLabel, consumerType,
140                 /*foregroundUsageTimeInMs=*/ 0, /*backgroundUsageTimeInMs=*/ 0,
141                 /*screenOnTimeInMs=*/ 0, /*consumePower=*/ 0, /*foregroundUsageConsumePower=*/ 0,
142                 /*foregroundServiceUsageConsumePower=*/ 0, /*backgroundUsageConsumePower=*/ 0,
143                 /*cachedUsageConsumePower=*/ 0);
144     }
145 
146     /** Sets the total consumed power in a specific time slot. */
setTotalConsumePower(double totalConsumePower)147     public void setTotalConsumePower(double totalConsumePower) {
148         mTotalConsumePower = totalConsumePower;
149         mPercentage = totalConsumePower == 0
150                 ? 0 : (mConsumePower / mTotalConsumePower) * 100.0;
151         mAdjustPercentageOffset = 0;
152     }
153 
154     /** Gets the total consumed power in a specific time slot. */
getTotalConsumePower()155     public double getTotalConsumePower() {
156         return mTotalConsumePower;
157     }
158 
159     /** Gets the percentage of total consumed power. */
getPercentage()160     public double getPercentage() {
161         return mPercentage;
162     }
163 
164     /** Gets the percentage offset to adjust. */
getAdjustPercentageOffset()165     public double getAdjustPercentageOffset() {
166         return mAdjustPercentageOffset;
167     }
168 
169     /** Sets the percentage offset to adjust. */
setAdjustPercentageOffset(int offset)170     public void setAdjustPercentageOffset(int offset) {
171         mAdjustPercentageOffset = offset;
172     }
173 
174     /** Gets the key for sorting */
getSortingKey()175     public double getSortingKey() {
176         return getKey() != null && SPECIAL_ENTRY_MAP.containsKey(getKey())
177                 ? -1 : getPercentage() + getAdjustPercentageOffset();
178     }
179 
180     /** Clones a new instance. */
clone()181     public BatteryDiffEntry clone() {
182         return new BatteryDiffEntry(
183                 this.mContext,
184                 this.mUid,
185                 this.mUserId,
186                 this.mKey,
187                 this.mIsHidden,
188                 this.mComponentId,
189                 this.mLegacyPackageName,
190                 this.mLegacyLabel,
191                 this.mConsumerType,
192                 this.mForegroundUsageTimeInMs,
193                 this.mBackgroundUsageTimeInMs,
194                 this.mScreenOnTimeInMs,
195                 this.mConsumePower,
196                 this.mForegroundUsageConsumePower,
197                 this.mForegroundServiceUsageConsumePower,
198                 this.mBackgroundUsageConsumePower,
199                 this.mCachedUsageConsumePower);
200     }
201 
202     /** Gets the app label name for this entry. */
getAppLabel()203     public String getAppLabel() {
204         loadLabelAndIcon();
205         // Returns default application label if we cannot find it.
206         return mAppLabel == null || mAppLabel.length() == 0 ? mLegacyLabel : mAppLabel;
207     }
208 
209     /** Gets the app icon {@link Drawable} for this entry. */
getAppIcon()210     public Drawable getAppIcon() {
211         loadLabelAndIcon();
212         return mAppIcon != null && mAppIcon.getConstantState() != null
213                 ? mAppIcon.getConstantState().newDrawable()
214                 : null;
215     }
216 
217     /** Gets the app icon id for this entry. */
getAppIconId()218     public int getAppIconId() {
219         loadLabelAndIcon();
220         return mAppIconId;
221     }
222 
223     /** Gets the searching package name for UID battery type. */
getPackageName()224     public String getPackageName() {
225         final String packageName = mDefaultPackageName != null
226                 ? mDefaultPackageName : mLegacyPackageName;
227         if (packageName == null) {
228             return packageName;
229         }
230         // Removes potential appended process name in the PackageName.
231         // From "com.opera.browser:privileged_process0" to "com.opera.browser"
232         final String[] splitPackageNames = packageName.split(":");
233         return splitPackageNames != null && splitPackageNames.length > 0
234                 ? splitPackageNames[0] : packageName;
235     }
236 
237     /** Whether this item is valid for users to launch restriction page? */
validForRestriction()238     public boolean validForRestriction() {
239         loadLabelAndIcon();
240         return mValidForRestriction;
241     }
242 
243     /** Whether the current BatteryDiffEntry is system component or not. */
isSystemEntry()244     public boolean isSystemEntry() {
245         if (mIsHidden) {
246             return false;
247         }
248         switch (mConsumerType) {
249             case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
250             case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
251                 return true;
252             case ConvertUtils.CONSUMER_TYPE_UID_BATTERY:
253             default:
254                 return false;
255         }
256     }
257 
loadLabelAndIcon()258     void loadLabelAndIcon() {
259         if (mIsLoaded) {
260             return;
261         }
262         // Checks whether we have cached data or not first before fetching.
263         final BatteryEntry.NameAndIcon nameAndIcon = getCache();
264         if (nameAndIcon != null) {
265             mAppLabel = nameAndIcon.mName;
266             mAppIcon = nameAndIcon.mIcon;
267             mAppIconId = nameAndIcon.mIconId;
268         }
269         final Boolean validForRestriction = sValidForRestriction.get(getKey());
270         if (validForRestriction != null) {
271             mValidForRestriction = validForRestriction;
272         }
273         // Both nameAndIcon and restriction configuration have cached data.
274         if (nameAndIcon != null && validForRestriction != null) {
275             return;
276         }
277         mIsLoaded = true;
278 
279         // Configures whether we can launch restriction page or not.
280         updateRestrictionFlagState();
281         sValidForRestriction.put(getKey(), Boolean.valueOf(mValidForRestriction));
282 
283         if (getKey() != null && SPECIAL_ENTRY_MAP.containsKey(getKey())) {
284             Pair<Integer, Integer> pair = SPECIAL_ENTRY_MAP.get(getKey());
285             mAppLabel = mContext.getString(pair.first);
286             mAppIconId = pair.second;
287             mAppIcon = mContext.getDrawable(mAppIconId);
288             sResourceCache.put(
289                     getKey(),
290                     new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, mAppIconId));
291             return;
292         }
293 
294         // Loads application icon and label based on consumer type.
295         switch (mConsumerType) {
296             case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
297                 final BatteryEntry.NameAndIcon nameAndIconForUser =
298                         BatteryEntry.getNameAndIconFromUserId(mContext, (int) mUserId);
299                 if (nameAndIconForUser != null) {
300                     mAppIcon = nameAndIconForUser.mIcon;
301                     mAppLabel = nameAndIconForUser.mName;
302                     sResourceCache.put(
303                             getKey(),
304                             new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, /*iconId=*/ 0));
305                 }
306                 break;
307             case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
308                 final BatteryEntry.NameAndIcon nameAndIconForSystem =
309                         BatteryEntry.getNameAndIconFromPowerComponent(mContext, mComponentId);
310                 if (nameAndIconForSystem != null) {
311                     mAppLabel = nameAndIconForSystem.mName;
312                     if (nameAndIconForSystem.mIconId != 0) {
313                         mAppIconId = nameAndIconForSystem.mIconId;
314                         mAppIcon = mContext.getDrawable(nameAndIconForSystem.mIconId);
315                     }
316                     sResourceCache.put(
317                             getKey(),
318                             new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, mAppIconId));
319                 }
320                 break;
321             case ConvertUtils.CONSUMER_TYPE_UID_BATTERY:
322                 loadNameAndIconForUid();
323                 // Uses application default icon if we cannot find it from package.
324                 if (mAppIcon == null) {
325                     mAppIcon = mContext.getPackageManager().getDefaultActivityIcon();
326                 }
327                 // Adds badge icon into app icon for work profile.
328                 mAppIcon = getBadgeIconForUser(mAppIcon);
329                 if (mAppLabel != null || mAppIcon != null) {
330                     sResourceCache.put(
331                             getKey(),
332                             new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, /*iconId=*/ 0));
333                 }
334                 break;
335         }
336     }
337 
getKey()338     String getKey() {
339         return mKey;
340     }
341 
342     @VisibleForTesting
updateRestrictionFlagState()343     void updateRestrictionFlagState() {
344         if (isSystemEntry()) {
345             mValidForRestriction = false;
346             return;
347         }
348         final boolean isValidPackage =
349                 BatteryUtils.getInstance(mContext).getPackageUid(getPackageName())
350                         != BatteryUtils.UID_NULL;
351         if (!isValidPackage) {
352             mValidForRestriction = false;
353             return;
354         }
355         try {
356             mValidForRestriction =
357                     mContext.getPackageManager().getPackageInfo(
358                             getPackageName(),
359                             PackageManager.MATCH_DISABLED_COMPONENTS
360                                     | PackageManager.MATCH_ANY_USER
361                                     | PackageManager.GET_SIGNATURES
362                                     | PackageManager.GET_PERMISSIONS)
363                             != null;
364         } catch (Exception e) {
365             Log.e(TAG, String.format("getPackageInfo() error %s for package=%s",
366                     e.getCause(), getPackageName()));
367             mValidForRestriction = false;
368         }
369     }
370 
getCache()371     private BatteryEntry.NameAndIcon getCache() {
372         final Locale locale = Locale.getDefault();
373         if (sCurrentLocale != locale) {
374             Log.d(TAG, String.format("clearCache() locale is changed from %s to %s",
375                     sCurrentLocale, locale));
376             sCurrentLocale = locale;
377             clearCache();
378         }
379         return sResourceCache.get(getKey());
380     }
381 
loadNameAndIconForUid()382     private void loadNameAndIconForUid() {
383         final String packageName = getPackageName();
384         final PackageManager packageManager = mContext.getPackageManager();
385         // Gets the application label from PackageManager.
386         if (packageName != null && packageName.length() != 0) {
387             try {
388                 final ApplicationInfo appInfo =
389                         packageManager.getApplicationInfo(packageName, /*no flags*/ 0);
390                 if (appInfo != null) {
391                     mAppLabel = packageManager.getApplicationLabel(appInfo).toString();
392                     mAppIcon = packageManager.getApplicationIcon(appInfo);
393                 }
394             } catch (NameNotFoundException e) {
395                 Log.e(TAG, "failed to retrieve ApplicationInfo for: " + packageName);
396                 mAppLabel = packageName;
397             }
398         }
399         // Early return if we found the app label and icon resource.
400         if (mAppLabel != null && mAppIcon != null) {
401             return;
402         }
403 
404         final int uid = (int) mUid;
405         final String[] packages = packageManager.getPackagesForUid(uid);
406         // Loads special defined application label and icon if available.
407         if (packages == null || packages.length == 0) {
408             final BatteryEntry.NameAndIcon nameAndIcon =
409                     BatteryEntry.getNameAndIconFromUid(mContext, mAppLabel, uid);
410             mAppLabel = nameAndIcon.mName;
411             mAppIcon = nameAndIcon.mIcon;
412         }
413 
414         final BatteryEntry.NameAndIcon nameAndIcon = BatteryEntry.loadNameAndIcon(
415                 mContext, uid, /*batteryEntry=*/ null, packageName, mAppLabel, mAppIcon);
416         // Clears BatteryEntry internal cache since we will have another one.
417         BatteryEntry.clearUidCache();
418         if (nameAndIcon != null) {
419             mAppLabel = nameAndIcon.mName;
420             mAppIcon = nameAndIcon.mIcon;
421             mDefaultPackageName = nameAndIcon.mPackageName;
422             if (mDefaultPackageName != null
423                     && !mDefaultPackageName.equals(nameAndIcon.mPackageName)) {
424                 Log.w(TAG, String.format("found different package: %s | %s",
425                         mDefaultPackageName, nameAndIcon.mPackageName));
426             }
427         }
428     }
429 
430     @Override
toString()431     public String toString() {
432         final StringBuilder builder = new StringBuilder()
433                 .append("BatteryDiffEntry{")
434                 .append(String.format("\n\tname=%s restrictable=%b",
435                         mAppLabel, mValidForRestriction))
436                 .append(String.format("\n\tconsume=%.2f%% %f/%f",
437                         mPercentage, mConsumePower, mTotalConsumePower))
438                 .append(String.format("\n\tconsume power= foreground:%f foregroundService:%f",
439                         mForegroundUsageConsumePower, mForegroundServiceUsageConsumePower))
440                 .append(String.format("\n\tconsume power= background:%f cached:%f",
441                         mBackgroundUsageConsumePower, mCachedUsageConsumePower))
442                 .append(String.format("\n\ttime= foreground:%s background:%s screen-on:%s",
443                         StringUtil.formatElapsedTime(mContext, (double) mForegroundUsageTimeInMs,
444                                 /*withSeconds=*/ true, /*collapseTimeUnit=*/ false),
445                         StringUtil.formatElapsedTime(mContext, (double) mBackgroundUsageTimeInMs,
446                                 /*withSeconds=*/ true, /*collapseTimeUnit=*/ false),
447                         StringUtil.formatElapsedTime(mContext, (double) mScreenOnTimeInMs,
448                                 /*withSeconds=*/ true, /*collapseTimeUnit=*/ false)))
449                 .append(String.format("\n\tpackage:%s|%s uid:%d userId:%d",
450                         mLegacyPackageName, getPackageName(), mUid, mUserId));
451         return builder.toString();
452     }
453 
454     /** Clears app icon and label cache data. */
clearCache()455     public static void clearCache() {
456         sResourceCache.clear();
457         sValidForRestriction.clear();
458     }
459 
getBadgeIconForUser(Drawable icon)460     private Drawable getBadgeIconForUser(Drawable icon) {
461         final int userId = UserHandle.getUserId((int) mUid);
462         return userId == UserHandle.USER_OWNER ? icon :
463                 mUserManager.getBadgedIconForUser(icon, new UserHandle(userId));
464     }
465 }
466