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