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