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