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.database.ContentObserver;
27 import android.os.BatteryManager;
28 import android.os.Handler;
29 import android.os.IThermalEventListener;
30 import android.os.IThermalService;
31 import android.os.PowerManager;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.SystemClock;
35 import android.os.Temperature;
36 import android.os.UserHandle;
37 import android.provider.Settings;
38 import android.text.format.DateUtils;
39 import android.util.Log;
40 import android.util.Slog;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.settingslib.fuelgauge.Estimate;
44 import com.android.settingslib.utils.ThreadUtils;
45 import com.android.systemui.Dependency;
46 import com.android.systemui.R;
47 import com.android.systemui.SystemUI;
48 import com.android.systemui.statusbar.phone.StatusBar;
49 
50 import java.io.FileDescriptor;
51 import java.io.PrintWriter;
52 import java.time.Duration;
53 import java.util.Arrays;
54 import java.util.concurrent.Future;
55 
56 public class PowerUI extends SystemUI {
57 
58     static final String TAG = "PowerUI";
59     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
60     private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
61     private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
62     private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
63     static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
64     private static final int CHARGE_CYCLE_PERCENT_RESET = 45;
65     private static final long SIX_HOURS_MILLIS = Duration.ofHours(6).toMillis();
66     public static final int NO_ESTIMATE_AVAILABLE = -1;
67     private static final String BOOT_COUNT_KEY = "boot_count";
68     private static final String PREFS = "powerui_prefs";
69 
70     private final Handler mHandler = new Handler();
71     @VisibleForTesting
72     final Receiver mReceiver = new Receiver();
73 
74     private PowerManager mPowerManager;
75     private WarningsUI mWarnings;
76     private final Configuration mLastConfiguration = new Configuration();
77     private int mPlugType = 0;
78     private int mInvalidCharger = 0;
79     private EnhancedEstimates mEnhancedEstimates;
80     private Future mLastShowWarningTask;
81     private boolean mEnableSkinTemperatureWarning;
82     private boolean mEnableUsbTemperatureAlarm;
83 
84     private int mLowBatteryAlertCloseLevel;
85     private final int[] mLowBatteryReminderLevels = new int[2];
86 
87     private long mScreenOffTime = -1;
88 
89     @VisibleForTesting boolean mLowWarningShownThisChargeCycle;
90     @VisibleForTesting boolean mSevereWarningShownThisChargeCycle;
91     @VisibleForTesting BatteryStateSnapshot mCurrentBatteryStateSnapshot;
92     @VisibleForTesting BatteryStateSnapshot mLastBatteryStateSnapshot;
93     @VisibleForTesting IThermalService mThermalService;
94 
95     @VisibleForTesting int mBatteryLevel = 100;
96     @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
97 
98     private IThermalEventListener mSkinThermalEventListener;
99     private IThermalEventListener mUsbThermalEventListener;
100 
start()101     public void start() {
102         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
103         mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
104         mWarnings = Dependency.get(WarningsUI.class);
105         mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
106         mLastConfiguration.setTo(mContext.getResources().getConfiguration());
107 
108         ContentObserver obs = new ContentObserver(mHandler) {
109             @Override
110             public void onChange(boolean selfChange) {
111                 updateBatteryWarningLevels();
112             }
113         };
114         final ContentResolver resolver = mContext.getContentResolver();
115         resolver.registerContentObserver(Settings.Global.getUriFor(
116                 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
117                 false, obs, UserHandle.USER_ALL);
118         updateBatteryWarningLevels();
119         mReceiver.init();
120 
121         // Check to see if we need to let the user know that the phone previously shut down due
122         // to the temperature being too high.
123         showWarnOnThermalShutdown();
124 
125         // Register an observer to configure mEnableSkinTemperatureWarning and perform the
126         // registration of skin thermal event listener upon Settings change.
127         resolver.registerContentObserver(
128                 Settings.Global.getUriFor(Settings.Global.SHOW_TEMPERATURE_WARNING),
129                 false /*notifyForDescendants*/,
130                 new ContentObserver(mHandler) {
131                     @Override
132                     public void onChange(boolean selfChange) {
133                         doSkinThermalEventListenerRegistration();
134                     }
135                 });
136         // Register an observer to configure mEnableUsbTemperatureAlarm and perform the
137         // registration of usb thermal event listener upon Settings change.
138         resolver.registerContentObserver(
139                 Settings.Global.getUriFor(Settings.Global.SHOW_USB_TEMPERATURE_ALARM),
140                 false /*notifyForDescendants*/,
141                 new ContentObserver(mHandler) {
142                     @Override
143                     public void onChange(boolean selfChange) {
144                         doUsbThermalEventListenerRegistration();
145                     }
146                 });
147         initThermalEventListeners();
148     }
149 
150     @Override
onConfigurationChanged(Configuration newConfig)151     protected void onConfigurationChanged(Configuration newConfig) {
152         final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
153 
154         // Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
155         if ((mLastConfiguration.updateFrom(newConfig) & mask) != 0) {
156             mHandler.post(this::initThermalEventListeners);
157         }
158     }
159 
updateBatteryWarningLevels()160     void updateBatteryWarningLevels() {
161         int critLevel = mContext.getResources().getInteger(
162                 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
163         int warnLevel = mContext.getResources().getInteger(
164                 com.android.internal.R.integer.config_lowBatteryWarningLevel);
165 
166         if (warnLevel < critLevel) {
167             warnLevel = critLevel;
168         }
169 
170         mLowBatteryReminderLevels[0] = warnLevel;
171         mLowBatteryReminderLevels[1] = critLevel;
172         mLowBatteryAlertCloseLevel = mLowBatteryReminderLevels[0]
173                 + mContext.getResources().getInteger(
174                         com.android.internal.R.integer.config_lowBatteryCloseWarningBump);
175     }
176 
177     /**
178      * Buckets the battery level.
179      *
180      * The code in this function is a little weird because I couldn't comprehend
181      * the bucket going up when the battery level was going down. --joeo
182      *
183      * 1 means that the battery is "ok"
184      * 0 means that the battery is between "ok" and what we should warn about.
185      * less than 0 means that the battery is low
186      */
findBatteryLevelBucket(int level)187     private int findBatteryLevelBucket(int level) {
188         if (level >= mLowBatteryAlertCloseLevel) {
189             return 1;
190         }
191         if (level > mLowBatteryReminderLevels[0]) {
192             return 0;
193         }
194         final int N = mLowBatteryReminderLevels.length;
195         for (int i=N-1; i>=0; i--) {
196             if (level <= mLowBatteryReminderLevels[i]) {
197                 return -1-i;
198             }
199         }
200         throw new RuntimeException("not possible!");
201     }
202 
203     @VisibleForTesting
204     final class Receiver extends BroadcastReceiver {
205 
init()206         public void init() {
207             // Register for Intent broadcasts for...
208             IntentFilter filter = new IntentFilter();
209             filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
210             filter.addAction(Intent.ACTION_BATTERY_CHANGED);
211             filter.addAction(Intent.ACTION_SCREEN_OFF);
212             filter.addAction(Intent.ACTION_SCREEN_ON);
213             filter.addAction(Intent.ACTION_USER_SWITCHED);
214             mContext.registerReceiver(this, filter, null, mHandler);
215         }
216 
217         @Override
onReceive(Context context, Intent intent)218         public void onReceive(Context context, Intent intent) {
219             String action = intent.getAction();
220             if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action)) {
221                 ThreadUtils.postOnBackgroundThread(() -> {
222                     if (mPowerManager.isPowerSaveMode()) {
223                         mWarnings.dismissLowBatteryWarning();
224                     }
225                 });
226             } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
227                 final int oldBatteryLevel = mBatteryLevel;
228                 mBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 100);
229                 final int oldBatteryStatus = mBatteryStatus;
230                 mBatteryStatus = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
231                         BatteryManager.BATTERY_STATUS_UNKNOWN);
232                 final int oldPlugType = mPlugType;
233                 mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1);
234                 final int oldInvalidCharger = mInvalidCharger;
235                 mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
236                 mLastBatteryStateSnapshot = mCurrentBatteryStateSnapshot;
237 
238                 final boolean plugged = mPlugType != 0;
239                 final boolean oldPlugged = oldPlugType != 0;
240 
241                 int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
242                 int bucket = findBatteryLevelBucket(mBatteryLevel);
243 
244                 if (DEBUG) {
245                     Slog.d(TAG, "buckets   ....." + mLowBatteryAlertCloseLevel
246                             + " .. " + mLowBatteryReminderLevels[0]
247                             + " .. " + mLowBatteryReminderLevels[1]);
248                     Slog.d(TAG, "level          " + oldBatteryLevel + " --> " + mBatteryLevel);
249                     Slog.d(TAG, "status         " + oldBatteryStatus + " --> " + mBatteryStatus);
250                     Slog.d(TAG, "plugType       " + oldPlugType + " --> " + mPlugType);
251                     Slog.d(TAG, "invalidCharger " + oldInvalidCharger + " --> " + mInvalidCharger);
252                     Slog.d(TAG, "bucket         " + oldBucket + " --> " + bucket);
253                     Slog.d(TAG, "plugged        " + oldPlugged + " --> " + plugged);
254                 }
255 
256                 mWarnings.update(mBatteryLevel, bucket, mScreenOffTime);
257                 if (oldInvalidCharger == 0 && mInvalidCharger != 0) {
258                     Slog.d(TAG, "showing invalid charger warning");
259                     mWarnings.showInvalidChargerWarning();
260                     return;
261                 } else if (oldInvalidCharger != 0 && mInvalidCharger == 0) {
262                     mWarnings.dismissInvalidChargerWarning();
263                 } else if (mWarnings.isInvalidChargerWarningShowing()) {
264                     // if invalid charger is showing, don't show low battery
265                     if (DEBUG) {
266                         Slog.d(TAG, "Bad Charger");
267                     }
268                     return;
269                 }
270 
271                 // Show the correct version of low battery warning if needed
272                 if (mLastShowWarningTask != null) {
273                     mLastShowWarningTask.cancel(true);
274                     if (DEBUG) {
275                         Slog.d(TAG, "cancelled task");
276                     }
277                 }
278                 mLastShowWarningTask = ThreadUtils.postOnBackgroundThread(() -> {
279                     maybeShowBatteryWarningV2(
280                             plugged, bucket);
281                 });
282 
283             } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
284                 mScreenOffTime = SystemClock.elapsedRealtime();
285             } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
286                 mScreenOffTime = -1;
287             } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
288                 mWarnings.userSwitched();
289             } else {
290                 Slog.w(TAG, "unknown intent: " + intent);
291             }
292         }
293     }
294 
maybeShowBatteryWarningV2(boolean plugged, int bucket)295     protected void maybeShowBatteryWarningV2(boolean plugged, int bucket) {
296         final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
297         final boolean isPowerSaverMode = mPowerManager.isPowerSaveMode();
298 
299         // Stick current battery state into an immutable container to determine if we should show
300         // a warning.
301         if (DEBUG) {
302             Slog.d(TAG, "evaluating which notification to show");
303         }
304         if (hybridEnabled) {
305             if (DEBUG) {
306                 Slog.d(TAG, "using hybrid");
307             }
308             Estimate estimate = refreshEstimateIfNeeded();
309             mCurrentBatteryStateSnapshot = new BatteryStateSnapshot(mBatteryLevel, isPowerSaverMode,
310                     plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1],
311                     mLowBatteryReminderLevels[0], estimate.getEstimateMillis(),
312                     estimate.getAverageDischargeTime(),
313                     mEnhancedEstimates.getSevereWarningThreshold(),
314                     mEnhancedEstimates.getLowWarningThreshold(), estimate.isBasedOnUsage(),
315                     mEnhancedEstimates.getLowWarningEnabled());
316         } else {
317             if (DEBUG) {
318                 Slog.d(TAG, "using standard");
319             }
320             mCurrentBatteryStateSnapshot = new BatteryStateSnapshot(mBatteryLevel, isPowerSaverMode,
321                     plugged, bucket, mBatteryStatus, mLowBatteryReminderLevels[1],
322                     mLowBatteryReminderLevels[0]);
323         }
324 
325         mWarnings.updateSnapshot(mCurrentBatteryStateSnapshot);
326         if (mCurrentBatteryStateSnapshot.isHybrid()) {
327             maybeShowHybridWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot);
328         } else {
329             maybeShowBatteryWarning(mCurrentBatteryStateSnapshot, mLastBatteryStateSnapshot);
330         }
331     }
332 
333     // updates the time estimate if we don't have one or battery level has changed.
334     @VisibleForTesting
refreshEstimateIfNeeded()335     Estimate refreshEstimateIfNeeded() {
336         if (mLastBatteryStateSnapshot == null
337                 || mLastBatteryStateSnapshot.getTimeRemainingMillis() == NO_ESTIMATE_AVAILABLE
338                 || mBatteryLevel != mLastBatteryStateSnapshot.getBatteryLevel()) {
339             final Estimate estimate = mEnhancedEstimates.getEstimate();
340             if (DEBUG) {
341                 Slog.d(TAG, "updated estimate: " + estimate.getEstimateMillis());
342             }
343             return estimate;
344         }
345         return new Estimate(mLastBatteryStateSnapshot.getTimeRemainingMillis(),
346                 mLastBatteryStateSnapshot.isBasedOnUsage(),
347                 mLastBatteryStateSnapshot.getAverageTimeToDischargeMillis());
348     }
349 
350     @VisibleForTesting
maybeShowHybridWarning(BatteryStateSnapshot currentSnapshot, BatteryStateSnapshot lastSnapshot)351     void maybeShowHybridWarning(BatteryStateSnapshot currentSnapshot,
352             BatteryStateSnapshot lastSnapshot) {
353         // if we are now over 45% battery & 6 hours remaining so we can trigger hybrid
354         // notification again
355         if (currentSnapshot.getBatteryLevel() >= CHARGE_CYCLE_PERCENT_RESET
356                 && currentSnapshot.getTimeRemainingMillis() > SIX_HOURS_MILLIS) {
357             mLowWarningShownThisChargeCycle = false;
358             mSevereWarningShownThisChargeCycle = false;
359             if (DEBUG) {
360                 Slog.d(TAG, "Charge cycle reset! Can show warnings again");
361             }
362         }
363 
364         final boolean playSound = currentSnapshot.getBucket() != lastSnapshot.getBucket()
365                 || lastSnapshot.getPlugged();
366 
367         if (shouldShowHybridWarning(currentSnapshot)) {
368             mWarnings.showLowBatteryWarning(playSound);
369             // mark if we've already shown a warning this cycle. This will prevent the notification
370             // trigger from spamming users by only showing low/critical warnings once per cycle
371             if (currentSnapshot.getTimeRemainingMillis()
372                     <= currentSnapshot.getSevereThresholdMillis()
373                     || currentSnapshot.getBatteryLevel()
374                     <= currentSnapshot.getSevereLevelThreshold()) {
375                 mSevereWarningShownThisChargeCycle = true;
376                 mLowWarningShownThisChargeCycle = true;
377                 if (DEBUG) {
378                     Slog.d(TAG, "Severe warning marked as shown this cycle");
379                 }
380             } else {
381                 Slog.d(TAG, "Low warning marked as shown this cycle");
382                 mLowWarningShownThisChargeCycle = true;
383             }
384         } else if (shouldDismissHybridWarning(currentSnapshot)) {
385             if (DEBUG) {
386                 Slog.d(TAG, "Dismissing warning");
387             }
388             mWarnings.dismissLowBatteryWarning();
389         } else {
390             if (DEBUG) {
391                 Slog.d(TAG, "Updating warning");
392             }
393             mWarnings.updateLowBatteryWarning();
394         }
395     }
396 
397     @VisibleForTesting
shouldShowHybridWarning(BatteryStateSnapshot snapshot)398     boolean shouldShowHybridWarning(BatteryStateSnapshot snapshot) {
399         if (snapshot.getPlugged()
400                 || snapshot.getBatteryStatus() == BatteryManager.BATTERY_STATUS_UNKNOWN) {
401             Slog.d(TAG, "can't show warning due to - plugged: " + snapshot.getPlugged()
402                     + " status unknown: "
403                     + (snapshot.getBatteryStatus() == BatteryManager.BATTERY_STATUS_UNKNOWN));
404             return false;
405         }
406 
407         // Only show the low warning if enabled once per charge cycle & no battery saver
408         final boolean canShowWarning = snapshot.isLowWarningEnabled()
409                 && !mLowWarningShownThisChargeCycle && !snapshot.isPowerSaver()
410                 && (snapshot.getTimeRemainingMillis() < snapshot.getLowThresholdMillis()
411                 || snapshot.getBatteryLevel() <= snapshot.getLowLevelThreshold());
412 
413         // Only show the severe warning once per charge cycle
414         final boolean canShowSevereWarning = !mSevereWarningShownThisChargeCycle
415                 && (snapshot.getTimeRemainingMillis() < snapshot.getSevereThresholdMillis()
416                 || snapshot.getBatteryLevel() <= snapshot.getSevereLevelThreshold());
417 
418         final boolean canShow = canShowWarning || canShowSevereWarning;
419 
420         if (DEBUG) {
421             Slog.d(TAG, "Enhanced trigger is: " + canShow + "\nwith battery snapshot:"
422                     + " mLowWarningShownThisChargeCycle: " + mLowWarningShownThisChargeCycle
423                     + " mSevereWarningShownThisChargeCycle: " + mSevereWarningShownThisChargeCycle
424                     + "\n" + snapshot.toString());
425         }
426         return canShow;
427     }
428 
429     @VisibleForTesting
shouldDismissHybridWarning(BatteryStateSnapshot snapshot)430     boolean shouldDismissHybridWarning(BatteryStateSnapshot snapshot) {
431         return snapshot.getPlugged()
432                 || snapshot.getTimeRemainingMillis() > snapshot.getLowThresholdMillis();
433     }
434 
maybeShowBatteryWarning( BatteryStateSnapshot currentSnapshot, BatteryStateSnapshot lastSnapshot)435     protected void maybeShowBatteryWarning(
436             BatteryStateSnapshot currentSnapshot,
437             BatteryStateSnapshot lastSnapshot) {
438         final boolean playSound = currentSnapshot.getBucket() != lastSnapshot.getBucket()
439                 || lastSnapshot.getPlugged();
440 
441         if (shouldShowLowBatteryWarning(currentSnapshot, lastSnapshot)) {
442             mWarnings.showLowBatteryWarning(playSound);
443         } else if (shouldDismissLowBatteryWarning(currentSnapshot, lastSnapshot)) {
444             mWarnings.dismissLowBatteryWarning();
445         } else {
446             mWarnings.updateLowBatteryWarning();
447         }
448     }
449 
450     @VisibleForTesting
shouldShowLowBatteryWarning( BatteryStateSnapshot currentSnapshot, BatteryStateSnapshot lastSnapshot)451     boolean shouldShowLowBatteryWarning(
452             BatteryStateSnapshot currentSnapshot,
453             BatteryStateSnapshot lastSnapshot) {
454         return !currentSnapshot.getPlugged()
455                 && !currentSnapshot.isPowerSaver()
456                 && (((currentSnapshot.getBucket() < lastSnapshot.getBucket()
457                         || lastSnapshot.getPlugged())
458                 && currentSnapshot.getBucket() < 0))
459                 && currentSnapshot.getBatteryStatus() != BatteryManager.BATTERY_STATUS_UNKNOWN;
460     }
461 
462     @VisibleForTesting
shouldDismissLowBatteryWarning( BatteryStateSnapshot currentSnapshot, BatteryStateSnapshot lastSnapshot)463     boolean shouldDismissLowBatteryWarning(
464             BatteryStateSnapshot currentSnapshot,
465             BatteryStateSnapshot lastSnapshot) {
466         return currentSnapshot.isPowerSaver()
467                 || currentSnapshot.getPlugged()
468                 || (currentSnapshot.getBucket() > lastSnapshot.getBucket()
469                         && currentSnapshot.getBucket() > 0);
470     }
471 
initThermalEventListeners()472     private void initThermalEventListeners() {
473         doSkinThermalEventListenerRegistration();
474         doUsbThermalEventListenerRegistration();
475     }
476 
477     @VisibleForTesting
doSkinThermalEventListenerRegistration()478     synchronized void doSkinThermalEventListenerRegistration() {
479         final boolean oldEnableSkinTemperatureWarning = mEnableSkinTemperatureWarning;
480         boolean ret = false;
481 
482         mEnableSkinTemperatureWarning = Settings.Global.getInt(mContext.getContentResolver(),
483             Settings.Global.SHOW_TEMPERATURE_WARNING,
484             mContext.getResources().getInteger(R.integer.config_showTemperatureWarning)) != 0;
485 
486         if (mEnableSkinTemperatureWarning != oldEnableSkinTemperatureWarning) {
487             try {
488                 if (mSkinThermalEventListener == null) {
489                     mSkinThermalEventListener = new SkinThermalEventListener();
490                 }
491                 if (mThermalService == null) {
492                     mThermalService = IThermalService.Stub.asInterface(
493                         ServiceManager.getService(Context.THERMAL_SERVICE));
494                 }
495                 if (mEnableSkinTemperatureWarning) {
496                     ret = mThermalService.registerThermalEventListenerWithType(
497                             mSkinThermalEventListener, Temperature.TYPE_SKIN);
498                 } else {
499                     ret = mThermalService.unregisterThermalEventListener(mSkinThermalEventListener);
500                 }
501             } catch (RemoteException e) {
502                 Slog.e(TAG, "Exception while (un)registering skin thermal event listener.", e);
503             }
504 
505             if (!ret) {
506                 mEnableSkinTemperatureWarning = !mEnableSkinTemperatureWarning;
507                 Slog.e(TAG, "Failed to register or unregister skin thermal event listener.");
508             }
509         }
510     }
511 
512     @VisibleForTesting
doUsbThermalEventListenerRegistration()513     synchronized void doUsbThermalEventListenerRegistration() {
514         final boolean oldEnableUsbTemperatureAlarm = mEnableUsbTemperatureAlarm;
515         boolean ret = false;
516 
517         mEnableUsbTemperatureAlarm = Settings.Global.getInt(mContext.getContentResolver(),
518             Settings.Global.SHOW_USB_TEMPERATURE_ALARM,
519             mContext.getResources().getInteger(R.integer.config_showUsbPortAlarm)) != 0;
520 
521         if (mEnableUsbTemperatureAlarm != oldEnableUsbTemperatureAlarm) {
522             try {
523                 if (mUsbThermalEventListener == null) {
524                     mUsbThermalEventListener = new UsbThermalEventListener();
525                 }
526                 if (mThermalService == null) {
527                     mThermalService = IThermalService.Stub.asInterface(
528                         ServiceManager.getService(Context.THERMAL_SERVICE));
529                 }
530                 if (mEnableUsbTemperatureAlarm) {
531                     ret = mThermalService.registerThermalEventListenerWithType(
532                             mUsbThermalEventListener, Temperature.TYPE_USB_PORT);
533                 } else {
534                     ret = mThermalService.unregisterThermalEventListener(mUsbThermalEventListener);
535                 }
536             } catch (RemoteException e) {
537                 Slog.e(TAG, "Exception while (un)registering usb thermal event listener.", e);
538             }
539 
540             if (!ret) {
541                 mEnableUsbTemperatureAlarm = !mEnableUsbTemperatureAlarm;
542                 Slog.e(TAG, "Failed to register or unregister usb thermal event listener.");
543             }
544         }
545     }
546 
showWarnOnThermalShutdown()547     private void showWarnOnThermalShutdown() {
548         int bootCount = -1;
549         int lastReboot = mContext.getSharedPreferences(PREFS, 0).getInt(BOOT_COUNT_KEY, -1);
550         try {
551             bootCount = Settings.Global.getInt(mContext.getContentResolver(),
552                     Settings.Global.BOOT_COUNT);
553         } catch (Settings.SettingNotFoundException e) {
554             Slog.e(TAG, "Failed to read system boot count from Settings.Global.BOOT_COUNT");
555         }
556         // Only show the thermal shutdown warning when there is a thermal reboot.
557         if (bootCount > lastReboot) {
558             mContext.getSharedPreferences(PREFS, 0).edit().putInt(BOOT_COUNT_KEY,
559                     bootCount).apply();
560             if (mPowerManager.getLastShutdownReason()
561                     == PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN) {
562                 mWarnings.showThermalShutdownWarning();
563             }
564         }
565     }
566 
dump(FileDescriptor fd, PrintWriter pw, String[] args)567     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
568         pw.print("mLowBatteryAlertCloseLevel=");
569         pw.println(mLowBatteryAlertCloseLevel);
570         pw.print("mLowBatteryReminderLevels=");
571         pw.println(Arrays.toString(mLowBatteryReminderLevels));
572         pw.print("mBatteryLevel=");
573         pw.println(Integer.toString(mBatteryLevel));
574         pw.print("mBatteryStatus=");
575         pw.println(Integer.toString(mBatteryStatus));
576         pw.print("mPlugType=");
577         pw.println(Integer.toString(mPlugType));
578         pw.print("mInvalidCharger=");
579         pw.println(Integer.toString(mInvalidCharger));
580         pw.print("mScreenOffTime=");
581         pw.print(mScreenOffTime);
582         if (mScreenOffTime >= 0) {
583             pw.print(" (");
584             pw.print(SystemClock.elapsedRealtime() - mScreenOffTime);
585             pw.print(" ago)");
586         }
587         pw.println();
588         pw.print("soundTimeout=");
589         pw.println(Settings.Global.getInt(mContext.getContentResolver(),
590                 Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0));
591         pw.print("bucket: ");
592         pw.println(Integer.toString(findBatteryLevelBucket(mBatteryLevel)));
593         pw.print("mEnableSkinTemperatureWarning=");
594         pw.println(mEnableSkinTemperatureWarning);
595         pw.print("mEnableUsbTemperatureAlarm=");
596         pw.println(mEnableUsbTemperatureAlarm);
597         mWarnings.dump(pw);
598     }
599 
600     /**
601      * The interface to allow PowerUI to communicate with whatever implementation of WarningsUI
602      * is being used by the system.
603      */
604     public interface WarningsUI {
605 
606         /**
607          * Updates battery and screen info for determining whether to trigger battery warnings or
608          * not.
609          * @param batteryLevel The current battery level
610          * @param bucket The current battery bucket
611          * @param screenOffTime How long the screen has been off in millis
612          */
update(int batteryLevel, int bucket, long screenOffTime)613         void update(int batteryLevel, int bucket, long screenOffTime);
614 
dismissLowBatteryWarning()615         void dismissLowBatteryWarning();
616 
showLowBatteryWarning(boolean playSound)617         void showLowBatteryWarning(boolean playSound);
618 
dismissInvalidChargerWarning()619         void dismissInvalidChargerWarning();
620 
showInvalidChargerWarning()621         void showInvalidChargerWarning();
622 
updateLowBatteryWarning()623         void updateLowBatteryWarning();
624 
isInvalidChargerWarningShowing()625         boolean isInvalidChargerWarningShowing();
626 
dismissHighTemperatureWarning()627         void dismissHighTemperatureWarning();
628 
showHighTemperatureWarning()629         void showHighTemperatureWarning();
630 
631         /**
632          * Display USB port overheat alarm
633          */
showUsbHighTemperatureAlarm()634         void showUsbHighTemperatureAlarm();
635 
showThermalShutdownWarning()636         void showThermalShutdownWarning();
637 
dump(PrintWriter pw)638         void dump(PrintWriter pw);
639 
userSwitched()640         void userSwitched();
641 
642         /**
643          * Updates the snapshot of battery state used for evaluating battery warnings
644          * @param snapshot object containing relevant values for making battery warning decisions.
645          */
updateSnapshot(BatteryStateSnapshot snapshot)646         void updateSnapshot(BatteryStateSnapshot snapshot);
647     }
648 
649     // Skin thermal event received from thermal service manager subsystem
650     @VisibleForTesting
651     final class SkinThermalEventListener extends IThermalEventListener.Stub {
notifyThrottling(Temperature temp)652         @Override public void notifyThrottling(Temperature temp) {
653             int status = temp.getStatus();
654 
655             if (status >= Temperature.THROTTLING_EMERGENCY) {
656                 StatusBar statusBar = getComponent(StatusBar.class);
657                 if (statusBar != null && !statusBar.isDeviceInVrMode()) {
658                     mWarnings.showHighTemperatureWarning();
659                     Slog.d(TAG, "SkinThermalEventListener: notifyThrottling was called "
660                             + ", current skin status = " + status
661                             + ", temperature = " + temp.getValue());
662                 }
663             } else {
664                 mWarnings.dismissHighTemperatureWarning();
665             }
666         }
667     }
668 
669     // Usb thermal event received from thermal service manager subsystem
670     @VisibleForTesting
671     final class UsbThermalEventListener extends IThermalEventListener.Stub {
notifyThrottling(Temperature temp)672         @Override public void notifyThrottling(Temperature temp) {
673             int status = temp.getStatus();
674 
675             if (status >= Temperature.THROTTLING_EMERGENCY) {
676                 mWarnings.showUsbHighTemperatureAlarm();
677                 Slog.d(TAG, "UsbThermalEventListener: notifyThrottling was called "
678                         + ", current usb port status = " + status
679                         + ", temperature = " + temp.getValue());
680             }
681         }
682     }
683 }
684