• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 package com.android.settings.fuelgauge;
15 
16 import android.content.Context;
17 import android.content.pm.ApplicationInfo;
18 import android.content.pm.PackageManager;
19 import android.content.pm.PackageManager.NameNotFoundException;
20 import android.graphics.drawable.Drawable;
21 import android.os.Process;
22 import android.os.UserHandle;
23 import android.os.UserManager;
24 import android.util.Log;
25 
26 import androidx.annotation.VisibleForTesting;
27 
28 import com.android.settingslib.utils.StringUtil;
29 
30 import java.util.Comparator;
31 import java.util.HashMap;
32 import java.util.Locale;
33 import java.util.Map;
34 
35 /** A container class to carry battery data in a specific time slot. */
36 public class BatteryDiffEntry {
37     private static final String TAG = "BatteryDiffEntry";
38 
39     static Locale sCurrentLocale = null;
40     // Caches app label and icon to improve loading performance.
41     static final Map<String, BatteryEntry.NameAndIcon> sResourceCache = new HashMap<>();
42     // Whether a specific item is valid to launch restriction page?
43     static final Map<String, Boolean> sValidForRestriction = new HashMap<>();
44 
45     /** A comparator for {@link BatteryDiffEntry} based on consumed percentage. */
46     public static final Comparator<BatteryDiffEntry> COMPARATOR =
47             (a, b) -> Double.compare(b.getPercentOfTotal(), a.getPercentOfTotal());
48 
49     public long mForegroundUsageTimeInMs;
50     public long mBackgroundUsageTimeInMs;
51     public double mConsumePower;
52     // A BatteryHistEntry corresponding to this diff usage data.
53     public final BatteryHistEntry mBatteryHistEntry;
54     private double mTotalConsumePower;
55     private double mPercentOfTotal;
56 
57     private Context mContext;
58     private UserManager mUserManager;
59     private String mDefaultPackageName = null;
60 
61     @VisibleForTesting int mAppIconId;
62     @VisibleForTesting String mAppLabel = null;
63     @VisibleForTesting Drawable mAppIcon = null;
64     @VisibleForTesting boolean mIsLoaded = false;
65     @VisibleForTesting boolean mValidForRestriction = true;
66 
BatteryDiffEntry( Context context, long foregroundUsageTimeInMs, long backgroundUsageTimeInMs, double consumePower, BatteryHistEntry batteryHistEntry)67     public BatteryDiffEntry(
68             Context context,
69             long foregroundUsageTimeInMs,
70             long backgroundUsageTimeInMs,
71             double consumePower,
72             BatteryHistEntry batteryHistEntry) {
73         mContext = context;
74         mConsumePower = consumePower;
75         mForegroundUsageTimeInMs = foregroundUsageTimeInMs;
76         mBackgroundUsageTimeInMs = backgroundUsageTimeInMs;
77         mBatteryHistEntry = batteryHistEntry;
78         mUserManager = context.getSystemService(UserManager.class);
79     }
80 
81     /** Sets the total consumed power in a specific time slot. */
setTotalConsumePower(double totalConsumePower)82     public void setTotalConsumePower(double totalConsumePower) {
83         mTotalConsumePower = totalConsumePower;
84         mPercentOfTotal = totalConsumePower == 0
85             ? 0 : (mConsumePower / mTotalConsumePower) * 100.0;
86     }
87 
88     /** Gets the percentage of total consumed power. */
getPercentOfTotal()89     public double getPercentOfTotal() {
90         return mPercentOfTotal;
91     }
92 
93     /** Clones a new instance. */
clone()94     public BatteryDiffEntry clone() {
95         return new BatteryDiffEntry(
96             this.mContext,
97             this.mForegroundUsageTimeInMs,
98             this.mBackgroundUsageTimeInMs,
99             this.mConsumePower,
100             this.mBatteryHistEntry /*same instance*/);
101     }
102 
103     /** Gets the app label name for this entry. */
getAppLabel()104     public String getAppLabel() {
105         loadLabelAndIcon();
106         // Returns default applicationn label if we cannot find it.
107         return mAppLabel == null || mAppLabel.length() == 0
108             ? mBatteryHistEntry.mAppLabel
109             : mAppLabel;
110     }
111 
112     /** Gets the app icon {@link Drawable} for this entry. */
getAppIcon()113     public Drawable getAppIcon() {
114         loadLabelAndIcon();
115         return mAppIcon;
116     }
117 
118     /** Gets the app icon id for this entry. */
getAppIconId()119     public int getAppIconId() {
120         loadLabelAndIcon();
121         return mAppIconId;
122     }
123 
124     /** Gets the searching package name for UID battery type. */
getPackageName()125     public String getPackageName() {
126         final String packageName = mDefaultPackageName != null
127             ? mDefaultPackageName : mBatteryHistEntry.mPackageName;
128         if (packageName == null) {
129             return packageName;
130         }
131         // Removes potential appended process name in the PackageName.
132         // From "com.opera.browser:privileged_process0" to "com.opera.browser"
133         final String[] splittedPackageNames = packageName.split(":");
134         return splittedPackageNames != null && splittedPackageNames.length > 0
135             ? splittedPackageNames[0] : packageName;
136     }
137 
138     /** Whether this item is valid for users to launch restriction page? */
validForRestriction()139     public boolean validForRestriction() {
140         loadLabelAndIcon();
141         return mValidForRestriction;
142     }
143 
144     /** Whether the current BatteryDiffEntry is system component or not. */
isSystemEntry()145     public boolean isSystemEntry() {
146         switch (mBatteryHistEntry.mConsumerType) {
147             case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
148             case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
149                 return true;
150             case ConvertUtils.CONSUMER_TYPE_UID_BATTERY:
151                 return isSystemUid((int) mBatteryHistEntry.mUid)
152                     || mBatteryHistEntry.mIsHidden;
153         }
154         return false;
155     }
156 
loadLabelAndIcon()157     void loadLabelAndIcon() {
158         if (mIsLoaded) {
159             return;
160         }
161         // Checks whether we have cached data or not first before fetching.
162         final BatteryEntry.NameAndIcon nameAndIcon = getCache();
163         if (nameAndIcon != null) {
164             mAppLabel = nameAndIcon.name;
165             mAppIcon = nameAndIcon.icon;
166             mAppIconId = nameAndIcon.iconId;
167         }
168         final Boolean validForRestriction = sValidForRestriction.get(getKey());
169         if (validForRestriction != null) {
170             mValidForRestriction = validForRestriction;
171         }
172         // Both nameAndIcon and restriction configuration have cached data.
173         if (nameAndIcon != null && validForRestriction != null) {
174             return;
175         }
176         mIsLoaded = true;
177 
178         // Configures whether we can launch restriction page or not.
179         updateRestrictionFlagState();
180         sValidForRestriction.put(getKey(), Boolean.valueOf(mValidForRestriction));
181 
182         // Loads application icon and label based on consumer type.
183         switch (mBatteryHistEntry.mConsumerType) {
184             case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
185                 final BatteryEntry.NameAndIcon nameAndIconForUser =
186                     BatteryEntry.getNameAndIconFromUserId(
187                         mContext, (int) mBatteryHistEntry.mUserId);
188                 if (nameAndIconForUser != null) {
189                     mAppIcon = nameAndIconForUser.icon;
190                     mAppLabel = nameAndIconForUser.name;
191                     sResourceCache.put(
192                         getKey(),
193                         new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, /*iconId=*/ 0));
194                 }
195                 break;
196             case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
197                 final BatteryEntry.NameAndIcon nameAndIconForSystem =
198                     BatteryEntry.getNameAndIconFromPowerComponent(
199                         mContext, mBatteryHistEntry.mDrainType);
200                 if (nameAndIconForSystem != null) {
201                     mAppLabel = nameAndIconForSystem.name;
202                     if (nameAndIconForSystem.iconId != 0) {
203                         mAppIconId = nameAndIconForSystem.iconId;
204                         mAppIcon = mContext.getDrawable(nameAndIconForSystem.iconId);
205                     }
206                     sResourceCache.put(
207                         getKey(),
208                         new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, mAppIconId));
209                 }
210                 break;
211             case ConvertUtils.CONSUMER_TYPE_UID_BATTERY:
212                 loadNameAndIconForUid();
213                 // Uses application default icon if we cannot find it from package.
214                 if (mAppIcon == null) {
215                     mAppIcon = mContext.getPackageManager().getDefaultActivityIcon();
216                 }
217                 // Adds badge icon into app icon for work profile.
218                 mAppIcon = getBadgeIconForUser(mAppIcon);
219                 if (mAppLabel != null || mAppIcon != null) {
220                     sResourceCache.put(
221                         getKey(),
222                         new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, /*iconId=*/ 0));
223                 }
224                 break;
225         }
226     }
227 
228     @VisibleForTesting
getKey()229     String getKey() {
230         return mBatteryHistEntry.getKey();
231     }
232 
233     @VisibleForTesting
updateRestrictionFlagState()234     void updateRestrictionFlagState() {
235         mValidForRestriction = true;
236         if (!mBatteryHistEntry.isAppEntry()) {
237             return;
238         }
239         final boolean isValidPackage =
240                 BatteryUtils.getInstance(mContext).getPackageUid(getPackageName())
241             != BatteryUtils.UID_NULL;
242         if (!isValidPackage) {
243             mValidForRestriction = false;
244             return;
245         }
246         try {
247             mValidForRestriction =
248                 mContext.getPackageManager().getPackageInfo(
249                     getPackageName(),
250                     PackageManager.MATCH_DISABLED_COMPONENTS
251                         | PackageManager.MATCH_ANY_USER
252                         | PackageManager.GET_SIGNATURES
253                         | PackageManager.GET_PERMISSIONS)
254                 != null;
255         } catch (Exception e) {
256             Log.e(TAG, String.format("getPackageInfo() error %s for package=%s",
257                 e.getCause(), getPackageName()));
258             mValidForRestriction = false;
259         }
260     }
261 
getCache()262     private BatteryEntry.NameAndIcon getCache() {
263         final Locale locale = Locale.getDefault();
264         if (sCurrentLocale != locale) {
265             Log.d(TAG, String.format("clearCache() locale is changed from %s to %s",
266                 sCurrentLocale, locale));
267             sCurrentLocale = locale;
268             clearCache();
269         }
270         return sResourceCache.get(getKey());
271     }
272 
loadNameAndIconForUid()273     private void loadNameAndIconForUid() {
274         final String packageName = getPackageName();
275         final PackageManager packageManager = mContext.getPackageManager();
276         // Gets the application label from PackageManager.
277         if (packageName != null && packageName.length() != 0) {
278             try {
279                 final ApplicationInfo appInfo =
280                     packageManager.getApplicationInfo(packageName, /*no flags*/ 0);
281                 if (appInfo != null) {
282                     mAppLabel = packageManager.getApplicationLabel(appInfo).toString();
283                     mAppIcon = packageManager.getApplicationIcon(appInfo);
284                 }
285             } catch (NameNotFoundException e) {
286                 Log.e(TAG, "failed to retrieve ApplicationInfo for: " + packageName);
287                 mAppLabel = packageName;
288             }
289         }
290         // Early return if we found the app label and icon resource.
291         if (mAppLabel != null && mAppIcon != null) {
292             return;
293         }
294 
295         final int uid = (int) mBatteryHistEntry.mUid;
296         final String[] packages = packageManager.getPackagesForUid(uid);
297         // Loads special defined application label and icon if available.
298         if (packages == null || packages.length == 0) {
299             final BatteryEntry.NameAndIcon nameAndIcon =
300                 BatteryEntry.getNameAndIconFromUid(mContext, mAppLabel, uid);
301             mAppLabel = nameAndIcon.name;
302             mAppIcon = nameAndIcon.icon;
303         }
304 
305         final BatteryEntry.NameAndIcon nameAndIcon =
306             BatteryEntry.loadNameAndIcon(
307                 mContext, uid, /*handler=*/ null, /*batteryEntry=*/ null,
308                 packageName, mAppLabel, mAppIcon);
309         // Clears BatteryEntry internal cache since we will have another one.
310         BatteryEntry.clearUidCache();
311         if (nameAndIcon != null) {
312             mAppLabel = nameAndIcon.name;
313             mAppIcon = nameAndIcon.icon;
314             mDefaultPackageName = nameAndIcon.packageName;
315             if (mDefaultPackageName != null
316                     && !mDefaultPackageName.equals(nameAndIcon.packageName)) {
317                 Log.w(TAG, String.format("found different package: %s | %s",
318                     mDefaultPackageName, nameAndIcon.packageName));
319             }
320         }
321     }
322 
323     @Override
toString()324     public String toString() {
325         final StringBuilder builder = new StringBuilder()
326             .append("BatteryDiffEntry{")
327             .append(String.format("\n\tname=%s restrictable=%b",
328                   mAppLabel, mValidForRestriction))
329             .append(String.format("\n\tconsume=%.2f%% %f/%f",
330                   mPercentOfTotal, mConsumePower, mTotalConsumePower))
331             .append(String.format("\n\tforeground:%s background:%s",
332                   StringUtil.formatElapsedTime(mContext, mForegroundUsageTimeInMs,
333                       /*withSeconds=*/ true, /*collapseTimeUnit=*/ false),
334                   StringUtil.formatElapsedTime(mContext, mBackgroundUsageTimeInMs,
335                       /*withSeconds=*/ true, /*collapseTimeUnit=*/ false)))
336             .append(String.format("\n\tpackage:%s|%s uid:%d userId:%d",
337                   mBatteryHistEntry.mPackageName, getPackageName(),
338                   mBatteryHistEntry.mUid, mBatteryHistEntry.mUserId));
339         return builder.toString();
340     }
341 
clearCache()342     static void clearCache() {
343         sResourceCache.clear();
344         sValidForRestriction.clear();
345     }
346 
getBadgeIconForUser(Drawable icon)347     private Drawable getBadgeIconForUser(Drawable icon) {
348         final int userId = UserHandle.getUserId((int) mBatteryHistEntry.mUid);
349         final UserHandle userHandle = new UserHandle(userId);
350         return mUserManager.getBadgedIconForUser(icon, userHandle);
351     }
352 
isSystemUid(int uid)353     private static boolean isSystemUid(int uid) {
354         final int appUid = UserHandle.getAppId(uid);
355         return appUid >= Process.SYSTEM_UID && appUid < Process.FIRST_APPLICATION_UID;
356     }
357 }
358