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