• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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;
17 
18 import android.app.AppOpsManager;
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.pm.ApplicationInfo;
22 import android.content.pm.InstallSourceInfo;
23 import android.content.pm.PackageInfo;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.os.BatteryManager;
27 import android.os.BatteryStats;
28 import android.os.BatteryStatsManager;
29 import android.os.BatteryUsageStats;
30 import android.os.BatteryUsageStatsQuery;
31 import android.os.Build;
32 import android.os.Process;
33 import android.os.SystemClock;
34 import android.os.UidBatteryConsumer;
35 import android.os.UserHandle;
36 import android.provider.Settings;
37 import android.text.TextUtils;
38 import android.text.format.DateUtils;
39 import android.util.Base64;
40 import android.util.Log;
41 
42 import androidx.annotation.IntDef;
43 import androidx.annotation.Nullable;
44 import androidx.annotation.VisibleForTesting;
45 import androidx.annotation.WorkerThread;
46 
47 import com.android.internal.util.ArrayUtils;
48 import com.android.settings.R;
49 import com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper;
50 import com.android.settings.fuelgauge.batterytip.AnomalyInfo;
51 import com.android.settings.fuelgauge.batterytip.BatteryDatabaseManager;
52 import com.android.settings.fuelgauge.batterytip.StatsManagerConfig;
53 import com.android.settings.overlay.FeatureFactory;
54 import com.android.settingslib.applications.AppUtils;
55 import com.android.settingslib.fuelgauge.Estimate;
56 import com.android.settingslib.fuelgauge.EstimateKt;
57 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
58 import com.android.settingslib.utils.PowerUtil;
59 import com.android.settingslib.utils.StringUtil;
60 import com.android.settingslib.utils.ThreadUtils;
61 
62 import com.google.protobuf.InvalidProtocolBufferException;
63 import com.google.protobuf.MessageLite;
64 
65 import java.lang.annotation.Retention;
66 import java.lang.annotation.RetentionPolicy;
67 import java.time.Instant;
68 import java.time.ZoneId;
69 import java.time.format.DateTimeFormatter;
70 import java.time.format.FormatStyle;
71 import java.util.List;
72 
73 /**
74  * Utils for battery operation
75  */
76 public class BatteryUtils {
77     public static final int UID_NULL = -1;
78     public static final int SDK_NULL = -1;
79     /** Special UID value for data usage by removed apps. */
80     public static final int UID_REMOVED_APPS = -4;
81     /** Special UID value for data usage by tethering. */
82     public static final int UID_TETHERING = -5;
83 
84     /** Flag to check if the dock defender mode has been temporarily bypassed */
85     public static final String SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS = "dock_defender_bypass";
86 
87     public static final String BYPASS_DOCK_DEFENDER_ACTION = "battery.dock.defender.bypass";
88 
89     private static final String GOOGLE_PLAY_STORE_PACKAGE = "com.android.vending";
90 
91     @Retention(RetentionPolicy.SOURCE)
92     @IntDef({StatusType.SCREEN_USAGE,
93             StatusType.FOREGROUND,
94             StatusType.BACKGROUND,
95             StatusType.ALL
96     })
97     public @interface StatusType {
98         int SCREEN_USAGE = 0;
99         int FOREGROUND = 1;
100         int BACKGROUND = 2;
101         int ALL = 3;
102     }
103 
104     @Retention(RetentionPolicy.SOURCE)
105     @IntDef({DockDefenderMode.FUTURE_BYPASS,
106             DockDefenderMode.ACTIVE,
107             DockDefenderMode.TEMPORARILY_BYPASSED,
108             DockDefenderMode.DISABLED})
109     public @interface DockDefenderMode {
110         int FUTURE_BYPASS = 0;
111         int ACTIVE = 1;
112         int TEMPORARILY_BYPASSED = 2;
113         int DISABLED = 3;
114     }
115 
116     private static final String TAG = "BatteryUtils";
117 
118     private static BatteryUtils sInstance;
119     private PackageManager mPackageManager;
120 
121     private AppOpsManager mAppOpsManager;
122     private Context mContext;
123     @VisibleForTesting
124     PowerUsageFeatureProvider mPowerUsageFeatureProvider;
125 
getInstance(Context context)126     public static BatteryUtils getInstance(Context context) {
127         if (sInstance == null || sInstance.isDataCorrupted()) {
128             sInstance = new BatteryUtils(context.getApplicationContext());
129         }
130         return sInstance;
131     }
132 
133     @VisibleForTesting
BatteryUtils(Context context)134     public BatteryUtils(Context context) {
135         mContext = context;
136         mPackageManager = context.getPackageManager();
137         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
138         mPowerUsageFeatureProvider = FeatureFactory.getFactory(context)
139                 .getPowerUsageFeatureProvider(context);
140     }
141 
getProcessTimeMs(@tatusType int type, @Nullable BatteryStats.Uid uid, int which)142     public long getProcessTimeMs(@StatusType int type, @Nullable BatteryStats.Uid uid,
143             int which) {
144         if (uid == null) {
145             return 0;
146         }
147 
148         switch (type) {
149             case StatusType.SCREEN_USAGE:
150                 return getScreenUsageTimeMs(uid, which);
151             case StatusType.FOREGROUND:
152                 return getProcessForegroundTimeMs(uid, which);
153             case StatusType.BACKGROUND:
154                 return getProcessBackgroundTimeMs(uid, which);
155             case StatusType.ALL:
156                 return getProcessForegroundTimeMs(uid, which)
157                         + getProcessBackgroundTimeMs(uid, which);
158         }
159         return 0;
160     }
161 
getScreenUsageTimeMs(BatteryStats.Uid uid, int which, long rawRealTimeUs)162     private long getScreenUsageTimeMs(BatteryStats.Uid uid, int which, long rawRealTimeUs) {
163         final int foregroundTypes[] = {BatteryStats.Uid.PROCESS_STATE_TOP};
164         Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid()));
165 
166         long timeUs = 0;
167         for (int type : foregroundTypes) {
168             final long localTime = uid.getProcessStateTime(type, rawRealTimeUs, which);
169             Log.v(TAG, "type: " + type + " time(us): " + localTime);
170             timeUs += localTime;
171         }
172         Log.v(TAG, "foreground time(us): " + timeUs);
173 
174         // Return the min value of STATE_TOP time and foreground activity time, since both of these
175         // time have some errors
176         return PowerUtil.convertUsToMs(
177                 Math.min(timeUs, getForegroundActivityTotalTimeUs(uid, rawRealTimeUs)));
178     }
179 
getScreenUsageTimeMs(BatteryStats.Uid uid, int which)180     private long getScreenUsageTimeMs(BatteryStats.Uid uid, int which) {
181         final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime());
182         return getScreenUsageTimeMs(uid, which, rawRealTimeUs);
183     }
184 
getProcessBackgroundTimeMs(BatteryStats.Uid uid, int which)185     private long getProcessBackgroundTimeMs(BatteryStats.Uid uid, int which) {
186         final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime());
187         final long timeUs = uid.getProcessStateTime(
188                 BatteryStats.Uid.PROCESS_STATE_BACKGROUND, rawRealTimeUs, which);
189 
190         Log.v(TAG, "package: " + mPackageManager.getNameForUid(uid.getUid()));
191         Log.v(TAG, "background time(us): " + timeUs);
192         return PowerUtil.convertUsToMs(timeUs);
193     }
194 
getProcessForegroundTimeMs(BatteryStats.Uid uid, int which)195     private long getProcessForegroundTimeMs(BatteryStats.Uid uid, int which) {
196         final long rawRealTimeUs = PowerUtil.convertMsToUs(SystemClock.elapsedRealtime());
197         return getScreenUsageTimeMs(uid, which, rawRealTimeUs)
198                 + PowerUtil.convertUsToMs(getForegroundServiceTotalTimeUs(uid, rawRealTimeUs));
199     }
200 
201     /**
202      * Returns true if the specified battery consumer should be excluded from the summary
203      * battery consumption list.
204      */
shouldHideUidBatteryConsumer(UidBatteryConsumer consumer)205     public boolean shouldHideUidBatteryConsumer(UidBatteryConsumer consumer) {
206         return shouldHideUidBatteryConsumer(consumer,
207                 mPackageManager.getPackagesForUid(consumer.getUid()));
208     }
209 
210     /**
211      * Returns true if the specified battery consumer should be excluded from the summary
212      * battery consumption list.
213      */
shouldHideUidBatteryConsumer(UidBatteryConsumer consumer, String[] packages)214     public boolean shouldHideUidBatteryConsumer(UidBatteryConsumer consumer, String[] packages) {
215         return mPowerUsageFeatureProvider.isTypeSystem(consumer.getUid(), packages)
216                 || shouldHideUidBatteryConsumerUnconditionally(consumer, packages);
217     }
218 
219     /**
220      * Returns true if the specified battery consumer should be excluded from
221      * battery consumption lists, either short or full.
222      */
shouldHideUidBatteryConsumerUnconditionally(UidBatteryConsumer consumer, String[] packages)223     public boolean shouldHideUidBatteryConsumerUnconditionally(UidBatteryConsumer consumer,
224             String[] packages) {
225         final int uid = consumer.getUid();
226         return uid == UID_TETHERING
227                 ? false
228                 : uid < 0 || isHiddenSystemModule(packages);
229     }
230 
231     /**
232      * Returns true if one the specified packages belongs to a hidden system module.
233      */
isHiddenSystemModule(String[] packages)234     public boolean isHiddenSystemModule(String[] packages) {
235         if (packages != null) {
236             for (int i = 0, length = packages.length; i < length; i++) {
237                 if (AppUtils.isHiddenSystemModule(mContext, packages[i])) {
238                     return true;
239                 }
240             }
241         }
242         return false;
243     }
244 
245     /**
246      * Calculate the power usage percentage for an app
247      *
248      * @param powerUsageMah   power used by the app
249      * @param totalPowerMah   total power used in the system
250      * @param dischargeAmount The discharge amount calculated by {@link BatteryStats}
251      * @return A percentage value scaled by {@paramref dischargeAmount}
252      * @see BatteryStats#getDischargeAmount(int)
253      */
calculateBatteryPercent(double powerUsageMah, double totalPowerMah, int dischargeAmount)254     public double calculateBatteryPercent(double powerUsageMah, double totalPowerMah,
255             int dischargeAmount) {
256         if (totalPowerMah == 0) {
257             return 0;
258         }
259 
260         return (powerUsageMah / totalPowerMah) * dischargeAmount;
261     }
262 
263     /**
264      * Find the package name for a {@link android.os.BatteryStats.Uid}
265      *
266      * @param uid id to get the package name
267      * @return the package name. If there are multiple packages related to
268      * given id, return the first one. Or return null if there are no known
269      * packages with the given id
270      * @see PackageManager#getPackagesForUid(int)
271      */
getPackageName(int uid)272     public String getPackageName(int uid) {
273         final String[] packageNames = mPackageManager.getPackagesForUid(uid);
274 
275         return ArrayUtils.isEmpty(packageNames) ? null : packageNames[0];
276     }
277 
278     /**
279      * Find the targetSdkVersion for package with name {@code packageName}
280      *
281      * @return the targetSdkVersion, or {@link #SDK_NULL} if {@code packageName} doesn't exist
282      */
getTargetSdkVersion(final String packageName)283     public int getTargetSdkVersion(final String packageName) {
284         try {
285             ApplicationInfo info = mPackageManager.getApplicationInfo(packageName,
286                     PackageManager.GET_META_DATA);
287 
288             return info.targetSdkVersion;
289         } catch (PackageManager.NameNotFoundException e) {
290             Log.e(TAG, "Cannot find package: " + packageName, e);
291         }
292 
293         return SDK_NULL;
294     }
295 
296     /**
297      * Check whether background restriction is enabled
298      */
isBackgroundRestrictionEnabled(final int targetSdkVersion, final int uid, final String packageName)299     public boolean isBackgroundRestrictionEnabled(final int targetSdkVersion, final int uid,
300             final String packageName) {
301         if (targetSdkVersion >= Build.VERSION_CODES.O) {
302             return true;
303         }
304         final int mode = mAppOpsManager
305                 .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName);
306         return mode == AppOpsManager.MODE_IGNORED || mode == AppOpsManager.MODE_ERRORED;
307     }
308 
309     /**
310      * Calculate the time since last full charge, including the device off time
311      *
312      * @param batteryUsageStats  class that contains the data
313      * @param currentTimeMs      current wall time
314      * @return time in millis
315      */
calculateLastFullChargeTime(BatteryUsageStats batteryUsageStats, long currentTimeMs)316     public long calculateLastFullChargeTime(BatteryUsageStats batteryUsageStats,
317             long currentTimeMs) {
318         return currentTimeMs - batteryUsageStats.getStatsStartTimestamp();
319     }
320 
logRuntime(String tag, String message, long startTime)321     public static void logRuntime(String tag, String message, long startTime) {
322         Log.d(tag, message + ": " + (System.currentTimeMillis() - startTime) + "ms");
323     }
324 
325     /**
326      * Return {@code true} if battery defender is on and charging.
327      */
isBatteryDefenderOn(BatteryInfo batteryInfo)328     public static boolean isBatteryDefenderOn(BatteryInfo batteryInfo) {
329         return batteryInfo.isBatteryDefender && !batteryInfo.discharging;
330     }
331 
332     /**
333      * Find package uid from package name
334      *
335      * @param packageName used to find the uid
336      * @return uid for packageName, or {@link #UID_NULL} if exception happens or
337      * {@code packageName} is null
338      */
getPackageUid(String packageName)339     public int getPackageUid(String packageName) {
340         try {
341             return packageName == null ? UID_NULL : mPackageManager.getPackageUid(packageName,
342                     PackageManager.GET_META_DATA);
343         } catch (PackageManager.NameNotFoundException e) {
344             return UID_NULL;
345         }
346     }
347 
348     /**
349      * Parses proto object from string.
350      *
351      * @param serializedProto the serialized proto string
352      * @param protoClass class of the proto
353      * @return instance of the proto class parsed from the string
354      */
355     @SuppressWarnings("unchecked")
parseProtoFromString( String serializedProto, T protoClass)356     public static <T extends MessageLite> T parseProtoFromString(
357             String serializedProto, T protoClass) {
358         if (serializedProto == null || serializedProto.isEmpty()) {
359             return (T) protoClass.getDefaultInstanceForType();
360         }
361         try {
362             return (T) protoClass.getParserForType()
363                     .parseFrom(Base64.decode(serializedProto, Base64.DEFAULT));
364         } catch (InvalidProtocolBufferException e) {
365             Log.e(TAG, "Failed to deserialize proto class", e);
366             return (T) protoClass.getDefaultInstanceForType();
367         }
368     }
369 
setForceAppStandby(int uid, String packageName, int mode)370     public void setForceAppStandby(int uid, String packageName,
371             int mode) {
372         final boolean isPreOApp = isPreOApp(packageName);
373         if (isPreOApp) {
374             // Control whether app could run in the background if it is pre O app
375             mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, uid, packageName, mode);
376         }
377         // Control whether app could run jobs in the background
378         mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName, mode);
379 
380         ThreadUtils.postOnBackgroundThread(() -> {
381             final BatteryDatabaseManager batteryDatabaseManager = BatteryDatabaseManager
382                     .getInstance(mContext);
383             if (mode == AppOpsManager.MODE_IGNORED) {
384                 batteryDatabaseManager.insertAction(AnomalyDatabaseHelper.ActionType.RESTRICTION,
385                         uid, packageName, System.currentTimeMillis());
386             } else if (mode == AppOpsManager.MODE_ALLOWED) {
387                 batteryDatabaseManager.deleteAction(AnomalyDatabaseHelper.ActionType.RESTRICTION,
388                         uid, packageName);
389             }
390         });
391     }
392 
isForceAppStandbyEnabled(int uid, String packageName)393     public boolean isForceAppStandbyEnabled(int uid, String packageName) {
394         return mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid,
395                 packageName) == AppOpsManager.MODE_IGNORED;
396     }
397 
clearForceAppStandby(String packageName)398     public boolean clearForceAppStandby(String packageName) {
399         final int uid = getPackageUid(packageName);
400         if (uid != UID_NULL && isForceAppStandbyEnabled(uid, packageName)) {
401             setForceAppStandby(uid, packageName, AppOpsManager.MODE_ALLOWED);
402             return true;
403         } else {
404             return false;
405         }
406     }
407 
408     @WorkerThread
getBatteryInfo(final String tag)409     public BatteryInfo getBatteryInfo(final String tag) {
410         final BatteryStatsManager systemService = mContext.getSystemService(
411                 BatteryStatsManager.class);
412         BatteryUsageStats batteryUsageStats;
413         try {
414             batteryUsageStats = systemService.getBatteryUsageStats(
415                     new BatteryUsageStatsQuery.Builder().includeBatteryHistory().build());
416         } catch (RuntimeException e) {
417             Log.e(TAG, "getBatteryInfo() error from getBatteryUsageStats()", e);
418             // Use default BatteryUsageStats.
419             batteryUsageStats = new BatteryUsageStats.Builder(new String[0]).build();
420         }
421 
422         final long startTime = System.currentTimeMillis();
423 
424         // Stuff we always need to get BatteryInfo
425         final Intent batteryBroadcast = getBatteryIntent(mContext);
426 
427         final long elapsedRealtimeUs = PowerUtil.convertMsToUs(
428                 SystemClock.elapsedRealtime());
429 
430         BatteryInfo batteryInfo;
431         Estimate estimate = getEnhancedEstimate();
432 
433         // couldn't get estimate from cache or provider, use fallback
434         if (estimate == null) {
435             estimate = new Estimate(
436                     batteryUsageStats.getBatteryTimeRemainingMs(),
437                     false /* isBasedOnUsage */,
438                     EstimateKt.AVERAGE_TIME_TO_DISCHARGE_UNKNOWN);
439         }
440 
441         BatteryUtils.logRuntime(tag, "BatteryInfoLoader post query", startTime);
442         batteryInfo = BatteryInfo.getBatteryInfo(mContext, batteryBroadcast,
443                 batteryUsageStats, estimate, elapsedRealtimeUs, false /* shortString */);
444         BatteryUtils.logRuntime(tag, "BatteryInfoLoader.loadInBackground", startTime);
445 
446         try {
447             batteryUsageStats.close();
448         } catch (Exception e) {
449             Log.e(TAG, "BatteryUsageStats.close() failed", e);
450         }
451         return batteryInfo;
452     }
453 
454     @VisibleForTesting
getEnhancedEstimate()455     Estimate getEnhancedEstimate() {
456         // Align the same logic in the BatteryControllerImpl.updateEstimate()
457         Estimate estimate = Estimate.getCachedEstimateIfAvailable(mContext);
458         if (estimate == null &&
459                 mPowerUsageFeatureProvider != null &&
460                 mPowerUsageFeatureProvider.isEnhancedBatteryPredictionEnabled(mContext)) {
461             estimate = mPowerUsageFeatureProvider.getEnhancedBatteryPrediction(mContext);
462             if (estimate != null) {
463                 Estimate.storeCachedEstimate(mContext, estimate);
464             }
465         }
466         return estimate;
467     }
468 
isDataCorrupted()469     private boolean isDataCorrupted() {
470         return mPackageManager == null || mAppOpsManager == null;
471     }
472 
473     @VisibleForTesting
getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs)474     long getForegroundActivityTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
475         final BatteryStats.Timer timer = uid.getForegroundActivityTimer();
476         if (timer != null) {
477             return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
478         }
479 
480         return 0;
481     }
482 
483     @VisibleForTesting
getForegroundServiceTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs)484     long getForegroundServiceTotalTimeUs(BatteryStats.Uid uid, long rawRealtimeUs) {
485         final BatteryStats.Timer timer = uid.getForegroundServiceTimer();
486         if (timer != null) {
487             return timer.getTotalTimeLocked(rawRealtimeUs, BatteryStats.STATS_SINCE_CHARGED);
488         }
489 
490         return 0;
491     }
492 
isPreOApp(final String packageName)493     public boolean isPreOApp(final String packageName) {
494         try {
495             ApplicationInfo info = mPackageManager.getApplicationInfo(packageName,
496                     PackageManager.GET_META_DATA);
497 
498             return info.targetSdkVersion < Build.VERSION_CODES.O;
499         } catch (PackageManager.NameNotFoundException e) {
500             Log.e(TAG, "Cannot find package: " + packageName, e);
501         }
502 
503         return false;
504     }
505 
isPreOApp(final String[] packageNames)506     public boolean isPreOApp(final String[] packageNames) {
507         if (ArrayUtils.isEmpty(packageNames)) {
508             return false;
509         }
510 
511         for (String packageName : packageNames) {
512             if (isPreOApp(packageName)) {
513                 return true;
514             }
515         }
516 
517         return false;
518     }
519 
520     /**
521      * Return {@code true} if we should hide anomaly app represented by {@code uid}
522      */
shouldHideAnomaly(PowerAllowlistBackend powerAllowlistBackend, int uid, AnomalyInfo anomalyInfo)523     public boolean shouldHideAnomaly(PowerAllowlistBackend powerAllowlistBackend, int uid,
524             AnomalyInfo anomalyInfo) {
525         final String[] packageNames = mPackageManager.getPackagesForUid(uid);
526         if (ArrayUtils.isEmpty(packageNames)) {
527             // Don't show it if app has been uninstalled
528             return true;
529         }
530 
531         return isSystemUid(uid) || powerAllowlistBackend.isAllowlisted(packageNames, uid)
532                 || (isSystemApp(mPackageManager, packageNames) && !hasLauncherEntry(packageNames))
533                 || (isExcessiveBackgroundAnomaly(anomalyInfo) && !isPreOApp(packageNames));
534     }
535 
isExcessiveBackgroundAnomaly(AnomalyInfo anomalyInfo)536     private boolean isExcessiveBackgroundAnomaly(AnomalyInfo anomalyInfo) {
537         return anomalyInfo.anomalyType
538                 == StatsManagerConfig.AnomalyType.EXCESSIVE_BACKGROUND_SERVICE;
539     }
540 
isSystemUid(int uid)541     private boolean isSystemUid(int uid) {
542         final int appUid = UserHandle.getAppId(uid);
543         return appUid >= Process.ROOT_UID && appUid < Process.FIRST_APPLICATION_UID;
544     }
545 
isSystemApp(PackageManager packageManager, String[] packageNames)546     private boolean isSystemApp(PackageManager packageManager, String[] packageNames) {
547         for (String packageName : packageNames) {
548             try {
549                 final ApplicationInfo info = packageManager.getApplicationInfo(packageName,
550                         0 /* flags */);
551                 if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
552                     return true;
553                 }
554             } catch (PackageManager.NameNotFoundException e) {
555                 Log.e(TAG, "Package not found: " + packageName, e);
556             }
557         }
558 
559         return false;
560     }
561 
hasLauncherEntry(String[] packageNames)562     private boolean hasLauncherEntry(String[] packageNames) {
563         final Intent launchIntent = new Intent(Intent.ACTION_MAIN, null);
564         launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
565 
566         // If we do not specify MATCH_DIRECT_BOOT_AWARE or
567         // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags
568         // according to the user's lock state. When the user is locked,
569         // components
570         // with ComponentInfo#directBootAware == false will be filtered. We should
571         // explicitly include both direct boot aware and unaware components here.
572         final List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(launchIntent,
573                 PackageManager.MATCH_DISABLED_COMPONENTS
574                         | PackageManager.MATCH_DIRECT_BOOT_AWARE
575                         | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
576                         | PackageManager.MATCH_SYSTEM_ONLY);
577         for (int i = 0, size = resolveInfos.size(); i < size; i++) {
578             final ResolveInfo resolveInfo = resolveInfos.get(i);
579             if (ArrayUtils.contains(packageNames, resolveInfo.activityInfo.packageName)) {
580                 return true;
581             }
582         }
583 
584         return false;
585     }
586 
587     /**
588      * Return version number of an app represented by {@code packageName}, and return -1 if not
589      * found.
590      */
getAppLongVersionCode(String packageName)591     public long getAppLongVersionCode(String packageName) {
592         try {
593             final PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
594                     0 /* flags */);
595             return packageInfo.getLongVersionCode();
596         } catch (PackageManager.NameNotFoundException e) {
597             Log.e(TAG, "Cannot find package: " + packageName, e);
598         }
599 
600         return -1L;
601     }
602 
603     /** Whether the package is installed from Google Play Store or not */
isAppInstalledFromGooglePlayStore(Context context, String packageName)604     public static boolean isAppInstalledFromGooglePlayStore(Context context, String packageName) {
605         if (TextUtils.isEmpty(packageName)) {
606             return false;
607         }
608         InstallSourceInfo installSourceInfo;
609         try {
610             installSourceInfo = context.getPackageManager().getInstallSourceInfo(packageName);
611         } catch (PackageManager.NameNotFoundException e) {
612             return false;
613         }
614         return installSourceInfo != null
615                 && GOOGLE_PLAY_STORE_PACKAGE.equals(installSourceInfo.getInitiatingPackageName());
616     }
617 
618     /** Gets the latest sticky battery intent from the Android system. */
getBatteryIntent(Context context)619     public static Intent getBatteryIntent(Context context) {
620         return com.android.settingslib.fuelgauge.BatteryUtils.getBatteryIntent(context);
621     }
622 
623     /** Gets the current dock defender mode */
getCurrentDockDefenderMode(Context context, BatteryInfo batteryInfo)624     public static int getCurrentDockDefenderMode(Context context, BatteryInfo batteryInfo) {
625         if (batteryInfo.pluggedStatus == BatteryManager.BATTERY_PLUGGED_DOCK) {
626             if (Settings.Global.getInt(context.getContentResolver(),
627                     SETTINGS_GLOBAL_DOCK_DEFENDER_BYPASS, 0) == 1) {
628                 return DockDefenderMode.TEMPORARILY_BYPASSED;
629             } else if (batteryInfo.isBatteryDefender && FeatureFactory.getFactory(context)
630                     .getPowerUsageFeatureProvider(context)
631                     .isExtraDefend()) {
632                 return DockDefenderMode.ACTIVE;
633             } else if (!batteryInfo.isBatteryDefender) {
634                 return DockDefenderMode.FUTURE_BYPASS;
635             }
636         }
637         return DockDefenderMode.DISABLED;
638     }
639 
640     /** Formats elapsed time without commas in between.  */
formatElapsedTimeWithoutComma( Context context, double millis, boolean withSeconds, boolean collapseTimeUnit)641     public static CharSequence formatElapsedTimeWithoutComma(
642             Context context, double millis, boolean withSeconds, boolean collapseTimeUnit) {
643         return StringUtil.formatElapsedTime(context, millis, withSeconds, collapseTimeUnit)
644                 .toString().replaceAll(",", "");
645     }
646 
647     /** Builds the battery usage time summary. */
buildBatteryUsageTimeSummary(final Context context, final boolean isSystem, final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs, final long screenOnTimeInMs)648     public static String buildBatteryUsageTimeSummary(final Context context, final boolean isSystem,
649             final long foregroundUsageTimeInMs, final long backgroundUsageTimeInMs,
650             final long screenOnTimeInMs) {
651         StringBuilder summary = new StringBuilder();
652         if (isSystem) {
653             final long totalUsageTimeInMs = foregroundUsageTimeInMs + backgroundUsageTimeInMs;
654             if (totalUsageTimeInMs != 0) {
655                 summary.append(buildBatteryUsageTimeInfo(context, totalUsageTimeInMs,
656                         R.string.battery_usage_total_less_than_one_minute,
657                         R.string.battery_usage_for_total_time));
658             }
659         } else {
660             if (screenOnTimeInMs != 0) {
661                 summary.append(buildBatteryUsageTimeInfo(context, screenOnTimeInMs,
662                         R.string.battery_usage_screen_time_less_than_one_minute,
663                         R.string.battery_usage_screen_time));
664             }
665             if (screenOnTimeInMs != 0 && backgroundUsageTimeInMs != 0) {
666                 summary.append('\n');
667             }
668             if (backgroundUsageTimeInMs != 0) {
669                 summary.append(buildBatteryUsageTimeInfo(context, backgroundUsageTimeInMs,
670                         R.string.battery_usage_background_less_than_one_minute,
671                         R.string.battery_usage_for_background_time));
672             }
673         }
674         return summary.toString();
675     }
676     /** Format the date of battery related info */
getBatteryInfoFormattedDate(long dateInMs)677     public static CharSequence getBatteryInfoFormattedDate(long dateInMs) {
678         final Instant instant = Instant.ofEpochMilli(dateInMs);
679         final String localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate().format(
680                 DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG));
681 
682         return localDate;
683     }
684 
685     /** Builds the battery usage time information for one timestamp. */
buildBatteryUsageTimeInfo(final Context context, long timeInMs, final int lessThanOneMinuteResId, final int normalResId)686     private static String buildBatteryUsageTimeInfo(final Context context, long timeInMs,
687             final int lessThanOneMinuteResId, final int normalResId) {
688         if (timeInMs < DateUtils.MINUTE_IN_MILLIS) {
689             return context.getString(lessThanOneMinuteResId);
690         }
691         final CharSequence timeSequence = formatElapsedTimeWithoutComma(
692                 context, (double) timeInMs, /*withSeconds=*/ false, /*collapseTimeUnit=*/ false);
693         return context.getString(normalResId, timeSequence);
694     }
695 }
696