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