1 /*
2  * Copyright (C) 2008 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.systemui.power;
18 
19 import android.app.NotificationManager;
20 import android.content.BroadcastReceiver;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.res.Resources;
26 import android.database.ContentObserver;
27 import android.os.BatteryManager;
28 import android.os.Handler;
29 import android.os.HardwarePropertiesManager;
30 import android.os.PowerManager;
31 import android.os.SystemClock;
32 import android.os.UserHandle;
33 import android.provider.Settings;
34 import android.text.TextUtils;
35 import android.text.format.DateUtils;
36 import android.util.Log;
37 import android.util.Slog;
38 import com.android.internal.logging.MetricsLogger;
39 import com.android.systemui.R;
40 import com.android.systemui.SystemUI;
41 import com.android.systemui.statusbar.phone.StatusBar;
42 import java.io.FileDescriptor;
43 import java.io.PrintWriter;
44 import java.util.Arrays;
45 
46 public class PowerUI extends SystemUI {
47     static final String TAG = "PowerUI";
48     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
49     private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
50     private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
51     private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
52 
53     private final Handler mHandler = new Handler();
54     private final Receiver mReceiver = new Receiver();
55 
56     private PowerManager mPowerManager;
57     private HardwarePropertiesManager mHardwarePropertiesManager;
58     private WarningsUI mWarnings;
59     private int mBatteryLevel = 100;
60     private int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
61     private int mPlugType = 0;
62     private int mInvalidCharger = 0;
63 
64     private int mLowBatteryAlertCloseLevel;
65     private final int[] mLowBatteryReminderLevels = new int[2];
66 
67     private long mScreenOffTime = -1;
68 
69     private float mThresholdTemp;
70     private float[] mRecentTemps = new float[MAX_RECENT_TEMPS];
71     private int mNumTemps;
72     private long mNextLogTime;
73 
start()74     public void start() {
75         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
76         mHardwarePropertiesManager = (HardwarePropertiesManager)
77                 mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE);
78         mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
79         mWarnings = new PowerNotificationWarnings(
80                 mContext,
81                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE),
82                 getComponent(StatusBar.class));
83 
84         ContentObserver obs = new ContentObserver(mHandler) {
85             @Override
86             public void onChange(boolean selfChange) {
87                 updateBatteryWarningLevels();
88             }
89         };
90         final ContentResolver resolver = mContext.getContentResolver();
91         resolver.registerContentObserver(Settings.Global.getUriFor(
92                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
93                 false, obs, UserHandle.USER_ALL);
94         updateBatteryWarningLevels();
95         mReceiver.init();
96 
97         // Check to see if we need to let the user know that the phone previously shut down due
98         // to the temperature being too high.
99         showThermalShutdownDialog();
100 
101         initTemperatureWarning();
102     }
103 
updateBatteryWarningLevels()104     void updateBatteryWarningLevels() {
105         int critLevel = mContext.getResources().getInteger(
106                 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
107 
108         final ContentResolver resolver = mContext.getContentResolver();
109         int defWarnLevel = mContext.getResources().getInteger(
110                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
111         int warnLevel = Settings.Global.getInt(resolver,
112                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL, defWarnLevel);
113         if (warnLevel == 0) {
114             warnLevel = defWarnLevel;
115         }
116         if (warnLevel < critLevel) {
117             warnLevel = critLevel;
118         }
119 
120         mLowBatteryReminderLevels[0] = warnLevel;
121         mLowBatteryReminderLevels[1] = critLevel;
122         mLowBatteryAlertCloseLevel = mLowBatteryReminderLevels[0]
123                 + mContext.getResources().getInteger(
124                         com.android.internal.R.integer.config_lowBatteryCloseWarningBump);
125     }
126 
127     /**
128      * Buckets the battery level.
129      *
130      * The code in this function is a little weird because I couldn't comprehend
131      * the bucket going up when the battery level was going down. --joeo
132      *
133      * 1 means that the battery is "ok"
134      * 0 means that the battery is between "ok" and what we should warn about.
135      * less than 0 means that the battery is low
136      */
findBatteryLevelBucket(int level)137     private int findBatteryLevelBucket(int level) {
138         if (level >= mLowBatteryAlertCloseLevel) {
139             return 1;
140         }
141         if (level > mLowBatteryReminderLevels[0]) {
142             return 0;
143         }
144         final int N = mLowBatteryReminderLevels.length;
145         for (int i=N-1; i>=0; i--) {
146             if (level <= mLowBatteryReminderLevels[i]) {
147                 return -1-i;
148             }
149         }
150         throw new RuntimeException("not possible!");
151     }
152 
153     private final class Receiver extends BroadcastReceiver {
154 
init()155         public void init() {
156             // Register for Intent broadcasts for...
157             IntentFilter filter = new IntentFilter();
158             filter.addAction(Intent.ACTION_BATTERY_CHANGED);
159             filter.addAction(Intent.ACTION_SCREEN_OFF);
160             filter.addAction(Intent.ACTION_SCREEN_ON);
161             filter.addAction(Intent.ACTION_USER_SWITCHED);
162             mContext.registerReceiver(this, filter, null, mHandler);
163         }
164 
165         @Override
onReceive(Context context, Intent intent)166         public void onReceive(Context context, Intent intent) {
167             String action = intent.getAction();
168             if (action.equals(Intent.ACTION_BATTERY_CHANGED)) {
169                 final int oldBatteryLevel = mBatteryLevel;
170                 mBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 100);
171                 final int oldBatteryStatus = mBatteryStatus;
172                 mBatteryStatus = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
173                         BatteryManager.BATTERY_STATUS_UNKNOWN);
174                 final int oldPlugType = mPlugType;
175                 mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1);
176                 final int oldInvalidCharger = mInvalidCharger;
177                 mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
178 
179                 final boolean plugged = mPlugType != 0;
180                 final boolean oldPlugged = oldPlugType != 0;
181 
182                 int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
183                 int bucket = findBatteryLevelBucket(mBatteryLevel);
184 
185                 if (DEBUG) {
186                     Slog.d(TAG, "buckets   ....." + mLowBatteryAlertCloseLevel
187                             + " .. " + mLowBatteryReminderLevels[0]
188                             + " .. " + mLowBatteryReminderLevels[1]);
189                     Slog.d(TAG, "level          " + oldBatteryLevel + " --> " + mBatteryLevel);
190                     Slog.d(TAG, "status         " + oldBatteryStatus + " --> " + mBatteryStatus);
191                     Slog.d(TAG, "plugType       " + oldPlugType + " --> " + mPlugType);
192                     Slog.d(TAG, "invalidCharger " + oldInvalidCharger + " --> " + mInvalidCharger);
193                     Slog.d(TAG, "bucket         " + oldBucket + " --> " + bucket);
194                     Slog.d(TAG, "plugged        " + oldPlugged + " --> " + plugged);
195                 }
196 
197                 mWarnings.update(mBatteryLevel, bucket, mScreenOffTime);
198                 if (oldInvalidCharger == 0 && mInvalidCharger != 0) {
199                     Slog.d(TAG, "showing invalid charger warning");
200                     mWarnings.showInvalidChargerWarning();
201                     return;
202                 } else if (oldInvalidCharger != 0 && mInvalidCharger == 0) {
203                     mWarnings.dismissInvalidChargerWarning();
204                 } else if (mWarnings.isInvalidChargerWarningShowing()) {
205                     // if invalid charger is showing, don't show low battery
206                     return;
207                 }
208 
209                 boolean isPowerSaver = mPowerManager.isPowerSaveMode();
210                 if (!plugged
211                         && !isPowerSaver
212                         && (bucket < oldBucket || oldPlugged)
213                         && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN
214                         && bucket < 0) {
215                     // only play SFX when the dialog comes up or the bucket changes
216                     final boolean playSound = bucket != oldBucket || oldPlugged;
217                     mWarnings.showLowBatteryWarning(playSound);
218                 } else if (isPowerSaver || plugged || (bucket > oldBucket && bucket > 0)) {
219                     mWarnings.dismissLowBatteryWarning();
220                 } else {
221                     mWarnings.updateLowBatteryWarning();
222                 }
223             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
224                 mScreenOffTime = SystemClock.elapsedRealtime();
225             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
226                 mScreenOffTime = -1;
227             } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
228                 mWarnings.userSwitched();
229             } else {
230                 Slog.w(TAG, "unknown intent: " + intent);
231             }
232         }
233     };
234 
initTemperatureWarning()235     private void initTemperatureWarning() {
236         ContentResolver resolver = mContext.getContentResolver();
237         Resources resources = mContext.getResources();
238         if (Settings.Global.getInt(resolver, Settings.Global.SHOW_TEMPERATURE_WARNING,
239                 resources.getInteger(R.integer.config_showTemperatureWarning)) == 0) {
240             return;
241         }
242 
243         mThresholdTemp = Settings.Global.getFloat(resolver, Settings.Global.WARNING_TEMPERATURE,
244                 resources.getInteger(R.integer.config_warningTemperature));
245 
246         if (mThresholdTemp < 0f) {
247             // Get the throttling temperature. No need to check if we're not throttling.
248             float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
249                     HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
250                     HardwarePropertiesManager.TEMPERATURE_THROTTLING);
251             if (throttlingTemps == null
252                     || throttlingTemps.length == 0
253                     || throttlingTemps[0] == HardwarePropertiesManager.UNDEFINED_TEMPERATURE) {
254                 return;
255             }
256             mThresholdTemp = throttlingTemps[0];
257         }
258         setNextLogTime();
259 
260         // We have passed all of the checks, start checking the temp
261         updateTemperatureWarning();
262     }
263 
showThermalShutdownDialog()264     private void showThermalShutdownDialog() {
265         if (mPowerManager.getLastShutdownReason()
266                 == PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN) {
267             mWarnings.showThermalShutdownWarning();
268         }
269     }
270 
updateTemperatureWarning()271     private void updateTemperatureWarning() {
272         float[] temps = mHardwarePropertiesManager.getDeviceTemperatures(
273                 HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
274                 HardwarePropertiesManager.TEMPERATURE_CURRENT);
275         if (temps.length != 0) {
276             float temp = temps[0];
277             mRecentTemps[mNumTemps++] = temp;
278 
279             StatusBar statusBar = getComponent(StatusBar.class);
280             if (statusBar != null && !statusBar.isDeviceInVrMode()
281                     && temp >= mThresholdTemp) {
282                 logAtTemperatureThreshold(temp);
283                 mWarnings.showHighTemperatureWarning();
284             } else {
285                 mWarnings.dismissHighTemperatureWarning();
286             }
287         }
288 
289         logTemperatureStats();
290 
291         mHandler.postDelayed(this::updateTemperatureWarning, TEMPERATURE_INTERVAL);
292     }
293 
logAtTemperatureThreshold(float temp)294     private void logAtTemperatureThreshold(float temp) {
295         StringBuilder sb = new StringBuilder();
296         sb.append("currentTemp=").append(temp)
297                 .append(",thresholdTemp=").append(mThresholdTemp)
298                 .append(",batteryStatus=").append(mBatteryStatus)
299                 .append(",recentTemps=");
300         for (int i = 0; i < mNumTemps; i++) {
301             sb.append(mRecentTemps[i]).append(',');
302         }
303         Slog.i(TAG, sb.toString());
304     }
305 
306     /**
307      * Calculates and logs min, max, and average
308      * {@link HardwarePropertiesManager#DEVICE_TEMPERATURE_SKIN} over the past
309      * {@link #TEMPERATURE_LOGGING_INTERVAL}.
310      */
logTemperatureStats()311     private void logTemperatureStats() {
312         if (mNextLogTime > System.currentTimeMillis() && mNumTemps != MAX_RECENT_TEMPS) {
313             return;
314         }
315 
316         if (mNumTemps > 0) {
317             float sum = mRecentTemps[0], min = mRecentTemps[0], max = mRecentTemps[0];
318             for (int i = 1; i < mNumTemps; i++) {
319                 float temp = mRecentTemps[i];
320                 sum += temp;
321                 if (temp > max) {
322                     max = temp;
323                 }
324                 if (temp < min) {
325                     min = temp;
326                 }
327             }
328 
329             float avg = sum / mNumTemps;
330             Slog.i(TAG, "avg=" + avg + ",min=" + min + ",max=" + max);
331             MetricsLogger.histogram(mContext, "device_skin_temp_avg", (int) avg);
332             MetricsLogger.histogram(mContext, "device_skin_temp_min", (int) min);
333             MetricsLogger.histogram(mContext, "device_skin_temp_max", (int) max);
334         }
335         setNextLogTime();
336         mNumTemps = 0;
337     }
338 
setNextLogTime()339     private void setNextLogTime() {
340         mNextLogTime = System.currentTimeMillis() + TEMPERATURE_LOGGING_INTERVAL;
341     }
342 
dump(FileDescriptor fd, PrintWriter pw, String[] args)343     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
344         pw.print("mLowBatteryAlertCloseLevel=");
345         pw.println(mLowBatteryAlertCloseLevel);
346         pw.print("mLowBatteryReminderLevels=");
347         pw.println(Arrays.toString(mLowBatteryReminderLevels));
348         pw.print("mBatteryLevel=");
349         pw.println(Integer.toString(mBatteryLevel));
350         pw.print("mBatteryStatus=");
351         pw.println(Integer.toString(mBatteryStatus));
352         pw.print("mPlugType=");
353         pw.println(Integer.toString(mPlugType));
354         pw.print("mInvalidCharger=");
355         pw.println(Integer.toString(mInvalidCharger));
356         pw.print("mScreenOffTime=");
357         pw.print(mScreenOffTime);
358         if (mScreenOffTime >= 0) {
359             pw.print(" (");
360             pw.print(SystemClock.elapsedRealtime() - mScreenOffTime);
361             pw.print(" ago)");
362         }
363         pw.println();
364         pw.print("soundTimeout=");
365         pw.println(Settings.Global.getInt(mContext.getContentResolver(),
366                 Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0));
367         pw.print("bucket: ");
368         pw.println(Integer.toString(findBatteryLevelBucket(mBatteryLevel)));
369         pw.print("mThresholdTemp=");
370         pw.println(Float.toString(mThresholdTemp));
371         pw.print("mNextLogTime=");
372         pw.println(Long.toString(mNextLogTime));
373         mWarnings.dump(pw);
374     }
375 
376     public interface WarningsUI {
update(int batteryLevel, int bucket, long screenOffTime)377         void update(int batteryLevel, int bucket, long screenOffTime);
dismissLowBatteryWarning()378         void dismissLowBatteryWarning();
showLowBatteryWarning(boolean playSound)379         void showLowBatteryWarning(boolean playSound);
dismissInvalidChargerWarning()380         void dismissInvalidChargerWarning();
showInvalidChargerWarning()381         void showInvalidChargerWarning();
updateLowBatteryWarning()382         void updateLowBatteryWarning();
isInvalidChargerWarningShowing()383         boolean isInvalidChargerWarningShowing();
dismissHighTemperatureWarning()384         void dismissHighTemperatureWarning();
showHighTemperatureWarning()385         void showHighTemperatureWarning();
showThermalShutdownWarning()386         void showThermalShutdownWarning();
dump(PrintWriter pw)387         void dump(PrintWriter pw);
userSwitched()388         void userSwitched();
389     }
390 }
391 
392