1 /*
2  * Copyright (C) 2018 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.server.power.batterysaver;
17 
18 import android.os.BatteryManagerInternal;
19 import android.os.SystemClock;
20 import android.util.ArrayMap;
21 import android.util.Slog;
22 import android.util.TimeUtils;
23 
24 import com.android.internal.annotations.GuardedBy;
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.server.EventLogTags;
27 import com.android.server.LocalServices;
28 
29 import java.io.PrintWriter;
30 import java.text.SimpleDateFormat;
31 import java.util.Date;
32 
33 /**
34  * This class keeps track of battery drain rate.
35  *
36  * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy.
37  * Do not call out with the lock held. (Settings provider is okay.)
38  *
39  * TODO: The use of the terms "percent" and "level" in this class is not standard. Fix it.
40  *
41  * Test:
42  atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySavingStatsTest.java
43  */
44 public class BatterySavingStats {
45 
46     private static final String TAG = "BatterySavingStats";
47 
48     private static final boolean DEBUG = BatterySaverPolicy.DEBUG;
49 
50     private final Object mLock;
51 
52     /** Whether battery saver is on or off. */
53     interface BatterySaverState {
54         int OFF = 0;
55         int ON = 1;
56         int ADAPTIVE = 2;
57 
58         int SHIFT = 0;
59         int BITS = 2;
60         int MASK = (1 << BITS) - 1;
61 
fromIndex(int index)62         static int fromIndex(int index) {
63             return (index >> SHIFT) & MASK;
64         }
65     }
66 
67     /** Whether the device is interactive (i.e. screen on) or not. */
68     interface InteractiveState {
69         int NON_INTERACTIVE = 0;
70         int INTERACTIVE = 1;
71 
72         int SHIFT = BatterySaverState.SHIFT + BatterySaverState.BITS;
73         int BITS = 1;
74         int MASK = (1 << BITS) - 1;
75 
fromIndex(int index)76         static int fromIndex(int index) {
77             return (index >> SHIFT) & MASK;
78         }
79     }
80 
81     /** Doze mode. */
82     interface DozeState {
83         int NOT_DOZING = 0;
84         int LIGHT = 1;
85         int DEEP = 2;
86 
87         int SHIFT = InteractiveState.SHIFT + InteractiveState.BITS;
88         int BITS = 2;
89         int MASK = (1 << BITS) - 1;
90 
fromIndex(int index)91         static int fromIndex(int index) {
92             return (index >> SHIFT) & MASK;
93         }
94     }
95 
96     /**
97      * Various stats in each state.
98      */
99     static class Stat {
100         public long startTime;
101         public long endTime;
102 
103         public int startBatteryLevel;
104         public int endBatteryLevel;
105 
106         public int startBatteryPercent;
107         public int endBatteryPercent;
108 
109         public long totalTimeMillis;
110         public int totalBatteryDrain;
111         public int totalBatteryDrainPercent;
112 
totalMinutes()113         public long totalMinutes() {
114             return totalTimeMillis / 60_000;
115         }
116 
drainPerHour()117         public double drainPerHour() {
118             if (totalTimeMillis == 0) {
119                 return 0;
120             }
121             return (double) totalBatteryDrain / (totalTimeMillis / (60.0 * 60 * 1000));
122         }
123 
drainPercentPerHour()124         public double drainPercentPerHour() {
125             if (totalTimeMillis == 0) {
126                 return 0;
127             }
128             return (double) totalBatteryDrainPercent / (totalTimeMillis / (60.0 * 60 * 1000));
129         }
130 
131         @VisibleForTesting
toStringForTest()132         String toStringForTest() {
133             return "{" + totalMinutes() + "m," + totalBatteryDrain + ","
134                     + String.format("%.2f", drainPerHour()) + "uA/H,"
135                     + String.format("%.2f", drainPercentPerHour()) + "%"
136                     + "}";
137         }
138     }
139 
140     private BatteryManagerInternal mBatteryManagerInternal;
141 
142     private static final int STATE_NOT_INITIALIZED = -1;
143     private static final int STATE_CHARGING = -2;
144 
145     /**
146      * Current state, one of STATE_* or values returned by {@link #statesToIndex}.
147      */
148     @GuardedBy("mLock")
149     private int mCurrentState = STATE_NOT_INITIALIZED;
150 
151     /**
152      * Stats in each state.
153      */
154     @VisibleForTesting
155     @GuardedBy("mLock")
156     final ArrayMap<Integer, Stat> mStats = new ArrayMap<>();
157 
158     @GuardedBy("mLock")
159     private int mBatterySaverEnabledCount = 0;
160 
161     @GuardedBy("mLock")
162     private boolean mIsBatterySaverEnabled;
163 
164     @GuardedBy("mLock")
165     private long mLastBatterySaverEnabledTime = 0;
166 
167     @GuardedBy("mLock")
168     private long mLastBatterySaverDisabledTime = 0;
169 
170     /** Visible for unit tests */
171     @VisibleForTesting
BatterySavingStats(Object lock)172     public BatterySavingStats(Object lock) {
173         mLock = lock;
174         mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
175     }
176 
getBatteryManagerInternal()177     private BatteryManagerInternal getBatteryManagerInternal() {
178         if (mBatteryManagerInternal == null) {
179             mBatteryManagerInternal = LocalServices.getService(BatteryManagerInternal.class);
180             if (mBatteryManagerInternal == null) {
181                 Slog.wtf(TAG, "BatteryManagerInternal not initialized");
182             }
183         }
184         return mBatteryManagerInternal;
185     }
186 
187     /**
188      * Takes a state triplet and generates a state index.
189      */
190     @VisibleForTesting
statesToIndex( int batterySaverState, int interactiveState, int dozeState)191     static int statesToIndex(
192             int batterySaverState, int interactiveState, int dozeState) {
193         int ret = batterySaverState & BatterySaverState.MASK;
194         ret |= (interactiveState & InteractiveState.MASK) << InteractiveState.SHIFT;
195         ret |= (dozeState & DozeState.MASK) << DozeState.SHIFT;
196         return ret;
197     }
198 
199     /**
200      * Takes a state index and returns a string for logging.
201      */
202     @VisibleForTesting
stateToString(int state)203     static String stateToString(int state) {
204         switch (state) {
205             case STATE_NOT_INITIALIZED:
206                 return "NotInitialized";
207             case STATE_CHARGING:
208                 return "Charging";
209         }
210         return "BS=" + BatterySaverState.fromIndex(state)
211                 + ",I=" + InteractiveState.fromIndex(state)
212                 + ",D=" + DozeState.fromIndex(state);
213     }
214 
215     /**
216      * @return {@link Stat} fo a given state.
217      */
218     @VisibleForTesting
getStat(int stateIndex)219     Stat getStat(int stateIndex) {
220         synchronized (mLock) {
221             Stat stat = mStats.get(stateIndex);
222             if (stat == null) {
223                 stat = new Stat();
224                 mStats.put(stateIndex, stat);
225             }
226             return stat;
227         }
228     }
229 
230     /**
231      * @return {@link Stat} fo a given state triplet.
232      */
getStat(int batterySaverState, int interactiveState, int dozeState)233     private Stat getStat(int batterySaverState, int interactiveState, int dozeState) {
234         return getStat(statesToIndex(batterySaverState, interactiveState, dozeState));
235     }
236 
237     @VisibleForTesting
injectCurrentTime()238     long injectCurrentTime() {
239         return SystemClock.elapsedRealtime();
240     }
241 
242     @VisibleForTesting
injectBatteryLevel()243     int injectBatteryLevel() {
244         final BatteryManagerInternal bmi = getBatteryManagerInternal();
245         if (bmi == null) {
246             return 0;
247         }
248         return bmi.getBatteryChargeCounter();
249     }
250 
251     @VisibleForTesting
injectBatteryPercent()252     int injectBatteryPercent() {
253         final BatteryManagerInternal bmi = getBatteryManagerInternal();
254         if (bmi == null) {
255             return 0;
256         }
257         return bmi.getBatteryLevel();
258     }
259 
260     /**
261      * Called from the outside whenever any of the states changes, when the device is not plugged
262      * in.
263      */
transitionState(int batterySaverState, int interactiveState, int dozeState)264     public void transitionState(int batterySaverState, int interactiveState, int dozeState) {
265         synchronized (mLock) {
266             final int newState = statesToIndex(
267                     batterySaverState, interactiveState, dozeState);
268             transitionStateLocked(newState);
269         }
270     }
271 
272     /**
273      * Called from the outside when the device is plugged in.
274      */
startCharging()275     public void startCharging() {
276         synchronized (mLock) {
277             transitionStateLocked(STATE_CHARGING);
278         }
279     }
280 
281     @GuardedBy("mLock")
transitionStateLocked(int newState)282     private void transitionStateLocked(int newState) {
283         if (mCurrentState == newState) {
284             return;
285         }
286         final long now = injectCurrentTime();
287         final int batteryLevel = injectBatteryLevel();
288         final int batteryPercent = injectBatteryPercent();
289 
290         final boolean oldBatterySaverEnabled =
291                 BatterySaverState.fromIndex(mCurrentState) != BatterySaverState.OFF;
292         final boolean newBatterySaverEnabled =
293                 BatterySaverState.fromIndex(newState) != BatterySaverState.OFF;
294         if (oldBatterySaverEnabled != newBatterySaverEnabled) {
295             mIsBatterySaverEnabled = newBatterySaverEnabled;
296             if (newBatterySaverEnabled) {
297                 mBatterySaverEnabledCount++;
298                 mLastBatterySaverEnabledTime = injectCurrentTime();
299             } else {
300                 mLastBatterySaverDisabledTime = injectCurrentTime();
301             }
302         }
303 
304         endLastStateLocked(now, batteryLevel, batteryPercent);
305         startNewStateLocked(newState, now, batteryLevel, batteryPercent);
306     }
307 
308     @GuardedBy("mLock")
endLastStateLocked(long now, int batteryLevel, int batteryPercent)309     private void endLastStateLocked(long now, int batteryLevel, int batteryPercent) {
310         if (mCurrentState < 0) {
311             return;
312         }
313         final Stat stat = getStat(mCurrentState);
314 
315         stat.endBatteryLevel = batteryLevel;
316         stat.endBatteryPercent = batteryPercent;
317         stat.endTime = now;
318 
319         final long deltaTime = stat.endTime - stat.startTime;
320         final int deltaDrain = stat.startBatteryLevel - stat.endBatteryLevel;
321         final int deltaPercent = stat.startBatteryPercent - stat.endBatteryPercent;
322 
323         stat.totalTimeMillis += deltaTime;
324         stat.totalBatteryDrain += deltaDrain;
325         stat.totalBatteryDrainPercent += deltaPercent;
326 
327         if (DEBUG) {
328             Slog.d(TAG, "State summary: " + stateToString(mCurrentState)
329                     + ": " + (deltaTime / 1_000) + "s "
330                     + "Start level: " + stat.startBatteryLevel + "uA "
331                     + "End level: " + stat.endBatteryLevel + "uA "
332                     + "Start percent: " + stat.startBatteryPercent + "% "
333                     + "End percent: " + stat.endBatteryPercent + "% "
334                     + "Drain " + deltaDrain + "uA");
335         }
336         EventLogTags.writeBatterySavingStats(
337                 BatterySaverState.fromIndex(mCurrentState),
338                 InteractiveState.fromIndex(mCurrentState),
339                 DozeState.fromIndex(mCurrentState),
340                 deltaTime,
341                 deltaDrain,
342                 deltaPercent,
343                 stat.totalTimeMillis,
344                 stat.totalBatteryDrain,
345                 stat.totalBatteryDrainPercent);
346 
347     }
348 
349     @GuardedBy("mLock")
startNewStateLocked(int newState, long now, int batteryLevel, int batteryPercent)350     private void startNewStateLocked(int newState, long now, int batteryLevel, int batteryPercent) {
351         if (DEBUG) {
352             Slog.d(TAG, "New state: " + stateToString(newState));
353         }
354         mCurrentState = newState;
355 
356         if (mCurrentState < 0) {
357             return;
358         }
359 
360         final Stat stat = getStat(mCurrentState);
361         stat.startBatteryLevel = batteryLevel;
362         stat.startBatteryPercent = batteryPercent;
363         stat.startTime = now;
364         stat.endTime = 0;
365     }
366 
dump(PrintWriter pw, String indent)367     public void dump(PrintWriter pw, String indent) {
368         synchronized (mLock) {
369             pw.print(indent);
370             pw.println("Battery saving stats:");
371 
372             indent = indent + "  ";
373 
374             final long now = System.currentTimeMillis();
375             final long nowElapsed = injectCurrentTime();
376             final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
377 
378             pw.print(indent);
379             pw.print("Battery Saver is currently: ");
380             pw.println(mIsBatterySaverEnabled ? "ON" : "OFF");
381             if (mLastBatterySaverEnabledTime > 0) {
382                 pw.print(indent);
383                 pw.print("  ");
384                 pw.print("Last ON time: ");
385                 pw.print(sdf.format(new Date(now - nowElapsed + mLastBatterySaverEnabledTime)));
386                 pw.print(" ");
387                 TimeUtils.formatDuration(mLastBatterySaverEnabledTime, nowElapsed, pw);
388                 pw.println();
389             }
390 
391             if (mLastBatterySaverDisabledTime > 0) {
392                 pw.print(indent);
393                 pw.print("  ");
394                 pw.print("Last OFF time: ");
395                 pw.print(sdf.format(new Date(now - nowElapsed + mLastBatterySaverDisabledTime)));
396                 pw.print(" ");
397                 TimeUtils.formatDuration(mLastBatterySaverDisabledTime, nowElapsed, pw);
398                 pw.println();
399             }
400 
401             pw.print(indent);
402             pw.print("  ");
403             pw.print("Times enabled: ");
404             pw.println(mBatterySaverEnabledCount);
405 
406             pw.println();
407 
408             pw.print(indent);
409             pw.println("Drain stats:");
410 
411             pw.print(indent);
412             pw.println("                   Battery saver OFF                          ON");
413             dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
414                     DozeState.NOT_DOZING, "NonDoze");
415             dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, "   Intr",
416                     DozeState.NOT_DOZING, "       ");
417 
418             dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
419                     DozeState.DEEP, "Deep   ");
420             dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, "   Intr",
421                     DozeState.DEEP, "       ");
422 
423             dumpLineLocked(pw, indent, InteractiveState.NON_INTERACTIVE, "NonIntr",
424                     DozeState.LIGHT, "Light  ");
425             dumpLineLocked(pw, indent, InteractiveState.INTERACTIVE, "   Intr",
426                     DozeState.LIGHT, "       ");
427         }
428     }
429 
dumpLineLocked(PrintWriter pw, String indent, int interactiveState, String interactiveLabel, int dozeState, String dozeLabel)430     private void dumpLineLocked(PrintWriter pw, String indent,
431             int interactiveState, String interactiveLabel,
432             int dozeState, String dozeLabel) {
433         pw.print(indent);
434         pw.print(dozeLabel);
435         pw.print(" ");
436         pw.print(interactiveLabel);
437         pw.print(": ");
438 
439         final Stat offStat = getStat(BatterySaverState.OFF, interactiveState, dozeState);
440         final Stat onStat = getStat(BatterySaverState.ON, interactiveState, dozeState);
441 
442         pw.println(String.format("%6dm %6dmAh(%3d%%) %8.1fmAh/h     %6dm %6dmAh(%3d%%) %8.1fmAh/h",
443                 offStat.totalMinutes(),
444                 offStat.totalBatteryDrain / 1000,
445                 offStat.totalBatteryDrainPercent,
446                 offStat.drainPerHour() / 1000.0,
447                 onStat.totalMinutes(),
448                 onStat.totalBatteryDrain / 1000,
449                 onStat.totalBatteryDrainPercent,
450                 onStat.drainPerHour() / 1000.0));
451     }
452 }
453