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