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     private static final int LOW_BATTERY_THRESHOLD = 20;
44     private static final int SEVERE_LOW_BATTERY_THRESHOLD = 10;
45     private static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
46     private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
47 
48     public static final int BATTERY_LEVEL_UNKNOWN = -1;
49     public static final int CHARGING_UNKNOWN = -1;
50     public static final int CHARGING_SLOWLY = 0;
51     public static final int CHARGING_REGULAR = 1;
52     public static final int CHARGING_FAST = 2;
53 
54     public final int status;
55     public final int level;
56     public final int plugged;
57     public final int chargingStatus;
58     public final int maxChargingWattage;
59     public final boolean present;
60     public final Optional<Boolean> incompatibleCharger;
61 
create(Context context, boolean incompatibleCharger)62     public static BatteryStatus create(Context context, boolean incompatibleCharger) {
63         final Intent batteryChangedIntent = BatteryUtils.getBatteryIntent(context);
64         return batteryChangedIntent == null
65                 ? null : new BatteryStatus(batteryChangedIntent, incompatibleCharger);
66     }
67 
BatteryStatus(int status, int level, int plugged, int chargingStatus, int maxChargingWattage, boolean present)68     public BatteryStatus(int status, int level, int plugged, int chargingStatus,
69             int maxChargingWattage, boolean present) {
70         this.status = status;
71         this.level = level;
72         this.plugged = plugged;
73         this.chargingStatus = chargingStatus;
74         this.maxChargingWattage = maxChargingWattage;
75         this.present = present;
76         this.incompatibleCharger = Optional.empty();
77     }
78 
79 
BatteryStatus(Intent batteryChangedIntent)80     public BatteryStatus(Intent batteryChangedIntent) {
81         this(batteryChangedIntent, Optional.empty());
82     }
83 
BatteryStatus(Intent batteryChangedIntent, boolean incompatibleCharger)84     public BatteryStatus(Intent batteryChangedIntent, boolean incompatibleCharger) {
85         this(batteryChangedIntent, Optional.of(incompatibleCharger));
86     }
87 
BatteryStatus(Intent batteryChangedIntent, Optional<Boolean> incompatibleCharger)88     private BatteryStatus(Intent batteryChangedIntent, Optional<Boolean> incompatibleCharger) {
89         status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
90         plugged = batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
91         level = getBatteryLevel(batteryChangedIntent);
92         chargingStatus = batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS,
93                 CHARGING_POLICY_DEFAULT);
94         present = batteryChangedIntent.getBooleanExtra(EXTRA_PRESENT, true);
95         this.incompatibleCharger = incompatibleCharger;
96 
97         maxChargingWattage = calculateMaxChargingMicroWatt(batteryChangedIntent);
98     }
99 
100     /** Determine whether the device is plugged. */
isPluggedIn()101     public boolean isPluggedIn() {
102         return isPluggedIn(plugged);
103     }
104 
105     /** Determine whether the device is plugged in (USB, power). */
isPluggedInWired()106     public boolean isPluggedInWired() {
107         return plugged == BatteryManager.BATTERY_PLUGGED_AC
108                 || plugged == BatteryManager.BATTERY_PLUGGED_USB;
109     }
110 
111     /**
112      * Determine whether the device is plugged in wireless. */
isPluggedInWireless()113     public boolean isPluggedInWireless() {
114         return plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS;
115     }
116 
117     /** Determine whether the device is plugged in dock. */
isPluggedInDock()118     public boolean isPluggedInDock() {
119         return isPluggedInDock(plugged);
120     }
121 
122     /**
123      * Whether or not the device is charged. Note that some devices never return 100% for
124      * battery level, so this allows either battery level or status to determine if the
125      * battery is charged.
126      */
isCharged()127     public boolean isCharged() {
128         return isCharged(status, level);
129     }
130 
131     /** Whether battery is low and needs to be charged. */
isBatteryLow()132     public boolean isBatteryLow() {
133         return isLowBattery(level);
134     }
135 
136     /** Whether battery defender is enabled. */
isBatteryDefender()137     public boolean isBatteryDefender() {
138         return isBatteryDefender(chargingStatus);
139     }
140 
141     /** Return current charging speed is fast, slow or normal. */
getChargingSpeed(Context context)142     public final int getChargingSpeed(Context context) {
143         final int slowThreshold = context.getResources().getInteger(
144                 R.integer.config_chargingSlowlyThreshold);
145         final int fastThreshold = context.getResources().getInteger(
146                 R.integer.config_chargingFastThreshold);
147         return maxChargingWattage <= 0 ? CHARGING_UNKNOWN :
148                 maxChargingWattage < slowThreshold ? CHARGING_SLOWLY :
149                         maxChargingWattage > fastThreshold ? CHARGING_FAST :
150                                 CHARGING_REGULAR;
151     }
152 
153     @Override
toString()154     public String toString() {
155         return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged
156                 + ",chargingStatus=" + chargingStatus + ",maxChargingWattage=" + maxChargingWattage
157                 + "}";
158     }
159 
160     /**
161      * Whether or not the device is charged. Note that some devices never return 100% for
162      * battery level, so this allows either battery level or status to determine if the
163      * battery is charged.
164      *
165      * @param batteryChangedIntent ACTION_BATTERY_CHANGED intent
166      * @return true if the device is charged
167      */
isCharged(Intent batteryChangedIntent)168     public static boolean isCharged(Intent batteryChangedIntent) {
169         int status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
170         int level = getBatteryLevel(batteryChangedIntent);
171         return isCharged(status, level);
172     }
173 
174     /**
175      * Whether or not the device is charged. Note that some devices never return 100% for
176      * battery level, so this allows either battery level or status to determine if the
177      * battery is charged.
178      *
179      * @param status values for "status" field in the ACTION_BATTERY_CHANGED Intent
180      * @param level values from 0 to 100
181      * @return true if the device is charged
182      */
isCharged(int status, int level)183     public static boolean isCharged(int status, int level) {
184         return status == BATTERY_STATUS_FULL || level >= 100;
185     }
186 
187     /** Gets the battery level from the intent. */
getBatteryLevel(Intent batteryChangedIntent)188     public static int getBatteryLevel(Intent batteryChangedIntent) {
189         if (batteryChangedIntent == null) {
190             return BATTERY_LEVEL_UNKNOWN;
191         }
192         final int level =
193                 batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_LEVEL, BATTERY_LEVEL_UNKNOWN);
194         final int scale = batteryChangedIntent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
195         return scale == 0
196                 ? BATTERY_LEVEL_UNKNOWN
197                 : Math.round((level / (float) scale) * 100f);
198     }
199 
200     /** Whether the device is plugged or not. */
isPluggedIn(Intent batteryChangedIntent)201     public static boolean isPluggedIn(Intent batteryChangedIntent) {
202         return isPluggedIn(batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0));
203     }
204 
205     /** Whether the device is plugged or not. */
isPluggedIn(int plugged)206     public static boolean isPluggedIn(int plugged) {
207         return plugged == BatteryManager.BATTERY_PLUGGED_AC
208                 || plugged == BatteryManager.BATTERY_PLUGGED_USB
209                 || plugged == BatteryManager.BATTERY_PLUGGED_WIRELESS
210                 || plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
211     }
212 
213     /** Determine whether the device is plugged in dock. */
isPluggedInDock(Intent batteryChangedIntent)214     public static boolean isPluggedInDock(Intent batteryChangedIntent) {
215         return isPluggedInDock(
216                 batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, BATTERY_PLUGGED_NONE));
217     }
218 
219     /** Determine whether the device is plugged in dock. */
isPluggedInDock(int plugged)220     public static boolean isPluggedInDock(int plugged) {
221         return plugged == BatteryManager.BATTERY_PLUGGED_DOCK;
222     }
223 
224     /**
225      * Whether the battery is low or not.
226      *
227      * @param batteryChangedIntent the {@link ACTION_BATTERY_CHANGED} intent
228      * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
229      */
isLowBattery(Intent batteryChangedIntent)230     public static boolean isLowBattery(Intent batteryChangedIntent) {
231         int level = getBatteryLevel(batteryChangedIntent);
232         return isLowBattery(level);
233     }
234 
235     /**
236      * Whether the battery is low or not.
237      *
238      * @param batteryLevel the battery level
239      * @return {@code true} if the battery level is less or equal to {@link LOW_BATTERY_THRESHOLD}
240      */
isLowBattery(int batteryLevel)241     public static boolean isLowBattery(int batteryLevel) {
242         return batteryLevel <= LOW_BATTERY_THRESHOLD;
243     }
244 
245     /**
246      * Whether the battery is severe low or not.
247      *
248      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
249      * @return {@code true} if the battery level is less or equal to {@link
250      *     SEVERE_LOW_BATTERY_THRESHOLD}
251      */
isSevereLowBattery(Intent batteryChangedIntent)252     public static boolean isSevereLowBattery(Intent batteryChangedIntent) {
253         int level = getBatteryLevel(batteryChangedIntent);
254         return level <= SEVERE_LOW_BATTERY_THRESHOLD;
255     }
256 
257     /**
258      * Whether the battery is extreme low or not.
259      *
260      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
261      * @return {@code true} if the battery level is less or equal to {@link
262      *     EXTREME_LOW_BATTERY_THRESHOLD}
263      */
isExtremeLowBattery(Intent batteryChangedIntent)264     public static boolean isExtremeLowBattery(Intent batteryChangedIntent) {
265         int level = getBatteryLevel(batteryChangedIntent);
266         return level <= EXTREME_LOW_BATTERY_THRESHOLD;
267     }
268 
269     /**
270      * Whether the battery defender is enabled or not.
271      *
272      * @param batteryChangedIntent the ACTION_BATTERY_CHANGED intent
273      * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
274      *     defend, or temp defend
275      */
isBatteryDefender(Intent batteryChangedIntent)276     public static boolean isBatteryDefender(Intent batteryChangedIntent) {
277         int chargingStatus =
278                 batteryChangedIntent.getIntExtra(EXTRA_CHARGING_STATUS, CHARGING_POLICY_DEFAULT);
279         return isBatteryDefender(chargingStatus);
280     }
281 
282     /**
283      * Whether the battery defender is enabled or not.
284      *
285      * @param chargingStatus for {@link EXTRA_CHARGING_STATUS} field in the ACTION_BATTERY_CHANGED
286      *     intent
287      * @return {@code true} if the battery defender is enabled. It could be dock defend, dwell
288      *     defend, or temp defend
289      */
isBatteryDefender(int chargingStatus)290     public static boolean isBatteryDefender(int chargingStatus) {
291         return chargingStatus == CHARGING_POLICY_ADAPTIVE_LONGLIFE;
292     }
293 
294     /**
295      * Gets the max charging current and max charging voltage form {@link
296      * Intent.ACTION_BATTERY_CHANGED} and calculates the charging speed based on the {@link
297      * R.integer.config_chargingSlowlyThreshold} and {@link R.integer.config_chargingFastThreshold}.
298      *
299      * @param context the application context
300      * @param batteryChangedIntent the intent from {@link Intent.ACTION_BATTERY_CHANGED}
301      * @return the charging speed. {@link CHARGING_REGULAR}, {@link CHARGING_FAST}, {@link
302      *     CHARGING_SLOWLY} or {@link CHARGING_UNKNOWN}
303      */
getChargingSpeed(Context context, Intent batteryChangedIntent)304     public static int getChargingSpeed(Context context, Intent batteryChangedIntent) {
305         final int maxChargingMicroWatt = calculateMaxChargingMicroWatt(batteryChangedIntent);
306         if (maxChargingMicroWatt <= 0) {
307             return CHARGING_UNKNOWN;
308         } else if (maxChargingMicroWatt
309                 < context.getResources().getInteger(R.integer.config_chargingSlowlyThreshold)) {
310             return CHARGING_SLOWLY;
311         } else if (maxChargingMicroWatt
312                 > context.getResources().getInteger(R.integer.config_chargingFastThreshold)) {
313             return CHARGING_FAST;
314         } else {
315             return CHARGING_REGULAR;
316         }
317     }
318 
calculateMaxChargingMicroWatt(Intent batteryChangedIntent)319     private static int calculateMaxChargingMicroWatt(Intent batteryChangedIntent) {
320         final int maxChargingMicroAmp =
321                 batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_CURRENT, -1);
322         int maxChargingMicroVolt = batteryChangedIntent.getIntExtra(EXTRA_MAX_CHARGING_VOLTAGE, -1);
323         if (maxChargingMicroVolt <= 0) {
324             maxChargingMicroVolt = DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT;
325         }
326 
327         if (maxChargingMicroAmp > 0) {
328             // Calculating µW = mA * mV
329             return (int) Math.round(maxChargingMicroAmp * 0.001 * maxChargingMicroVolt * 0.001);
330         } else {
331             return -1;
332         }
333     }
334 }
335