1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settingslib.fuelgauge;
18 
19 import static android.os.BatteryManager.BATTERY_STATUS_FULL;
20 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
21 import static android.os.BatteryManager.CHARGING_POLICY_ADAPTIVE_LONGLIFE;
22 import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
23 import static android.os.BatteryManager.EXTRA_CHARGING_STATUS;
24 import static android.os.BatteryManager.EXTRA_MAX_CHARGING_CURRENT;
25 import static android.os.BatteryManager.EXTRA_MAX_CHARGING_VOLTAGE;
26 import static android.os.BatteryManager.EXTRA_PLUGGED;
27 import static android.os.BatteryManager.EXTRA_PRESENT;
28 import static android.os.BatteryManager.EXTRA_STATUS;
29 import static android.os.OsProtoEnums.BATTERY_PLUGGED_NONE;
30 
31 import android.content.Context;
32 import android.content.Intent;
33 import android.os.BatteryManager;
34 
35 import com.android.settingslib.R;
36 
37 import java.util.Optional;
38 
39 /**
40  * Stores and computes some battery information.
41  */
42 public class BatteryStatus {
43 
44     private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
45 
46     public static final int BATTERY_LEVEL_UNKNOWN = -1;
47     public static final int CHARGING_UNKNOWN = -1;
48     public static final int CHARGING_SLOWLY = 0;
49     public static final int CHARGING_REGULAR = 1;
50     public static final int CHARGING_FAST = 2;
51     public static final int LOW_BATTERY_THRESHOLD = 20;
52     public static final int SEVERE_LOW_BATTERY_THRESHOLD = 10;
53     public static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
54 
55     public final int status;
56     public final int level;
57     public final int plugged;
58     public final int chargingStatus;
59     public final int maxChargingWattage;
60     public final boolean present;
61     public final Optional<Boolean> incompatibleCharger;
62 
create(Context context, boolean incompatibleCharger)63     public static BatteryStatus create(Context context, boolean incompatibleCharger) {
64         final Intent batteryChangedIntent = BatteryUtils.getBatteryIntent(context);
65         return batteryChangedIntent == null
66                 ? null : new BatteryStatus(batteryChangedIntent, incompatibleCharger);
67     }
68 
BatteryStatus(int status, int level, int plugged, int chargingStatus, int maxChargingWattage, boolean present)69     public BatteryStatus(int status, int level, int plugged, int chargingStatus,
70             int maxChargingWattage, boolean present) {
71         this.status = status;
72         this.level = level;
73         this.plugged = plugged;
74         this.chargingStatus = chargingStatus;
75         this.maxChargingWattage = maxChargingWattage;
76         this.present = present;
77         this.incompatibleCharger = Optional.empty();
78     }
79 
80 
BatteryStatus(Intent batteryChangedIntent)81     public BatteryStatus(Intent batteryChangedIntent) {
82         this(batteryChangedIntent, Optional.empty());
83     }
84 
BatteryStatus(Intent batteryChangedIntent, boolean incompatibleCharger)85     public BatteryStatus(Intent batteryChangedIntent, boolean incompatibleCharger) {
86         this(batteryChangedIntent, Optional.of(incompatibleCharger));
87     }
88 
BatteryStatus(Intent batteryChangedIntent, Optional<Boolean> incompatibleCharger)89     private BatteryStatus(Intent batteryChangedIntent, Optional<Boolean> incompatibleCharger) {
90         status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
91         plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
92         level = getBatteryLevel(batteryChangedIntent);
93         chargingStatus = batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS,
94                 CHARGING_POLICY_DEFAULT);
95         present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
96         this.incompatibleCharger = incompatibleCharger;
97 
98         maxChargingWattage = calculateMaxChargingMicroWatt(batteryChangedIntent);
99     }
100 
101     /** Determine whether the device is plugged. */
isPluggedIn()102     public boolean isPluggedIn() {
103         return isPluggedIn(plugged);
104     }
105 
106     /** Determine whether the device is plugged in (USB, power). */
isPluggedInWired()107     public boolean isPluggedInWired() {
108         return plugged == BatteryManager.BATTERY_PLUGGED_AC
109                 || plugged == BatteryManager.BATTERY_PLUGGED_USB;
110     }
111 
112     /**
113      * Determine whether the device is plugged in wireless.
114      */
isPluggedInWireless()115     public boolean isPluggedInWireless() {
116         return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
117     }
118 
119     /** Determine whether the device is plugged in dock. */
isPluggedInDock()120     public boolean isPluggedInDock() {
121         return isPluggedInDock(plugged);
122     }
123 
124     /**
125      * Whether or not the device is charged. Note that some devices never return 100% for
126      * battery level, so this allows either battery level or status to determine if the
127      * battery is charged.
128      */
isCharged()129     public boolean isCharged() {
130         return isCharged(status, level);
131     }
132 
133     /** Whether battery is low and needs to be charged. */
isBatteryLow()134     public boolean isBatteryLow() {
135         return isLowBattery(level);
136     }
137 
138     /** Whether battery defender is enabled. */
isBatteryDefender()139     public boolean isBatteryDefender() {
140         return isBatteryDefender(chargingStatus);
141     }
142 
143     /** Return current charging speed is fast, slow or normal. */
getChargingSpeed(Context context)144     public final int getChargingSpeed(Context context) {
145         final int slowThreshold = context.getResources().getInteger(
146                 R.integer.config_chargingSlowlyThreshold);
147         final int fastThreshold = context.getResources().getInteger(
148                 getFastChargingThresholdResId());
149 
150         return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
151                 maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
152                         maxChargingWattage > fastThreshold ? CHARGING_FAST :
153                                 CHARGING_REGULAR;
154     }
155 
156     @Override
toString()157     public String toString() {
158         return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged
159                 + ",chargingStatus=" + chargingStatus + ",maxChargingWattage=" + maxChargingWattage
160                 + "}";
161     }
162 
163     /**
164      * Whether or not the device is charged. Note that some devices never return 100% for
165      * battery level, so this allows either battery level or status to determine if the
166      * battery is charged.
167      *
168      * @param batteryChangedIntent ACTION_BATTERY_CHANGED intent
169      * @return true if the device is charged
170      */
isCharged(Intent batteryChangedIntent)171     public static boolean isCharged(Intent batteryChangedIntent) {
172         int status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
173         int level = getBatteryLevel(batteryChangedIntent);
174         return isCharged(status, level);
175     }
176 
177     /**
178      * Whether or not the device is charged. Note that some devices never return 100% for
179      * battery level, so this allows either battery level or status to determine if the
180      * battery is charged.
181      *
182      * @param status values for "status" field in the ACTION_BATTERY_CHANGED Intent
183      * @param level values from 0 to 100
184      * @return true if the device is charged
185      */
isCharged(int status, int level)186     public static boolean isCharged(int status, int level) {
187         return status == BATTERY_STATUS_FULL || level >= 100;
188     }
189 
190     /**
191      * Whether or not the device is charged. Note that some devices never return 100% for battery
192      * level, so this allows either battery level or status to determine if the battery is charged.
193      *
194      * @param status the value from extra {@link BatteryManager.EXTRA_STATUS} of
195      *     {@link Intent.ACTION_BATTERY_CHANGED} intent
196      * @param level the value from extra {@link BatteryManager.EXTRA_LEVEL} of
197      *     {@link Intent.ACTION_BATTERY_CHANGED} intent
198      * @param scale the value from extra {@link BatteryManager.EXTRA_SCALE} of
199      *     {@link Intent.ACTION_BATTERY_CHANGED} intent
200      */
isCharged(int status, int level, int scale)201     public static boolean isCharged(int status, int level, int scale) {
202         var batteryLevel = getBatteryLevel(level, scale);
203         return isCharged(status, batteryLevel);
204     }
205 
206     /** Gets the battery level from the intent. */
getBatteryLevel(Intent batteryChangedIntent)207     public static int getBatteryLevel(Intent batteryChangedIntent) {
208         if (batteryChangedIntent == null) {
209             return BATTERY_LEVEL_UNKNOWN;
210         }
211         final int level =
212                 batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN);
213         final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
214         return getBatteryLevel(level, scale);
215     }
216 
217     /**
218      * Gets the battery level from the value of {@link Intent.BATTERY_CHANGED_INTENT}'s EXTRA_LEVEL
219      * and EXTRA_SCALE.
220      */
getBatteryLevel(int level, int scale)221     public static int getBatteryLevel(int level, int scale) {
222         return scale == 0
223                 ? BATTERY_LEVEL_UNKNOWN
224                 : Math.round((level / (float) scale) * 100f);
225     }
226 
227     /** Returns the plugged type from {@code batteryChangedIntent}. */
getPluggedType(Intent batteryChangedIntent)228     public static int getPluggedType(Intent batteryChangedIntent) {
229         return batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
230     }
231 
232     /** Whether the device is plugged or not. */
isPluggedIn(Intent batteryChangedIntent)233     public static boolean isPluggedIn(Intent batteryChangedIntent) {
234         return isPluggedIn(getPluggedType(batteryChangedIntent));
235     }
236 
237     /** Whether the device is plugged or not. */
isPluggedIn(int plugged)238     public static boolean isPluggedIn(int plugged) {
239         return plugged == BatteryManager.BATTERY_PLUGGED_AC
240                 || plugged == BatteryManager.BATTERY_PLUGGED_USB
241                 || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
242                 || plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
243     }
244 
245     /** Determine whether the device is plugged in dock. */
isPluggedInDock(Intent batteryChangedIntent)246     public static boolean isPluggedInDock(Intent batteryChangedIntent) {
247         return isPluggedInDock(
248                 batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, BATTERY_PLUGGED_NONE));
249     }
250 
251     /** Determine whether the device is plugged in dock. */
isPluggedInDock(int plugged)252     public static boolean isPluggedInDock(int plugged) {
253         return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
254     }
255 
256     /**
257      * Whether the battery is low or not.
258      *
259      * @param batteryChangedIntent the {@link ACTION_BATTERY_CHANGED} intent
260      * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
261      */
isLowBattery(Intent batteryChangedIntent)262     public static boolean isLowBattery(Intent batteryChangedIntent) {
263         int level = getBatteryLevel(batteryChangedIntent);
264         return isLowBattery(level);
265     }
266 
267     /**
268      * Whether the battery is low or not.
269      *
270      * @param batteryLevel the battery level
271      * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
272      */
isLowBattery(int batteryLevel)273     public static boolean isLowBattery(int batteryLevel) {
274         return batteryLevel <= LOW_BATTERY_THRESHOLD;
275     }
276 
277     /**
278      * Whether the battery is severe low or not.
279      *
280      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
281      * @return {@code true} if the battery level is less or equal to {@link
282      * SEVERE_LOW_BATTERY_THRESHOLD}
283      */
isSevereLowBattery(Intent batteryChangedIntent)284     public static boolean isSevereLowBattery(Intent batteryChangedIntent) {
285         int batteryLevel = getBatteryLevel(batteryChangedIntent);
286         return isSevereLowBattery(batteryLevel);
287     }
288 
289     /**
290      * Whether the battery is severe low or not.
291      *
292      * @param batteryLevel the value of battery level
293      * @return {@code true} if the battery level is less or equal to {@link
294      * SEVERE_LOW_BATTERY_THRESHOLD}
295      */
isSevereLowBattery(int batteryLevel)296     public static boolean isSevereLowBattery(int batteryLevel) {
297         return batteryLevel <= SEVERE_LOW_BATTERY_THRESHOLD;
298     }
299 
300     /**
301      * Whether the battery is extreme low or not.
302      *
303      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
304      * @return {@code true} if the battery level is less or equal to {@link
305      * EXTREME_LOW_BATTERY_THRESHOLD}
306      */
isExtremeLowBattery(Intent batteryChangedIntent)307     public static boolean isExtremeLowBattery(Intent batteryChangedIntent) {
308         int level = getBatteryLevel(batteryChangedIntent);
309         return isExtremeLowBattery(level);
310     }
311 
312     /**
313      * Whether the battery is extreme low or not.
314      *
315      * @return {@code true} if the {@code batteryLevel} is less or equal to
316      * {@link EXTREME_LOW_BATTERY_THRESHOLD}
317      */
isExtremeLowBattery(int batteryLevel)318     public static boolean isExtremeLowBattery(int batteryLevel) {
319         return batteryLevel <= EXTREME_LOW_BATTERY_THRESHOLD;
320     }
321 
322     /**
323      * Whether the battery defender is enabled or not.
324      *
325      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
326      * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
327      * defend, or temp defend
328      */
isBatteryDefender(Intent batteryChangedIntent)329     public static boolean isBatteryDefender(Intent batteryChangedIntent) {
330         int chargingStatus =
331                 batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
332         return isBatteryDefender(chargingStatus);
333     }
334 
335     /**
336      * Whether the battery defender is enabled or not.
337      *
338      * @param chargingStatus for {@link EXTRA_CHARGING_STATUS} field in the ACTION_BATTERY_CHANGED
339      *     intent
340      * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
341      *     defend, or temp defend
342      */
isBatteryDefender(int chargingStatus)343     public static boolean isBatteryDefender(int chargingStatus) {
344         return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
345     }
346 
347     /**
348      * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold}
349      * and {@link R.integer.config_chargingFastThreshold}.
350      *
351      * @param context the application context
352      * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED}
353      * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
354      *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
355      */
getChargingSpeed(Context context, Intent batteryChangedIntent)356     public static int getChargingSpeed(Context context, Intent batteryChangedIntent) {
357         final int maxChargingMicroCurrent =
358                 batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
359         int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
360 
361         return calculateChargingSpeed(context, maxChargingMicroCurrent, maxChargingMicroVolt);
362     }
363 
364     /**
365      * Calculates the charging speed based on the {@link R.integer.config_chargingSlowlyThreshold}
366      * and {@link R.integer.config_chargingFastThreshold}.
367      *
368      * @param maxChargingMicroCurrent the max charging micro current that is retrieved form the
369      *     extra of {@link Intent.Action_BATTERY_CHANGED}
370      * @param maxChargingMicroVolt the max charging micro voltage that is retrieved form the extra
371      *     of {@link Intent.Action_BATTERY_CHANGED}
372      * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
373      *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
374      */
calculateChargingSpeed( Context context, int maxChargingMicroCurrent, int maxChargingMicroVolt)375     public static int calculateChargingSpeed(
376             Context context, int maxChargingMicroCurrent, int maxChargingMicroVolt) {
377         final int maxChargingMicroWatt =
378                 calculateMaxChargingMicroWatt(maxChargingMicroCurrent, maxChargingMicroVolt);
379 
380         if (maxChargingMicroWatt <= 0) {
381             return CHARGING_UNKNOWN;
382         } else if (maxChargingMicroWatt
383                 < context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
384             return CHARGING_SLOWLY;
385         } else if (maxChargingMicroWatt
386                 > context.getResources().getInteger(getFastChargingThresholdResId())) {
387             return CHARGING_FAST;
388         } else {
389             return CHARGING_REGULAR;
390         }
391     }
392 
calculateMaxChargingMicroWatt(Intent batteryChangedIntent)393     private static int calculateMaxChargingMicroWatt(Intent batteryChangedIntent) {
394         final int maxChargingMicroAmp =
395                 batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
396         int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
397 
398         return calculateMaxChargingMicroWatt(maxChargingMicroAmp, maxChargingMicroVolt);
399     }
400 
calculateMaxChargingMicroWatt(int maxChargingMicroAmp, int maxChargingMicroVolt)401     private static int calculateMaxChargingMicroWatt(int maxChargingMicroAmp,
402             int maxChargingMicroVolt) {
403         if (maxChargingMicroVolt <= 0) {
404             maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
405         }
406 
407         if (maxChargingMicroAmp > 0) {
408             // Calculating µW = mA * mV
409             return (int) Math.round(maxChargingMicroAmp * 0.001 * maxChargingMicroVolt * 0.001);
410         } else {
411             return -1;
412         }
413     }
414 
getFastChargingThresholdResId()415     private static int getFastChargingThresholdResId() {
416         return BatteryUtils.isChargingStringV2Enabled()
417                         ? R.integer.config_chargingFastThreshold_v2
418                         : R.integer.config_chargingFastThreshold;
419     }
420 }
421