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