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