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