blob: 80ebb6ee9acdcc287c4873afc92458a022f05b6c [file] [log] [blame]
Joe Onorato10523b4d2010-10-25 10:42:46 -07001/*
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
17package com.android.systemui.power;
18
Joe Onorato10523b4d2010-10-25 10:42:46 -070019import android.content.BroadcastReceiver;
Dianne Hackborn14272302014-06-10 23:13:02 -070020import android.content.ContentResolver;
Joe Onorato10523b4d2010-10-25 10:42:46 -070021import android.content.Context;
Joe Onorato10523b4d2010-10-25 10:42:46 -070022import android.content.Intent;
23import android.content.IntentFilter;
Adam Lesinski1aab3fa2017-07-31 13:42:00 -070024import android.content.pm.ActivityInfo;
25import android.content.res.Configuration;
Andrew Sappersteine3352562017-01-20 15:41:03 -080026import android.content.res.Resources;
Dianne Hackborn14272302014-06-10 23:13:02 -070027import android.database.ContentObserver;
Joe Onorato4ca7f1e2010-10-27 15:32:23 -070028import android.os.BatteryManager;
Joe Onorato10523b4d2010-10-25 10:42:46 -070029import android.os.Handler;
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -080030import android.os.HardwarePropertiesManager;
Todd Poynorfb95e122017-10-04 13:00:32 -070031import android.os.IBinder;
32import android.os.IThermalEventListener;
33import android.os.IThermalService;
Daniel Sandlerdea64622013-09-23 16:05:57 -040034import android.os.PowerManager;
Todd Poynorfb95e122017-10-04 13:00:32 -070035import android.os.RemoteException;
36import android.os.ServiceManager;
Daniel Sandlerdea64622013-09-23 16:05:57 -040037import android.os.SystemClock;
Todd Poynorfb95e122017-10-04 13:00:32 -070038import android.os.Temperature;
Dianne Hackborn14272302014-06-10 23:13:02 -070039import android.os.UserHandle;
Joe Onorato10523b4d2010-10-25 10:42:46 -070040import android.provider.Settings;
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -080041import android.text.format.DateUtils;
John Spurlock1bb480a2014-08-02 17:12:43 -040042import android.util.Log;
Daniel Sandlerdea64622013-09-23 16:05:57 -040043import android.util.Slog;
Jason Monkd819c312017-08-11 12:53:36 -040044
45import com.android.internal.annotations.VisibleForTesting;
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -080046import com.android.internal.logging.MetricsLogger;
Salvador Martinez110f9a12018-01-31 09:57:17 -080047import com.android.settingslib.utils.ThreadUtils;
Jason Monkd819c312017-08-11 12:53:36 -040048import com.android.systemui.Dependency;
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -080049import com.android.systemui.R;
Joe Onorato10523b4d2010-10-25 10:42:46 -070050import com.android.systemui.SystemUI;
Jason Monk2a6ea9c2017-01-26 11:14:51 -050051import com.android.systemui.statusbar.phone.StatusBar;
Jason Monkd819c312017-08-11 12:53:36 -040052
John Spurlockde84f0e2013-06-12 12:41:00 -040053import java.io.FileDescriptor;
54import java.io.PrintWriter;
Salvador Martinezfd38aa52018-03-28 23:56:59 -070055import java.time.Duration;
John Spurlockde84f0e2013-06-12 12:41:00 -040056import java.util.Arrays;
Bill Lin32ed3d62018-10-02 18:10:09 +080057import java.util.Locale;
John Spurlockde84f0e2013-06-12 12:41:00 -040058
Joe Onorato10523b4d2010-10-25 10:42:46 -070059public class PowerUI extends SystemUI {
60 static final String TAG = "PowerUI";
John Spurlock1bb480a2014-08-02 17:12:43 -040061 static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -080062 private static final long TEMPERATURE_INTERVAL = 30 * DateUtils.SECOND_IN_MILLIS;
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -080063 private static final long TEMPERATURE_LOGGING_INTERVAL = DateUtils.HOUR_IN_MILLIS;
Bill Lin32ed3d62018-10-02 18:10:09 +080064 private static final int TEMPERATURE_OVERHEAT_WARNING = 0;
65 private static final int TEMPERATURE_OVERHEAT_ALARM = 1;
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -080066 private static final int MAX_RECENT_TEMPS = 125; // TEMPERATURE_LOGGING_INTERVAL plus a buffer
Salvador Martinezf9e47502018-01-04 13:45:48 -080067 static final long THREE_HOURS_IN_MILLIS = DateUtils.HOUR_IN_MILLIS * 3;
Salvador Martinezfd38aa52018-03-28 23:56:59 -070068 private static final int CHARGE_CYCLE_PERCENT_RESET = 45;
69 private static final long SIX_HOURS_MILLIS = Duration.ofHours(6).toMillis();
Joe Onorato10523b4d2010-10-25 10:42:46 -070070
John Spurlocked452c52014-03-06 12:02:31 -050071 private final Handler mHandler = new Handler();
Amin Shaikh2f6c45c2018-04-16 14:00:09 -040072 @VisibleForTesting
73 final Receiver mReceiver = new Receiver();
Joe Onorato4ca7f1e2010-10-27 15:32:23 -070074
John Spurlock3332ba52014-03-10 17:44:07 -040075 private PowerManager mPowerManager;
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -080076 private HardwarePropertiesManager mHardwarePropertiesManager;
John Spurlock3332ba52014-03-10 17:44:07 -040077 private WarningsUI mWarnings;
Adam Lesinski1aab3fa2017-07-31 13:42:00 -070078 private final Configuration mLastConfiguration = new Configuration();
Salvador Martinezf9e47502018-01-04 13:45:48 -080079 private long mTimeRemaining = Long.MAX_VALUE;
John Spurlocked452c52014-03-06 12:02:31 -050080 private int mPlugType = 0;
81 private int mInvalidCharger = 0;
Salvador Martinezf9e47502018-01-04 13:45:48 -080082 private EnhancedEstimates mEnhancedEstimates;
Salvador Martinez926f0712018-07-03 18:07:07 -070083 private Estimate mLastEstimate;
Salvador Martinez36307962018-02-08 14:29:08 -080084 private boolean mLowWarningShownThisChargeCycle;
85 private boolean mSevereWarningShownThisChargeCycle;
Bill Lin32ed3d62018-10-02 18:10:09 +080086 private boolean mEnableTemperatureWarning;
87 private boolean mEnableTemperatureAlarm;
88 private boolean mIsOverheatAlarming;
Joe Onorato4ca7f1e2010-10-27 15:32:23 -070089
John Spurlocked452c52014-03-06 12:02:31 -050090 private int mLowBatteryAlertCloseLevel;
91 private final int[] mLowBatteryReminderLevels = new int[2];
Joe Onorato10523b4d2010-10-25 10:42:46 -070092
Daniel Sandlerdea64622013-09-23 16:05:57 -040093 private long mScreenOffTime = -1;
94
Bill Lin32ed3d62018-10-02 18:10:09 +080095 private float mThresholdWarningTemp;
96 private float mThresholdAlarmTemp;
97 private float mThresholdAlarmTempTolerance;
98 private float[] mRecentSkinTemps = new float[MAX_RECENT_TEMPS];
99 private float[] mRecentAlarmTemps = new float[MAX_RECENT_TEMPS];
100 private int mWarningNumTemps;
101 private int mAlarmNumTemps;
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800102 private long mNextLogTime;
Todd Poynorfb95e122017-10-04 13:00:32 -0700103 private IThermalService mThermalService;
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800104
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700105 @VisibleForTesting int mBatteryLevel = 100;
Salvador Martinez36307962018-02-08 14:29:08 -0800106 @VisibleForTesting int mBatteryStatus = BatteryManager.BATTERY_STATUS_UNKNOWN;
107
Adam Lesinski1aab3fa2017-07-31 13:42:00 -0700108 // by using the same instance (method references are not guaranteed to be the same object
Salvador Martinezf9e47502018-01-04 13:45:48 -0800109 // We create a method reference here so that we are guaranteed that we can remove a callback
Adam Lesinski1aab3fa2017-07-31 13:42:00 -0700110 // each time they are created).
Bill Lin32ed3d62018-10-02 18:10:09 +0800111 private final Runnable mUpdateTempCallback = this::updateTemperature;
Adam Lesinski1aab3fa2017-07-31 13:42:00 -0700112
Joe Onorato10523b4d2010-10-25 10:42:46 -0700113 public void start() {
John Spurlock3332ba52014-03-10 17:44:07 -0400114 mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800115 mHardwarePropertiesManager = (HardwarePropertiesManager)
116 mContext.getSystemService(Context.HARDWARE_PROPERTIES_SERVICE);
John Spurlock3332ba52014-03-10 17:44:07 -0400117 mScreenOffTime = mPowerManager.isScreenOn() ? -1 : SystemClock.elapsedRealtime();
Jason Monkd819c312017-08-11 12:53:36 -0400118 mWarnings = Dependency.get(WarningsUI.class);
Salvador Martinezf9e47502018-01-04 13:45:48 -0800119 mEnhancedEstimates = Dependency.get(EnhancedEstimates.class);
Adam Lesinski1aab3fa2017-07-31 13:42:00 -0700120 mLastConfiguration.setTo(mContext.getResources().getConfiguration());
Daniel Sandlerdea64622013-09-23 16:05:57 -0400121
Dianne Hackborn14272302014-06-10 23:13:02 -0700122 ContentObserver obs = new ContentObserver(mHandler) {
123 @Override
124 public void onChange(boolean selfChange) {
125 updateBatteryWarningLevels();
126 }
127 };
128 final ContentResolver resolver = mContext.getContentResolver();
129 resolver.registerContentObserver(Settings.Global.getUriFor(
130 Settings.Global.LOW_POWER_MODE_TRIGGER_LEVEL),
131 false, obs, UserHandle.USER_ALL);
132 updateBatteryWarningLevels();
John Spurlock3332ba52014-03-10 17:44:07 -0400133 mReceiver.init();
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800134
Salvador Martineza6f7b252017-04-10 10:46:15 -0700135 // Check to see if we need to let the user know that the phone previously shut down due
136 // to the temperature being too high.
137 showThermalShutdownDialog();
138
Bill Lin32ed3d62018-10-02 18:10:09 +0800139 initTemperature();
John Spurlock3332ba52014-03-10 17:44:07 -0400140 }
Dianne Hackborn14272302014-06-10 23:13:02 -0700141
Adam Lesinski1aab3fa2017-07-31 13:42:00 -0700142 @Override
143 protected void onConfigurationChanged(Configuration newConfig) {
144 final int mask = ActivityInfo.CONFIG_MCC | ActivityInfo.CONFIG_MNC;
145
146 // Safe to modify mLastConfiguration here as it's only updated by the main thread (here).
147 if ((mLastConfiguration.updateFrom(newConfig) & mask) != 0) {
Bill Lin32ed3d62018-10-02 18:10:09 +0800148 mHandler.post(this::initTemperature);
Adam Lesinski1aab3fa2017-07-31 13:42:00 -0700149 }
150 }
151
Dianne Hackborn14272302014-06-10 23:13:02 -0700152 void updateBatteryWarningLevels() {
153 int critLevel = mContext.getResources().getInteger(
154 com.android.internal.R.integer.config_criticalBatteryWarningLevel);
Makoto Onuki16a0dd22018-03-20 10:40:37 -0700155 int warnLevel = mContext.getResources().getInteger(
Dianne Hackborn14272302014-06-10 23:13:02 -0700156 com.android.internal.R.integer.config_lowBatteryWarningLevel);
Makoto Onuki4a9036b2018-01-11 14:48:20 -0800157
Dianne Hackborn14272302014-06-10 23:13:02 -0700158 if (warnLevel < critLevel) {
159 warnLevel = critLevel;
160 }
161
162 mLowBatteryReminderLevels[0] = warnLevel;
163 mLowBatteryReminderLevels[1] = critLevel;
164 mLowBatteryAlertCloseLevel = mLowBatteryReminderLevels[0]
165 + mContext.getResources().getInteger(
166 com.android.internal.R.integer.config_lowBatteryCloseWarningBump);
167 }
168
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700169 /**
170 * Buckets the battery level.
171 *
172 * The code in this function is a little weird because I couldn't comprehend
173 * the bucket going up when the battery level was going down. --joeo
174 *
175 * 1 means that the battery is "ok"
176 * 0 means that the battery is between "ok" and what we should warn about.
177 * less than 0 means that the battery is low
178 */
179 private int findBatteryLevelBucket(int level) {
180 if (level >= mLowBatteryAlertCloseLevel) {
181 return 1;
182 }
Dianne Hackborn14272302014-06-10 23:13:02 -0700183 if (level > mLowBatteryReminderLevels[0]) {
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700184 return 0;
185 }
186 final int N = mLowBatteryReminderLevels.length;
187 for (int i=N-1; i>=0; i--) {
188 if (level <= mLowBatteryReminderLevels[i]) {
189 return -1-i;
190 }
191 }
192 throw new RuntimeException("not possible!");
193 }
194
Amin Shaikh2f6c45c2018-04-16 14:00:09 -0400195 @VisibleForTesting
196 final class Receiver extends BroadcastReceiver {
John Spurlock3332ba52014-03-10 17:44:07 -0400197
198 public void init() {
199 // Register for Intent broadcasts for...
200 IntentFilter filter = new IntentFilter();
Amin Shaikh2f6c45c2018-04-16 14:00:09 -0400201 filter.addAction(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
John Spurlock3332ba52014-03-10 17:44:07 -0400202 filter.addAction(Intent.ACTION_BATTERY_CHANGED);
203 filter.addAction(Intent.ACTION_SCREEN_OFF);
204 filter.addAction(Intent.ACTION_SCREEN_ON);
John Spurlockecbc5e82014-10-22 09:05:51 -0400205 filter.addAction(Intent.ACTION_USER_SWITCHED);
Bill Lin32ed3d62018-10-02 18:10:09 +0800206 filter.addAction(Intent.ACTION_POWER_DISCONNECTED);
John Spurlock3332ba52014-03-10 17:44:07 -0400207 mContext.registerReceiver(this, filter, null, mHandler);
John Spurlock3332ba52014-03-10 17:44:07 -0400208 }
209
Joe Onorato10523b4d2010-10-25 10:42:46 -0700210 @Override
211 public void onReceive(Context context, Intent intent) {
212 String action = intent.getAction();
Amin Shaikh2f6c45c2018-04-16 14:00:09 -0400213 if (PowerManager.ACTION_POWER_SAVE_MODE_CHANGED.equals(action)) {
214 ThreadUtils.postOnBackgroundThread(() -> {
215 if (mPowerManager.isPowerSaveMode()) {
216 mWarnings.dismissLowBatteryWarning();
217 }
218 });
219 } else if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700220 final int oldBatteryLevel = mBatteryLevel;
221 mBatteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 100);
222 final int oldBatteryStatus = mBatteryStatus;
223 mBatteryStatus = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
224 BatteryManager.BATTERY_STATUS_UNKNOWN);
225 final int oldPlugType = mPlugType;
226 mPlugType = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 1);
227 final int oldInvalidCharger = mInvalidCharger;
228 mInvalidCharger = intent.getIntExtra(BatteryManager.EXTRA_INVALID_CHARGER, 0);
229
230 final boolean plugged = mPlugType != 0;
231 final boolean oldPlugged = oldPlugType != 0;
232
233 int oldBucket = findBatteryLevelBucket(oldBatteryLevel);
234 int bucket = findBatteryLevelBucket(mBatteryLevel);
235
Daniel Sandler71986622011-07-26 13:06:49 -0400236 if (DEBUG) {
Daniel Sandlerdea64622013-09-23 16:05:57 -0400237 Slog.d(TAG, "buckets ....." + mLowBatteryAlertCloseLevel
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700238 + " .. " + mLowBatteryReminderLevels[0]
239 + " .. " + mLowBatteryReminderLevels[1]);
Daniel Sandlerdea64622013-09-23 16:05:57 -0400240 Slog.d(TAG, "level " + oldBatteryLevel + " --> " + mBatteryLevel);
241 Slog.d(TAG, "status " + oldBatteryStatus + " --> " + mBatteryStatus);
242 Slog.d(TAG, "plugType " + oldPlugType + " --> " + mPlugType);
243 Slog.d(TAG, "invalidCharger " + oldInvalidCharger + " --> " + mInvalidCharger);
244 Slog.d(TAG, "bucket " + oldBucket + " --> " + bucket);
245 Slog.d(TAG, "plugged " + oldPlugged + " --> " + plugged);
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700246 }
247
John Spurlocked452c52014-03-06 12:02:31 -0500248 mWarnings.update(mBatteryLevel, bucket, mScreenOffTime);
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700249 if (oldInvalidCharger == 0 && mInvalidCharger != 0) {
Daniel Sandlerdea64622013-09-23 16:05:57 -0400250 Slog.d(TAG, "showing invalid charger warning");
John Spurlocked452c52014-03-06 12:02:31 -0500251 mWarnings.showInvalidChargerWarning();
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700252 return;
253 } else if (oldInvalidCharger != 0 && mInvalidCharger == 0) {
John Spurlocked452c52014-03-06 12:02:31 -0500254 mWarnings.dismissInvalidChargerWarning();
255 } else if (mWarnings.isInvalidChargerWarningShowing()) {
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700256 // if invalid charger is showing, don't show low battery
257 return;
258 }
259
Salvador Martinezf9e47502018-01-04 13:45:48 -0800260 // Show the correct version of low battery warning if needed
Salvador Martinez110f9a12018-01-31 09:57:17 -0800261 ThreadUtils.postOnBackgroundThread(() -> {
Salvador Martinez926f0712018-07-03 18:07:07 -0700262 maybeShowBatteryWarning(
263 oldBatteryLevel, plugged, oldPlugged, oldBucket, bucket);
Salvador Martinez110f9a12018-01-31 09:57:17 -0800264 });
Beverly334bc5f2017-07-31 10:37:17 -0400265
Daniel Sandlerdea64622013-09-23 16:05:57 -0400266 } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
267 mScreenOffTime = SystemClock.elapsedRealtime();
268 } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
269 mScreenOffTime = -1;
John Spurlockecbc5e82014-10-22 09:05:51 -0400270 } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
271 mWarnings.userSwitched();
Bill Lin32ed3d62018-10-02 18:10:09 +0800272 } else if (Intent.ACTION_POWER_DISCONNECTED.equals(action)) {
273 updateTemperature();
Joe Onorato10523b4d2010-10-25 10:42:46 -0700274 } else {
Daniel Sandlerdea64622013-09-23 16:05:57 -0400275 Slog.w(TAG, "unknown intent: " + intent);
Joe Onorato10523b4d2010-10-25 10:42:46 -0700276 }
277 }
Salvador Martinezf9e47502018-01-04 13:45:48 -0800278 }
279
Salvador Martinez926f0712018-07-03 18:07:07 -0700280 protected void maybeShowBatteryWarning(int oldBatteryLevel, boolean plugged, boolean oldPlugged,
281 int oldBucket, int bucket) {
Salvador Martinezf9e47502018-01-04 13:45:48 -0800282 boolean isPowerSaver = mPowerManager.isPowerSaveMode();
283 // only play SFX when the dialog comes up or the bucket changes
284 final boolean playSound = bucket != oldBucket || oldPlugged;
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700285 final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
286 if (hybridEnabled) {
Salvador Martinez926f0712018-07-03 18:07:07 -0700287 Estimate estimate = mLastEstimate;
288 if (estimate == null || mBatteryLevel != oldBatteryLevel) {
289 estimate = mEnhancedEstimates.getEstimate();
290 mLastEstimate = estimate;
291 }
Salvador Martinezf9e47502018-01-04 13:45:48 -0800292 // Turbo is not always booted once SysUI is running so we have ot make sure we actually
293 // get data back
294 if (estimate != null) {
295 mTimeRemaining = estimate.estimateMillis;
296 mWarnings.updateEstimate(estimate);
Salvador Martinezbb902fc2018-01-22 19:46:55 -0800297 mWarnings.updateThresholds(mEnhancedEstimates.getLowWarningThreshold(),
298 mEnhancedEstimates.getSevereWarningThreshold());
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700299
300 // if we are now over 45% battery & 6 hours remaining we can trigger hybrid
301 // notification again
302 if (mBatteryLevel >= CHARGE_CYCLE_PERCENT_RESET
303 && mTimeRemaining > SIX_HOURS_MILLIS) {
304 mLowWarningShownThisChargeCycle = false;
305 mSevereWarningShownThisChargeCycle = false;
306 }
Salvador Martinezf9e47502018-01-04 13:45:48 -0800307 }
308 }
309
Salvador Martinez36307962018-02-08 14:29:08 -0800310 if (shouldShowLowBatteryWarning(plugged, oldPlugged, oldBucket, bucket,
311 mTimeRemaining, isPowerSaver, mBatteryStatus)) {
Salvador Martinezf9e47502018-01-04 13:45:48 -0800312 mWarnings.showLowBatteryWarning(playSound);
Salvador Martinez36307962018-02-08 14:29:08 -0800313
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700314 // mark if we've already shown a warning this cycle. This will prevent the notification
315 // trigger from spamming users by only showing low/critical warnings once per cycle
316 if (hybridEnabled) {
317 if (mTimeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
318 || mBatteryLevel < mLowBatteryReminderLevels[1]) {
319 mSevereWarningShownThisChargeCycle = true;
320 } else {
321 mLowWarningShownThisChargeCycle = true;
322 }
Salvador Martinez36307962018-02-08 14:29:08 -0800323 }
Salvador Martinezf9e47502018-01-04 13:45:48 -0800324 } else if (shouldDismissLowBatteryWarning(plugged, oldBucket, bucket, mTimeRemaining,
325 isPowerSaver)) {
326 mWarnings.dismissLowBatteryWarning();
327 } else {
328 mWarnings.updateLowBatteryWarning();
329 }
330 }
331
332 @VisibleForTesting
333 boolean shouldShowLowBatteryWarning(boolean plugged, boolean oldPlugged, int oldBucket,
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700334 int bucket, long timeRemaining, boolean isPowerSaver, int batteryStatus) {
335 if (mEnhancedEstimates.isHybridNotificationEnabled()) {
336 // triggering logic when enhanced estimate is available
337 return isEnhancedTrigger(plugged, timeRemaining, isPowerSaver, batteryStatus);
338 }
339 // legacy triggering logic
Salvador Martinezf9e47502018-01-04 13:45:48 -0800340 return !plugged
341 && !isPowerSaver
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700342 && (((bucket < oldBucket || oldPlugged) && bucket < 0))
343 && batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN;
Salvador Martinezf9e47502018-01-04 13:45:48 -0800344 }
345
Salvador Martinezf9e47502018-01-04 13:45:48 -0800346 @VisibleForTesting
347 boolean shouldDismissLowBatteryWarning(boolean plugged, int oldBucket, int bucket,
348 long timeRemaining, boolean isPowerSaver) {
Salvador Martinezc50a9bd2018-08-03 14:07:44 -0700349 final boolean hybridEnabled = mEnhancedEstimates.isHybridNotificationEnabled();
350 final boolean hybridWouldDismiss = hybridEnabled
Salvador Martinezbb902fc2018-01-22 19:46:55 -0800351 && timeRemaining > mEnhancedEstimates.getLowWarningThreshold();
Salvador Martinezf9e47502018-01-04 13:45:48 -0800352 final boolean standardWouldDismiss = (bucket > oldBucket && bucket > 0);
Salvador Martinezc50a9bd2018-08-03 14:07:44 -0700353 return (isPowerSaver && !hybridEnabled)
Salvador Martinezf9e47502018-01-04 13:45:48 -0800354 || plugged
355 || (standardWouldDismiss && (!mEnhancedEstimates.isHybridNotificationEnabled()
356 || hybridWouldDismiss));
357 }
Joe Onorato10523b4d2010-10-25 10:42:46 -0700358
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700359 private boolean isEnhancedTrigger(boolean plugged, long timeRemaining, boolean isPowerSaver,
360 int batteryStatus) {
Salvador Martinezc50a9bd2018-08-03 14:07:44 -0700361 if (plugged || batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) {
Salvador Martinez36307962018-02-08 14:29:08 -0800362 return false;
363 }
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700364 int warnLevel = mLowBatteryReminderLevels[0];
365 int critLevel = mLowBatteryReminderLevels[1];
Salvador Martinez36307962018-02-08 14:29:08 -0800366
Salvador Martinezc50a9bd2018-08-03 14:07:44 -0700367 // Only show the low warning once per charge cycle & no battery saver
368 final boolean canShowWarning = !mLowWarningShownThisChargeCycle && !isPowerSaver
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700369 && (timeRemaining < mEnhancedEstimates.getLowWarningThreshold()
370 || mBatteryLevel <= warnLevel);
Salvador Martinez36307962018-02-08 14:29:08 -0800371
Salvador Martinezfd38aa52018-03-28 23:56:59 -0700372 // Only show the severe warning once per charge cycle
373 final boolean canShowSevereWarning = !mSevereWarningShownThisChargeCycle
374 && (timeRemaining < mEnhancedEstimates.getSevereWarningThreshold()
375 || mBatteryLevel <= critLevel);
Salvador Martinez36307962018-02-08 14:29:08 -0800376
377 return canShowWarning || canShowSevereWarning;
378 }
379
Bill Lin32ed3d62018-10-02 18:10:09 +0800380 private void initTemperature() {
381 initTemperatureWarning();
382 initTemperatureAlarm();
383 if (mEnableTemperatureWarning || mEnableTemperatureAlarm) {
384 bindThermalService();
385 }
386 }
387
388 private void initTemperatureAlarm() {
389 mEnableTemperatureAlarm = mContext.getResources().getInteger(
390 R.integer.config_showTemperatureAlarm) != 0;
391 if (!mEnableTemperatureAlarm) {
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800392 return;
393 }
394
Bill Lin32ed3d62018-10-02 18:10:09 +0800395 float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
396 HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
397 HardwarePropertiesManager.TEMPERATURE_THROTTLING);
398 if (throttlingTemps == null || throttlingTemps.length < TEMPERATURE_OVERHEAT_ALARM + 1) {
399 mThresholdAlarmTemp = mContext.getResources().getInteger(
400 R.integer.config_alarmTemperature);
401 } else {
402 mThresholdAlarmTemp = throttlingTemps[TEMPERATURE_OVERHEAT_ALARM];
403 }
404 mThresholdAlarmTempTolerance = mThresholdAlarmTemp - mContext.getResources().getInteger(
405 R.integer.config_alarmTemperatureTolerance);
406 Log.d(TAG, "mThresholdAlarmTemp=" + mThresholdAlarmTemp + ", mThresholdAlarmTempTolerance="
407 + mThresholdAlarmTempTolerance);
408 }
409
410 private void initTemperatureWarning() {
411 ContentResolver resolver = mContext.getContentResolver();
412 Resources resources = mContext.getResources();
413 mEnableTemperatureWarning = Settings.Global.getInt(resolver,
414 Settings.Global.SHOW_TEMPERATURE_WARNING,
415 resources.getInteger(R.integer.config_showTemperatureWarning)) != 0;
416 if (!mEnableTemperatureWarning) {
417 return;
418 }
419
420 mThresholdWarningTemp = Settings.Global.getFloat(resolver,
421 Settings.Global.WARNING_TEMPERATURE,
Andrew Sappersteine3352562017-01-20 15:41:03 -0800422 resources.getInteger(R.integer.config_warningTemperature));
Andrew Sapperstein75184712017-01-05 16:45:37 -0800423
Bill Lin32ed3d62018-10-02 18:10:09 +0800424 if (mThresholdWarningTemp < 0f) {
Todd Poynorfb95e122017-10-04 13:00:32 -0700425 // Get the shutdown temperature, adjust for warning tolerance.
Andrew Sapperstein75184712017-01-05 16:45:37 -0800426 float[] throttlingTemps = mHardwarePropertiesManager.getDeviceTemperatures(
427 HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
Jason Monkd819c312017-08-11 12:53:36 -0400428 HardwarePropertiesManager.TEMPERATURE_SHUTDOWN);
Andrew Sapperstein75184712017-01-05 16:45:37 -0800429 if (throttlingTemps == null
430 || throttlingTemps.length == 0
431 || throttlingTemps[0] == HardwarePropertiesManager.UNDEFINED_TEMPERATURE) {
432 return;
433 }
Bill Lin32ed3d62018-10-02 18:10:09 +0800434 mThresholdWarningTemp = throttlingTemps[0] - resources.getInteger(
435 R.integer.config_warningTemperatureTolerance);
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800436 }
Bill Lin32ed3d62018-10-02 18:10:09 +0800437 }
Adam Lesinski1aab3fa2017-07-31 13:42:00 -0700438
Bill Lin32ed3d62018-10-02 18:10:09 +0800439 private void bindThermalService() {
Todd Poynorfb95e122017-10-04 13:00:32 -0700440 if (mThermalService == null) {
441 // Enable push notifications of throttling from vendor thermal
442 // management subsystem via thermalservice, in addition to our
443 // usual polling, to react to temperature jumps more quickly.
444 IBinder b = ServiceManager.getService("thermalservice");
445
446 if (b != null) {
447 mThermalService = IThermalService.Stub.asInterface(b);
448 try {
449 mThermalService.registerThermalEventListener(
450 new ThermalEventListener());
451 } catch (RemoteException e) {
452 // Should never happen.
453 }
454 } else {
455 Slog.w(TAG, "cannot find thermalservice, no throttling push notifications");
456 }
457 }
458
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800459 setNextLogTime();
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800460
Adam Lesinski1aab3fa2017-07-31 13:42:00 -0700461 // This initialization method may be called on a configuration change. Only one set of
462 // ongoing callbacks should be occurring, so remove any now. updateTemperatureWarning will
463 // schedule an ongoing callback.
464 mHandler.removeCallbacks(mUpdateTempCallback);
465
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800466 // We have passed all of the checks, start checking the temp
Bill Lin32ed3d62018-10-02 18:10:09 +0800467 updateTemperature();
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800468 }
469
Salvador Martineza6f7b252017-04-10 10:46:15 -0700470 private void showThermalShutdownDialog() {
471 if (mPowerManager.getLastShutdownReason()
472 == PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN) {
473 mWarnings.showThermalShutdownWarning();
474 }
475 }
476
Bill Lin32ed3d62018-10-02 18:10:09 +0800477 /**
478 * Update temperature depend on config, there are type types of messages by design
479 * TEMPERATURE_OVERHEAT_WARNING Send generic notification to notify user
480 * TEMPERATURE_OVERHEAT_ALARM popup emergency Dialog for user
481 */
Jason Monkd819c312017-08-11 12:53:36 -0400482 @VisibleForTesting
Bill Lin32ed3d62018-10-02 18:10:09 +0800483 protected void updateTemperature() {
484 if (!mEnableTemperatureWarning && !mEnableTemperatureAlarm) {
485 return;
486 }
487
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800488 float[] temps = mHardwarePropertiesManager.getDeviceTemperatures(
489 HardwarePropertiesManager.DEVICE_TEMPERATURE_SKIN,
490 HardwarePropertiesManager.TEMPERATURE_CURRENT);
Bill Lin32ed3d62018-10-02 18:10:09 +0800491 if (temps == null) {
492 Log.e(TAG, "Can't query current temperature value from HardProps SKIN type");
493 return;
494 }
495
496 final float[] temps_compat;
497 if (temps.length < TEMPERATURE_OVERHEAT_ALARM + 1) {
498 temps_compat = new float[] { temps[0], temps[0] };
499 } else {
500 temps_compat = temps;
501 }
502
503 if (mEnableTemperatureWarning) {
504 updateTemperatureWarning(temps_compat);
505 logTemperatureStats(TEMPERATURE_OVERHEAT_WARNING);
506 }
507
508 if (mEnableTemperatureAlarm) {
509 updateTemperatureAlarm(temps_compat);
510 logTemperatureStats(TEMPERATURE_OVERHEAT_ALARM);
511 }
512
513 mHandler.postDelayed(mUpdateTempCallback, TEMPERATURE_INTERVAL);
514 }
515
516 /**
517 * Update legacy overheat warning notification from skin temperatures
518 *
519 * @param temps the array include two types of value from DEVICE_TEMPERATURE_SKIN
520 * this function only obtain the value of temps[TEMPERATURE_OVERHEAT_WARNING]
521 */
522 @VisibleForTesting
523 protected void updateTemperatureWarning(float[] temps) {
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800524 if (temps.length != 0) {
Bill Lin32ed3d62018-10-02 18:10:09 +0800525 float temp = temps[TEMPERATURE_OVERHEAT_WARNING];
526 mRecentSkinTemps[mWarningNumTemps++] = temp;
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800527
528 StatusBar statusBar = getComponent(StatusBar.class);
529 if (statusBar != null && !statusBar.isDeviceInVrMode()
Bill Lin32ed3d62018-10-02 18:10:09 +0800530 && temp >= mThresholdWarningTemp) {
531 logAtTemperatureThreshold(TEMPERATURE_OVERHEAT_WARNING, temp,
532 mThresholdWarningTemp);
Salvador Martineza6f7b252017-04-10 10:46:15 -0700533 mWarnings.showHighTemperatureWarning();
Andrew Sapperstein65d8a5f2016-12-19 14:36:33 -0800534 } else {
Salvador Martineza6f7b252017-04-10 10:46:15 -0700535 mWarnings.dismissHighTemperatureWarning();
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800536 }
537 }
Andrew Sappersteinb7caf1d2016-12-14 15:39:20 -0800538 }
539
Bill Lin32ed3d62018-10-02 18:10:09 +0800540 /**
541 * Update overheat alarm from skin temperatures
542 * OEM can config alarm with beep sound by config_alarmTemperatureBeepSound
543 *
544 * @param temps the array include two types of value from DEVICE_TEMPERATURE_SKIN
545 * this function only obtain the value of temps[TEMPERATURE_OVERHEAT_ALARM]
546 */
547 @VisibleForTesting
548 protected void updateTemperatureAlarm(float[] temps) {
549 if (temps.length != 0) {
550 final float temp = temps[TEMPERATURE_OVERHEAT_ALARM];
551 final boolean shouldBeepSound = mContext.getResources().getBoolean(
552 R.bool.config_alarmTemperatureBeepSound);
553 mRecentAlarmTemps[mAlarmNumTemps++] = temp;
554 if (temp >= mThresholdAlarmTemp && !mIsOverheatAlarming) {
555 mWarnings.notifyHighTemperatureAlarm(true /* overheat */, shouldBeepSound);
556 mIsOverheatAlarming = true;
557 } else if (temp <= mThresholdAlarmTempTolerance && mIsOverheatAlarming) {
558 mWarnings.notifyHighTemperatureAlarm(false /* overheat */, false /* beepSound */);
559 mIsOverheatAlarming = false;
560 }
561 logAtTemperatureThreshold(TEMPERATURE_OVERHEAT_ALARM, temp, mThresholdAlarmTemp);
562 }
563 }
564
565 private void logAtTemperatureThreshold(int type, float temp, float thresholdTemp) {
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800566 StringBuilder sb = new StringBuilder();
567 sb.append("currentTemp=").append(temp)
Bill Lin32ed3d62018-10-02 18:10:09 +0800568 .append(",overheatType=").append(type)
569 .append(",isOverheatAlarm=").append(mIsOverheatAlarming)
570 .append(",thresholdTemp=").append(thresholdTemp)
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800571 .append(",batteryStatus=").append(mBatteryStatus)
572 .append(",recentTemps=");
Bill Lin32ed3d62018-10-02 18:10:09 +0800573 if (type == TEMPERATURE_OVERHEAT_WARNING) {
574 for (int i = 0; i < mWarningNumTemps; i++) {
575 sb.append(mRecentSkinTemps[i]).append(',');
576 }
577 } else {
578 for (int i = 0; i < mAlarmNumTemps; i++) {
579 sb.append(mRecentAlarmTemps[i]).append(',');
580 }
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800581 }
582 Slog.i(TAG, sb.toString());
583 }
584
585 /**
586 * Calculates and logs min, max, and average
587 * {@link HardwarePropertiesManager#DEVICE_TEMPERATURE_SKIN} over the past
588 * {@link #TEMPERATURE_LOGGING_INTERVAL}.
Bill Lin32ed3d62018-10-02 18:10:09 +0800589 * @param type TEMPERATURE_OVERHEAT_WARNING Send generic notification to notify user
590 * TEMPERATURE_OVERHEAT_ALARM Popup emergency Dialog for user
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800591 */
Bill Lin32ed3d62018-10-02 18:10:09 +0800592 private void logTemperatureStats(int type) {
593 int numTemp = type == TEMPERATURE_OVERHEAT_ALARM ? mAlarmNumTemps : mWarningNumTemps;
594 if (mNextLogTime > System.currentTimeMillis() && numTemp != MAX_RECENT_TEMPS) {
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800595 return;
596 }
Bill Lin32ed3d62018-10-02 18:10:09 +0800597 float[] recentTemps =
598 type == TEMPERATURE_OVERHEAT_ALARM ? mRecentAlarmTemps : mRecentSkinTemps;
599 if (numTemp > 0) {
600 float sum = recentTemps[0], min = recentTemps[0], max = recentTemps[0];
601 for (int i = 1; i < numTemp; i++) {
602 float temp = recentTemps[i];
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800603 sum += temp;
604 if (temp > max) {
605 max = temp;
606 }
607 if (temp < min) {
608 min = temp;
609 }
610 }
611
Bill Lin32ed3d62018-10-02 18:10:09 +0800612 float avg = sum / numTemp;
613 Slog.i(TAG, "Type=" + type + ",avg=" + avg + ",min=" + min + ",max=" + max);
614 String t = type == TEMPERATURE_OVERHEAT_WARNING ? "skin" : "alarm";
615 MetricsLogger.histogram(mContext,
616 String.format(Locale.ENGLISH, "device_%1$s_temp_avg", t), (int) avg);
617 MetricsLogger.histogram(mContext,
618 String.format(Locale.ENGLISH, "device_%1$s_temp_min", t), (int) min);
619 MetricsLogger.histogram(mContext,
620 String.format(Locale.ENGLISH, "device_%1$s_temp_max", t), (int) max);
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800621 }
622 setNextLogTime();
Bill Lin32ed3d62018-10-02 18:10:09 +0800623 if (type == TEMPERATURE_OVERHEAT_ALARM) {
624 mAlarmNumTemps = 0;
625 } else {
626 mWarningNumTemps = 0;
627 }
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800628 }
629
630 private void setNextLogTime() {
631 mNextLogTime = System.currentTimeMillis() + TEMPERATURE_LOGGING_INTERVAL;
632 }
633
Joe Onorato10523b4d2010-10-25 10:42:46 -0700634 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700635 pw.print("mLowBatteryAlertCloseLevel=");
636 pw.println(mLowBatteryAlertCloseLevel);
637 pw.print("mLowBatteryReminderLevels=");
638 pw.println(Arrays.toString(mLowBatteryReminderLevels));
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700639 pw.print("mBatteryLevel=");
640 pw.println(Integer.toString(mBatteryLevel));
641 pw.print("mBatteryStatus=");
642 pw.println(Integer.toString(mBatteryStatus));
643 pw.print("mPlugType=");
644 pw.println(Integer.toString(mPlugType));
645 pw.print("mInvalidCharger=");
646 pw.println(Integer.toString(mInvalidCharger));
Daniel Sandlerdea64622013-09-23 16:05:57 -0400647 pw.print("mScreenOffTime=");
648 pw.print(mScreenOffTime);
649 if (mScreenOffTime >= 0) {
650 pw.print(" (");
651 pw.print(SystemClock.elapsedRealtime() - mScreenOffTime);
652 pw.print(" ago)");
653 }
654 pw.println();
655 pw.print("soundTimeout=");
656 pw.println(Settings.Global.getInt(mContext.getContentResolver(),
657 Settings.Global.LOW_BATTERY_SOUND_TIMEOUT, 0));
Joe Onorato4ca7f1e2010-10-27 15:32:23 -0700658 pw.print("bucket: ");
659 pw.println(Integer.toString(findBatteryLevelBucket(mBatteryLevel)));
Bill Lin32ed3d62018-10-02 18:10:09 +0800660 pw.print("mThresholdWarningTemp=");
661 pw.println(Float.toString(mThresholdWarningTemp));
662 pw.print("mThresholdAlarmTemp=");
663 pw.println(Float.toString(mThresholdAlarmTemp));
Andrew Sappersteina6ed7f82017-02-01 17:13:08 -0800664 pw.print("mNextLogTime=");
665 pw.println(Long.toString(mNextLogTime));
John Spurlocked452c52014-03-06 12:02:31 -0500666 mWarnings.dump(pw);
667 }
668
669 public interface WarningsUI {
670 void update(int batteryLevel, int bucket, long screenOffTime);
Bill Lin32ed3d62018-10-02 18:10:09 +0800671
Salvador Martinezf9e47502018-01-04 13:45:48 -0800672 void updateEstimate(Estimate estimate);
Bill Lin32ed3d62018-10-02 18:10:09 +0800673
Salvador Martinezbb902fc2018-01-22 19:46:55 -0800674 void updateThresholds(long lowThreshold, long severeThreshold);
Bill Lin32ed3d62018-10-02 18:10:09 +0800675
John Spurlocked452c52014-03-06 12:02:31 -0500676 void dismissLowBatteryWarning();
Bill Lin32ed3d62018-10-02 18:10:09 +0800677
John Spurlocked452c52014-03-06 12:02:31 -0500678 void showLowBatteryWarning(boolean playSound);
Bill Lin32ed3d62018-10-02 18:10:09 +0800679
John Spurlocked452c52014-03-06 12:02:31 -0500680 void dismissInvalidChargerWarning();
Bill Lin32ed3d62018-10-02 18:10:09 +0800681
John Spurlocked452c52014-03-06 12:02:31 -0500682 void showInvalidChargerWarning();
Bill Lin32ed3d62018-10-02 18:10:09 +0800683
John Spurlocked452c52014-03-06 12:02:31 -0500684 void updateLowBatteryWarning();
Bill Lin32ed3d62018-10-02 18:10:09 +0800685
John Spurlocked452c52014-03-06 12:02:31 -0500686 boolean isInvalidChargerWarningShowing();
Bill Lin32ed3d62018-10-02 18:10:09 +0800687
Salvador Martineza6f7b252017-04-10 10:46:15 -0700688 void dismissHighTemperatureWarning();
Bill Lin32ed3d62018-10-02 18:10:09 +0800689
Salvador Martineza6f7b252017-04-10 10:46:15 -0700690 void showHighTemperatureWarning();
Bill Lin32ed3d62018-10-02 18:10:09 +0800691
692 /**
693 * PowerUI detect thermal overheat, notify to popup alert dialog strongly.
694 * Alarm with beep sound with un-dismissible dialog until device cool down below threshold,
695 * then popup another dismissible dialog to user.
696 *
697 * @param overheat whether device temperature over threshold
698 * @param beepSound should beep sound once overheat
699 */
700 void notifyHighTemperatureAlarm(boolean overheat, boolean beepSound);
701
Salvador Martineza6f7b252017-04-10 10:46:15 -0700702 void showThermalShutdownWarning();
Bill Lin32ed3d62018-10-02 18:10:09 +0800703
John Spurlocked452c52014-03-06 12:02:31 -0500704 void dump(PrintWriter pw);
Bill Lin32ed3d62018-10-02 18:10:09 +0800705
John Spurlockecbc5e82014-10-22 09:05:51 -0400706 void userSwitched();
Joe Onorato10523b4d2010-10-25 10:42:46 -0700707 }
Joe Onorato10523b4d2010-10-25 10:42:46 -0700708
Todd Poynorfb95e122017-10-04 13:00:32 -0700709 // Thermal event received from vendor thermal management subsystem
710 private final class ThermalEventListener extends IThermalEventListener.Stub {
711 @Override public void notifyThrottling(boolean isThrottling, Temperature temp) {
712 // Trigger an update of the temperature warning. Only one
713 // callback can be enabled at a time, so remove any existing
Bill Lin32ed3d62018-10-02 18:10:09 +0800714 // callback; updateTemperature will schedule another one.
Todd Poynorfb95e122017-10-04 13:00:32 -0700715 mHandler.removeCallbacks(mUpdateTempCallback);
Bill Lin32ed3d62018-10-02 18:10:09 +0800716 updateTemperature();
Todd Poynorfb95e122017-10-04 13:00:32 -0700717 }
718 }
719}