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