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.pm.ActivityInfo;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.database.ContentObserver;
28 import android.os.BatteryManager;
29 import android.os.Handler;
30 import android.os.HardwarePropertiesManager;
31 import android.os.IBinder;
32 import android.os.IThermalEventListener;
33 import android.os.IThermalService;
34 import android.os.PowerManager;
35 import android.os.RemoteException;
36 import android.os.ServiceManager;
37 import android.os.SystemClock;
38 import android.os.Temperature;
39 import android.os.UserHandle;
40 import android.provider.Settings;
41 import android.text.format.DateUtils;
42 import android.util.Log;
43 import android.util.Slog;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.logging.MetricsLogger;
47 import com.android.settingslib.utils.ThreadUtils;
48 import com.android.systemui.Dependency;
49 import com.android.systemui.R;
50 import com.android.systemui.SystemUI;
51 import com.android.systemui.statusbar.phone.StatusBar;
52 
53 import java.io.FileDescriptor;
54 import java.io.PrintWriter;
55 import java.time.Duration;
56 import java.util.Arrays;
57 
58 public class PowerUI extends SystemUI {
59     static final String TAG = "PowerUI";
60     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
61     private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
62     private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
63     private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
64     static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
65     private static final int CHARGE_CYCLE_PERCENT_RESET = 45;
66     private static final long SIX_HOURS_MILLIS = Duration.ofHours(6).toMillis();
67 
68     private final Handler mHandler = new Handler();
69     @VisibleForTesting
70     final Receiver mReceiver = new Receiver();
71 
72     private PowerManager mPowerManager;
73     private HardwarePropertiesManager mHardwarePropertiesManager;
74     private WarningsUI mWarnings;
75     private final Configuration mLastConfiguration = new Configuration();
76     private long mTimeRemaining = Long.MAX_VALUE;
77     private int mPlugType = 0;
78     private int mInvalidCharger = 0;
79     private EnhancedEstimates mEnhancedEstimates;
80     private boolean mLowWarningShownThisChargeCycle;
81     private boolean mSevereWarningShownThisChargeCycle;
82 
83     private int mLowBatteryAlertCloseLevel;
84     private final int[] mLowBatteryReminderLevels = new int[2];
85 
86     private long mScreenOffTime = -1;
87 
88     private float mThresholdTemp;
89     private float[] mRecentTemps = new float[MAX_RECENT_TEMPS];
90     private int mNumTemps;
91     private long mNextLogTime;
92     private IThermalService mThermalService;
93 
94     @VisibleForTesting int mBatteryLevel = 100;
95     @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
96 
97     // by using the same instance (method references are not guaranteed to be the same object
98     // We create a method reference here so that we are guaranteed that we can remove a callback
99     // each time they are created).
100     private final Runnable mUpdateTempCallback = this::updateTemperatureWarning;
101 
start()102     public void start() {
103         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
104         mHardwarePropertiesManager = (HardwarePropertiesManager)
105                 mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE);
106         mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
107         mWarnings = Dependency.get(WarningsUI.class);
108         mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
109         mLastConfiguration.setTo(mContext.getResources().getConfiguration());
110 
111         ContentObserver obs = new ContentObserver(mHandler) {
112             @Override
113             public void onChange(boolean selfChange) {
114                 updateBatteryWarningLevels();
115             }
116         };
117         final ContentResolver resolver = mContext.getContentResolver();
118         resolver.registerContentObserver(Settings.Global.getUriFor(
119                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
120                 false, obs, UserHandle.USER_ALL);
121         updateBatteryWarningLevels();
122         mReceiver.init();
123 
124         // Check to see if we need to let the user know that the phone previously shut down due
125         // to the temperature being too high.
126         showThermalShutdownDialog();
127 
128         initTemperatureWarning();
129     }
130 
131     @Override
onConfigurationChanged(Configuration newConfig)132     protected void onConfigurationChanged(Configuration newConfig) {
133         final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
134 
135         // Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
136         if ((mLastConfiguration.updateFrom(newConfig) & mask) != 0) {
137             mHandler.post(this::initTemperatureWarning);
138         }
139     }
140 
updateBatteryWarningLevels()141     void updateBatteryWarningLevels() {
142         int critLevel = mContext.getResources().getInteger(
143                 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
144         int warnLevel = mContext.getResources().getInteger(
145                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
146 
147         if (warnLevel < critLevel) {
148             warnLevel = critLevel;
149         }
150 
151         mLowBatteryReminderLevels[0] = warnLevel;
152         mLowBatteryReminderLevels[1] = critLevel;
153         mLowBatteryAlertCloseLevel = mLowBatteryReminderLevels[0]
154                 + mContext.getResources().getInteger(
155                         com.android.internal.R.integer.config_lowBatteryCloseWarningBump);
156     }
157 
158     /**
159      * Buckets the battery level.
160      *
161      * The code in this function is a little weird because I couldn't comprehend
162      * the bucket going up when the battery level was going down. --joeo
163      *
164      * 1 means that the battery is "ok"
165      * 0 means that the battery is between "ok" and what we should warn about.
166      * less than 0 means that the battery is low
167      */
findBatteryLevelBucket(int level)168     private int findBatteryLevelBucket(int level) {
169         if (level >= mLowBatteryAlertCloseLevel) {
170             return 1;
171         }
172         if (level > mLowBatteryReminderLevels[0]) {
173             return 0;
174         }
175         final int N = mLowBatteryReminderLevels.length;
176         for (int i=N-1; i>=0; i--) {
177             if (level <= mLowBatteryReminderLevels[i]) {
178                 return -1-i;
179             }
180         }
181         throw new RuntimeException("not possible!");
182     }
183 
184     @VisibleForTesting
185     final class Receiver extends BroadcastReceiver {
186 
init()187         public void init() {
188             // Register for Intent broadcasts for...
189             IntentFilter filter = new IntentFilter();
190             filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
191             filter.addAction(Intent.ACTION_BATTERY_CHANGED);
192             filter.addAction(Intent.ACTION_SCREEN_OFF);
193             filter.addAction(Intent.ACTION_SCREEN_ON);
194             filter.addAction(Intent.ACTION_USER_SWITCHED);
195             mContext.registerReceiver(this, filter, null, mHandler);
196         }
197 
198         @Override
onReceive(Context context, Intent intent)199         public void onReceive(Context context, Intent intent) {
200             String action = intent.getAction();
201             if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action)) {
202                 ThreadUtils.postOnBackgroundThread(() -> {
203                     if (mPowerManager.isPowerSaveMode()) {
204                         mWarnings.dismissLowBatteryWarning();
205                     }
206                 });
207             } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
208                 final int oldBatteryLevel = mBatteryLevel;
209                 mBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 100);
210                 final int oldBatteryStatus = mBatteryStatus;
211                 mBatteryStatus = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
212                         BatteryManager.BATTERY_STATUS_UNKNOWN);
213                 final int oldPlugType = mPlugType;
214                 mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1);
215                 final int oldInvalidCharger = mInvalidCharger;
216                 mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
217 
218                 final boolean plugged = mPlugType != 0;
219                 final boolean oldPlugged = oldPlugType != 0;
220 
221                 int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
222                 int bucket = findBatteryLevelBucket(mBatteryLevel);
223 
224                 if (DEBUG) {
225                     Slog.d(TAG, "buckets   ....." + mLowBatteryAlertCloseLevel
226                             + " .. " + mLowBatteryReminderLevels[0]
227                             + " .. " + mLowBatteryReminderLevels[1]);
228                     Slog.d(TAG, "level          " + oldBatteryLevel + " --> " + mBatteryLevel);
229                     Slog.d(TAG, "status         " + oldBatteryStatus + " --> " + mBatteryStatus);
230                     Slog.d(TAG, "plugType       " + oldPlugType + " --> " + mPlugType);
231                     Slog.d(TAG, "invalidCharger " + oldInvalidCharger + " --> " + mInvalidCharger);
232                     Slog.d(TAG, "bucket         " + oldBucket + " --> " + bucket);
233                     Slog.d(TAG, "plugged        " + oldPlugged + " --> " + plugged);
234                 }
235 
236                 mWarnings.update(mBatteryLevel, bucket, mScreenOffTime);
237                 if (oldInvalidCharger == 0 && mInvalidCharger != 0) {
238                     Slog.d(TAG, "showing invalid charger warning");
239                     mWarnings.showInvalidChargerWarning();
240                     return;
241                 } else if (oldInvalidCharger != 0 && mInvalidCharger == 0) {
242                     mWarnings.dismissInvalidChargerWarning();
243                 } else if (mWarnings.isInvalidChargerWarningShowing()) {
244                     // if invalid charger is showing, don't show low battery
245                     return;
246                 }
247 
248                 // Show the correct version of low battery warning if needed
249                 ThreadUtils.postOnBackgroundThread(() -> {
250                     maybeShowBatteryWarning(plugged, oldPlugged, oldBucket, bucket);
251                 });
252 
253             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
254                 mScreenOffTime = SystemClock.elapsedRealtime();
255             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
256                 mScreenOffTime = -1;
257             } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
258                 mWarnings.userSwitched();
259             } else {
260                 Slog.w(TAG, "unknown intent: " + intent);
261             }
262         }
263     }
264 
maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, int bucket)265     protected void maybeShowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
266         int bucket) {
267         boolean isPowerSaver = mPowerManager.isPowerSaveMode();
268         // only play SFX when the dialog comes up or the bucket changes
269         final boolean playSound = bucket != oldBucket || oldPlugged;
270         final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
271         if (hybridEnabled) {
272             final Estimate estimate = mEnhancedEstimates.getEstimate();
273             // Turbo is not always booted once SysUI is running so we have ot make sure we actually
274             // get data back
275             if (estimate != null) {
276                 mTimeRemaining = estimate.estimateMillis;
277                 mWarnings.updateEstimate(estimate);
278                 mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(),
279                         mEnhancedEstimates.getSevereWarningThreshold());
280 
281                 // if we are now over 45% battery & 6 hours remaining we can trigger hybrid
282                 // notification again
283                 if (mBatteryLevel >= CHARGE_CYCLE_PERCENT_RESET
284                         && mTimeRemaining > SIX_HOURS_MILLIS) {
285                     mLowWarningShownThisChargeCycle = false;
286                     mSevereWarningShownThisChargeCycle = false;
287                 }
288             }
289         }
290 
291         if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket,
292                 mTimeRemaining, isPowerSaver, mBatteryStatus)) {
293             mWarnings.showLowBatteryWarning(playSound);
294 
295             // mark if we've already shown a warning this cycle. This will prevent the notification
296             // trigger from spamming users by only showing low/critical warnings once per cycle
297             if (hybridEnabled) {
298                 if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
299                         || mBatteryLevel < mLowBatteryReminderLevels[1]) {
300                     mSevereWarningShownThisChargeCycle = true;
301                 } else {
302                     mLowWarningShownThisChargeCycle = true;
303                 }
304             }
305         } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
306                 isPowerSaver)) {
307             mWarnings.dismissLowBatteryWarning();
308         } else {
309             mWarnings.updateLowBatteryWarning();
310         }
311     }
312 
313     @VisibleForTesting
shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket, int bucket, long timeRemaining, boolean isPowerSaver, int batteryStatus)314     boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
315             int bucket, long timeRemaining, boolean isPowerSaver, int batteryStatus) {
316         if (mEnhancedEstimates.isHybridNotificationEnabled()) {
317             // triggering logic when enhanced estimate is available
318             return isEnhancedTrigger(plugged, timeRemaining, isPowerSaver, batteryStatus);
319         }
320         // legacy triggering logic
321         return !plugged
322                 && !isPowerSaver
323                 && (((bucket < oldBucket || oldPlugged) && bucket < 0))
324                 && batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
325     }
326 
327     @VisibleForTesting
shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket, long timeRemaining, boolean isPowerSaver)328     boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
329             long timeRemaining, boolean isPowerSaver) {
330         final boolean hybridWouldDismiss = mEnhancedEstimates.isHybridNotificationEnabled()
331                 && timeRemaining > mEnhancedEstimates.getLowWarningThreshold();
332         final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
333         return isPowerSaver
334                 || plugged
335                 || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled()
336                         || hybridWouldDismiss));
337     }
338 
isEnhancedTrigger(boolean plugged, long timeRemaining, boolean isPowerSaver, int batteryStatus)339     private boolean isEnhancedTrigger(boolean plugged, long timeRemaining, boolean isPowerSaver,
340             int batteryStatus) {
341         if (plugged || isPowerSaver || batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
342             return false;
343         }
344         int warnLevel = mLowBatteryReminderLevels[0];
345         int critLevel = mLowBatteryReminderLevels[1];
346 
347         // Only show the low warning once per charge cycle
348         final boolean canShowWarning = !mLowWarningShownThisChargeCycle
349                 && (timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
350                         || mBatteryLevel <= warnLevel);
351 
352         // Only show the severe warning once per charge cycle
353         final boolean canShowSevereWarning = !mSevereWarningShownThisChargeCycle
354                 && (timeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
355                         || mBatteryLevel <= critLevel);
356 
357         return canShowWarning || canShowSevereWarning;
358     }
359 
initTemperatureWarning()360     private void initTemperatureWarning() {
361         ContentResolver resolver = mContext.getContentResolver();
362         Resources resources = mContext.getResources();
363         if (Settings.Global.getInt(resolver, Settings.Global.SHOW_TEMPERATURE_WARNING,
364                 resources.getInteger(R.integer.config_showTemperatureWarning)) == 0) {
365             return;
366         }
367 
368         mThresholdTemp = Settings.Global.getFloat(resolver, Settings.Global.WARNING_TEMPERATURE,
369                 resources.getInteger(R.integer.config_warningTemperature));
370 
371         if (mThresholdTemp < 0f) {
372             // Get the shutdown temperature, adjust for warning tolerance.
373             float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
374                     HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
375                     HardwarePropertiesManager.TEMPERATURE_SHUTDOWN);
376             if (throttlingTemps == null
377                     || throttlingTemps.length == 0
378                     || throttlingTemps[0] == HardwarePropertiesManager.UNDEFINED_TEMPERATURE) {
379                 return;
380             }
381             mThresholdTemp = throttlingTemps[0] -
382                     resources.getInteger(R.integer.config_warningTemperatureTolerance);
383         }
384 
385         if (mThermalService == null) {
386             // Enable push notifications of throttling from vendor thermal
387             // management subsystem via thermalservice, in addition to our
388             // usual polling, to react to temperature jumps more quickly.
389             IBinder b = ServiceManager.getService("thermalservice");
390 
391             if (b != null) {
392                 mThermalService = IThermalService.Stub.asInterface(b);
393                 try {
394                     mThermalService.registerThermalEventListener(
395                         new ThermalEventListener());
396                 } catch (RemoteException e) {
397                     // Should never happen.
398                 }
399             } else {
400                 Slog.w(TAG, "cannot find thermalservice, no throttling push notifications");
401             }
402         }
403 
404         setNextLogTime();
405 
406         // This initialization method may be called on a configuration change. Only one set of
407         // ongoing callbacks should be occurring, so remove any now. updateTemperatureWarning will
408         // schedule an ongoing callback.
409         mHandler.removeCallbacks(mUpdateTempCallback);
410 
411         // We have passed all of the checks, start checking the temp
412         updateTemperatureWarning();
413     }
414 
showThermalShutdownDialog()415     private void showThermalShutdownDialog() {
416         if (mPowerManager.getLastShutdownReason()
417                 == PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN) {
418             mWarnings.showThermalShutdownWarning();
419         }
420     }
421 
422     @VisibleForTesting
updateTemperatureWarning()423     protected void updateTemperatureWarning() {
424         float[] temps = mHardwarePropertiesManager.getDeviceTemperatures(
425                 HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
426                 HardwarePropertiesManager.TEMPERATURE_CURRENT);
427         if (temps.length != 0) {
428             float temp = temps[0];
429             mRecentTemps[mNumTemps++] = temp;
430 
431             StatusBar statusBar = getComponent(StatusBar.class);
432             if (statusBar != null && !statusBar.isDeviceInVrMode()
433                     && temp >= mThresholdTemp) {
434                 logAtTemperatureThreshold(temp);
435                 mWarnings.showHighTemperatureWarning();
436             } else {
437                 mWarnings.dismissHighTemperatureWarning();
438             }
439         }
440 
441         logTemperatureStats();
442 
443         mHandler.postDelayed(mUpdateTempCallback, TEMPERATURE_INTERVAL);
444     }
445 
logAtTemperatureThreshold(float temp)446     private void logAtTemperatureThreshold(float temp) {
447         StringBuilder sb = new StringBuilder();
448         sb.append("currentTemp=").append(temp)
449                 .append(",thresholdTemp=").append(mThresholdTemp)
450                 .append(",batteryStatus=").append(mBatteryStatus)
451                 .append(",recentTemps=");
452         for (int i = 0; i < mNumTemps; i++) {
453             sb.append(mRecentTemps[i]).append(',');
454         }
455         Slog.i(TAG, sb.toString());
456     }
457 
458     /**
459      * Calculates and logs min, max, and average
460      * {@link HardwarePropertiesManager#DEVICE_TEMPERATURE_SKIN} over the past
461      * {@link #TEMPERATURE_LOGGING_INTERVAL}.
462      */
logTemperatureStats()463     private void logTemperatureStats() {
464         if (mNextLogTime > System.currentTimeMillis() && mNumTemps != MAX_RECENT_TEMPS) {
465             return;
466         }
467 
468         if (mNumTemps > 0) {
469             float sum = mRecentTemps[0], min = mRecentTemps[0], max = mRecentTemps[0];
470             for (int i = 1; i < mNumTemps; i++) {
471                 float temp = mRecentTemps[i];
472                 sum += temp;
473                 if (temp > max) {
474                     max = temp;
475                 }
476                 if (temp < min) {
477                     min = temp;
478                 }
479             }
480 
481             float avg = sum / mNumTemps;
482             Slog.i(TAG, "avg=" + avg + ",min=" + min + ",max=" + max);
483             MetricsLogger.histogram(mContext, "device_skin_temp_avg", (int) avg);
484             MetricsLogger.histogram(mContext, "device_skin_temp_min", (int) min);
485             MetricsLogger.histogram(mContext, "device_skin_temp_max", (int) max);
486         }
487         setNextLogTime();
488         mNumTemps = 0;
489     }
490 
setNextLogTime()491     private void setNextLogTime() {
492         mNextLogTime = System.currentTimeMillis() + TEMPERATURE_LOGGING_INTERVAL;
493     }
494 
dump(FileDescriptor fd, PrintWriter pw, String[] args)495     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
496         pw.print("mLowBatteryAlertCloseLevel=");
497         pw.println(mLowBatteryAlertCloseLevel);
498         pw.print("mLowBatteryReminderLevels=");
499         pw.println(Arrays.toString(mLowBatteryReminderLevels));
500         pw.print("mBatteryLevel=");
501         pw.println(Integer.toString(mBatteryLevel));
502         pw.print("mBatteryStatus=");
503         pw.println(Integer.toString(mBatteryStatus));
504         pw.print("mPlugType=");
505         pw.println(Integer.toString(mPlugType));
506         pw.print("mInvalidCharger=");
507         pw.println(Integer.toString(mInvalidCharger));
508         pw.print("mScreenOffTime=");
509         pw.print(mScreenOffTime);
510         if (mScreenOffTime >= 0) {
511             pw.print(" (");
512             pw.print(SystemClock.elapsedRealtime() - mScreenOffTime);
513             pw.print(" ago)");
514         }
515         pw.println();
516         pw.print("soundTimeout=");
517         pw.println(Settings.Global.getInt(mContext.getContentResolver(),
518                 Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0));
519         pw.print("bucket: ");
520         pw.println(Integer.toString(findBatteryLevelBucket(mBatteryLevel)));
521         pw.print("mThresholdTemp=");
522         pw.println(Float.toString(mThresholdTemp));
523         pw.print("mNextLogTime=");
524         pw.println(Long.toString(mNextLogTime));
525         mWarnings.dump(pw);
526     }
527 
528     public interface WarningsUI {
update(int batteryLevel, int bucket, long screenOffTime)529         void update(int batteryLevel, int bucket, long screenOffTime);
updateEstimate(Estimate estimate)530         void updateEstimate(Estimate estimate);
updateThresholds(long lowThreshold, long severeThreshold)531         void updateThresholds(long lowThreshold, long severeThreshold);
dismissLowBatteryWarning()532         void dismissLowBatteryWarning();
showLowBatteryWarning(boolean playSound)533         void showLowBatteryWarning(boolean playSound);
dismissInvalidChargerWarning()534         void dismissInvalidChargerWarning();
showInvalidChargerWarning()535         void showInvalidChargerWarning();
updateLowBatteryWarning()536         void updateLowBatteryWarning();
isInvalidChargerWarningShowing()537         boolean isInvalidChargerWarningShowing();
dismissHighTemperatureWarning()538         void dismissHighTemperatureWarning();
showHighTemperatureWarning()539         void showHighTemperatureWarning();
showThermalShutdownWarning()540         void showThermalShutdownWarning();
dump(PrintWriter pw)541         void dump(PrintWriter pw);
userSwitched()542         void userSwitched();
543     }
544 
545     // Thermal event received from vendor thermal management subsystem
546     private final class ThermalEventListener extends IThermalEventListener.Stub {
notifyThrottling(boolean isThrottling, Temperature temp)547         @Override public void notifyThrottling(boolean isThrottling, Temperature temp) {
548             // Trigger an update of the temperature warning.  Only one
549             // callback can be enabled at a time, so remove any existing
550             // callback; updateTemperatureWarning will schedule another one.
551             mHandler.removeCallbacks(mUpdateTempCallback);
552             updateTemperatureWarning();
553         }
554     }
555 }
556