1 /* 2 * Copyright (C) 2020 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.server.display; 18 19 import android.content.Context; 20 import android.database.ContentObserver; 21 import android.hardware.display.BrightnessInfo; 22 import android.net.Uri; 23 import android.os.Handler; 24 import android.os.IBinder; 25 import android.os.IThermalEventListener; 26 import android.os.IThermalService; 27 import android.os.PowerManager; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.os.SystemClock; 31 import android.os.Temperature; 32 import android.os.UserHandle; 33 import android.provider.Settings; 34 import android.util.MathUtils; 35 import android.util.Slog; 36 import android.util.TimeUtils; 37 import android.view.SurfaceControlHdrLayerInfoListener; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData; 41 import com.android.server.display.DisplayManagerService.Clock; 42 43 import java.io.PrintWriter; 44 import java.util.Iterator; 45 import java.util.LinkedList; 46 47 /** 48 * Controls the status of high-brightness mode for devices that support it. This class assumes that 49 * an instance is always created even if a device does not support high-brightness mode (HBM); in 50 * the case where it is not supported, the majority of the logic is skipped. On devices that support 51 * HBM, we keep track of the ambient lux as well as historical usage of HBM to determine when HBM is 52 * allowed and not. This class's output is simply a brightness-range maximum value (queried via 53 * {@link #getCurrentBrightnessMax}) that changes depending on whether HBM is enabled or not. 54 */ 55 class HighBrightnessModeController { 56 private static final String TAG = "HighBrightnessModeController"; 57 58 private static final boolean DEBUG = false; 59 60 private static final float HDR_PERCENT_OF_SCREEN_REQUIRED = 0.50f; 61 62 private final float mBrightnessMin; 63 private final float mBrightnessMax; 64 private final Handler mHandler; 65 private final Runnable mHbmChangeCallback; 66 private final Runnable mRecalcRunnable; 67 private final Clock mClock; 68 private final SkinThermalStatusObserver mSkinThermalStatusObserver; 69 private final Context mContext; 70 private final SettingsObserver mSettingsObserver; 71 private final Injector mInjector; 72 73 private HdrListener mHdrListener; 74 private HighBrightnessModeData mHbmData; 75 private IBinder mRegisteredDisplayToken; 76 77 private boolean mIsInAllowedAmbientRange = false; 78 private boolean mIsTimeAvailable = false; 79 private boolean mIsAutoBrightnessEnabled = false; 80 private float mBrightness; 81 private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 82 private boolean mIsHdrLayerPresent = false; 83 private boolean mIsThermalStatusWithinLimit = true; 84 private boolean mIsBlockedByLowPowerMode = false; 85 private int mWidth; 86 private int mHeight; 87 private float mAmbientLux; 88 89 /** 90 * If HBM is currently running, this is the start time for the current HBM session. 91 */ 92 private long mRunningStartTimeMillis = -1; 93 94 /** 95 * List of previous HBM-events ordered from most recent to least recent. 96 * Meant to store only the events that fall into the most recent 97 * {@link mHbmData.timeWindowMillis}. 98 */ 99 private LinkedList<HbmEvent> mEvents = new LinkedList<>(); 100 HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback, Context context)101 HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, 102 float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, 103 Runnable hbmChangeCallback, Context context) { 104 this(new Injector(), handler, width, height, displayToken, brightnessMin, brightnessMax, 105 hbmData, hbmChangeCallback, context); 106 } 107 108 @VisibleForTesting HighBrightnessModeController(Injector injector, Handler handler, int width, int height, IBinder displayToken, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, Runnable hbmChangeCallback, Context context)109 HighBrightnessModeController(Injector injector, Handler handler, int width, int height, 110 IBinder displayToken, float brightnessMin, float brightnessMax, 111 HighBrightnessModeData hbmData, Runnable hbmChangeCallback, 112 Context context) { 113 mInjector = injector; 114 mContext = context; 115 mClock = injector.getClock(); 116 mHandler = handler; 117 mBrightness = brightnessMin; 118 mBrightnessMin = brightnessMin; 119 mBrightnessMax = brightnessMax; 120 mHbmChangeCallback = hbmChangeCallback; 121 mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); 122 mSettingsObserver = new SettingsObserver(mHandler); 123 mRecalcRunnable = this::recalculateTimeAllowance; 124 mHdrListener = new HdrListener(); 125 126 resetHbmData(width, height, displayToken, hbmData); 127 } 128 setAutoBrightnessEnabled(boolean isEnabled)129 void setAutoBrightnessEnabled(boolean isEnabled) { 130 if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) { 131 return; 132 } 133 if (DEBUG) { 134 Slog.d(TAG, "setAutoBrightnessEnabled( " + isEnabled + " )"); 135 } 136 mIsAutoBrightnessEnabled = isEnabled; 137 mIsInAllowedAmbientRange = false; // reset when auto-brightness switches 138 recalculateTimeAllowance(); 139 } 140 getCurrentBrightnessMin()141 float getCurrentBrightnessMin() { 142 return mBrightnessMin; 143 } 144 getCurrentBrightnessMax()145 float getCurrentBrightnessMax() { 146 if (!deviceSupportsHbm() || isCurrentlyAllowed()) { 147 // Either the device doesn't support HBM, or HBM range is currently allowed (device 148 // it in a high-lux environment). In either case, return the highest brightness 149 // level supported by the device. 150 return mBrightnessMax; 151 } else { 152 // Hbm is not allowed, only allow up to the brightness where we 153 // transition to high brightness mode. 154 return mHbmData.transitionPoint; 155 } 156 } 157 getNormalBrightnessMax()158 float getNormalBrightnessMax() { 159 return deviceSupportsHbm() ? mHbmData.transitionPoint : mBrightnessMax; 160 } 161 getHdrBrightnessValue()162 float getHdrBrightnessValue() { 163 // For HDR brightness, we take the current brightness and scale it to the max. The reason 164 // we do this is because we want brightness to go to HBM max when it would normally go 165 // to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition 166 // point happens to be) in order to go full HDR. Likewise, HDR on manual brightness should 167 // automatically scale the brightness without forcing the user to adjust to higher values. 168 return MathUtils.map(getCurrentBrightnessMin(), getCurrentBrightnessMax(), 169 mBrightnessMin, mBrightnessMax, mBrightness); 170 } 171 onAmbientLuxChange(float ambientLux)172 void onAmbientLuxChange(float ambientLux) { 173 mAmbientLux = ambientLux; 174 if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) { 175 return; 176 } 177 178 final boolean isHighLux = (ambientLux >= mHbmData.minimumLux); 179 if (isHighLux != mIsInAllowedAmbientRange) { 180 mIsInAllowedAmbientRange = isHighLux; 181 recalculateTimeAllowance(); 182 } 183 } 184 onBrightnessChanged(float brightness)185 void onBrightnessChanged(float brightness) { 186 if (!deviceSupportsHbm()) { 187 return; 188 } 189 mBrightness = brightness; 190 191 // If we are starting or ending a high brightness mode session, store the current 192 // session in mRunningStartTimeMillis, or the old one in mEvents. 193 final boolean wasHbmDrainingAvailableTime = mRunningStartTimeMillis != -1; 194 final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint 195 && !mIsHdrLayerPresent; 196 if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) { 197 final long currentTime = mClock.uptimeMillis(); 198 if (shouldHbmDrainAvailableTime) { 199 mRunningStartTimeMillis = currentTime; 200 } else { 201 mEvents.addFirst(new HbmEvent(mRunningStartTimeMillis, currentTime)); 202 mRunningStartTimeMillis = -1; 203 204 if (DEBUG) { 205 Slog.d(TAG, "New HBM event: " + mEvents.getFirst()); 206 } 207 } 208 } 209 210 recalculateTimeAllowance(); 211 } 212 getHighBrightnessMode()213 int getHighBrightnessMode() { 214 return mHbmMode; 215 } 216 stop()217 void stop() { 218 registerHdrListener(null /*displayToken*/); 219 mSkinThermalStatusObserver.stopObserving(); 220 mSettingsObserver.stopObserving(); 221 } 222 resetHbmData(int width, int height, IBinder displayToken, HighBrightnessModeData hbmData)223 void resetHbmData(int width, int height, IBinder displayToken, HighBrightnessModeData hbmData) { 224 mWidth = width; 225 mHeight = height; 226 mHbmData = hbmData; 227 228 unregisterHdrListener(); 229 mSkinThermalStatusObserver.stopObserving(); 230 mSettingsObserver.stopObserving(); 231 if (deviceSupportsHbm()) { 232 registerHdrListener(displayToken); 233 recalculateTimeAllowance(); 234 if (mHbmData.thermalStatusLimit > PowerManager.THERMAL_STATUS_NONE) { 235 mIsThermalStatusWithinLimit = true; 236 mSkinThermalStatusObserver.startObserving(); 237 } 238 if (!mHbmData.allowInLowPowerMode) { 239 mIsBlockedByLowPowerMode = false; 240 mSettingsObserver.startObserving(); 241 } 242 } 243 } 244 dump(PrintWriter pw)245 void dump(PrintWriter pw) { 246 mHandler.runWithScissors(() -> dumpLocal(pw), 1000); 247 } 248 249 @VisibleForTesting getHdrListener()250 HdrListener getHdrListener() { 251 return mHdrListener; 252 } 253 dumpLocal(PrintWriter pw)254 private void dumpLocal(PrintWriter pw) { 255 pw.println("HighBrightnessModeController:"); 256 pw.println(" mBrightness=" + mBrightness); 257 pw.println(" mCurrentMin=" + getCurrentBrightnessMin()); 258 pw.println(" mCurrentMax=" + getCurrentBrightnessMax()); 259 pw.println(" mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode) 260 + (mHbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR 261 ? "(" + getHdrBrightnessValue() + ")" : "")); 262 pw.println(" mHbmData=" + mHbmData); 263 pw.println(" mAmbientLux=" + mAmbientLux 264 + (mIsAutoBrightnessEnabled ? "" : " (old/invalid)")); 265 pw.println(" mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange); 266 pw.println(" mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled); 267 pw.println(" mIsHdrLayerPresent=" + mIsHdrLayerPresent); 268 pw.println(" mBrightnessMin=" + mBrightnessMin); 269 pw.println(" mBrightnessMax=" + mBrightnessMax); 270 pw.println(" remainingTime=" + calculateRemainingTime(mClock.uptimeMillis())); 271 pw.println(" mIsTimeAvailable= " + mIsTimeAvailable); 272 pw.println(" mRunningStartTimeMillis=" + TimeUtils.formatUptime(mRunningStartTimeMillis)); 273 pw.println(" mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit); 274 pw.println(" mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode); 275 pw.println(" width*height=" + mWidth + "*" + mHeight); 276 pw.println(" mEvents="); 277 final long currentTime = mClock.uptimeMillis(); 278 long lastStartTime = currentTime; 279 if (mRunningStartTimeMillis != -1) { 280 lastStartTime = dumpHbmEvent(pw, new HbmEvent(mRunningStartTimeMillis, currentTime)); 281 } 282 for (HbmEvent event : mEvents) { 283 if (lastStartTime > event.endTimeMillis) { 284 pw.println(" event: [normal brightness]: " 285 + TimeUtils.formatDuration(lastStartTime - event.endTimeMillis)); 286 } 287 lastStartTime = dumpHbmEvent(pw, event); 288 } 289 290 mSkinThermalStatusObserver.dump(pw); 291 } 292 dumpHbmEvent(PrintWriter pw, HbmEvent event)293 private long dumpHbmEvent(PrintWriter pw, HbmEvent event) { 294 final long duration = event.endTimeMillis - event.startTimeMillis; 295 pw.println(" event: [" 296 + TimeUtils.formatUptime(event.startTimeMillis) + ", " 297 + TimeUtils.formatUptime(event.endTimeMillis) + "] (" 298 + TimeUtils.formatDuration(duration) + ")"); 299 return event.startTimeMillis; 300 } 301 isCurrentlyAllowed()302 private boolean isCurrentlyAllowed() { 303 // Returns true if HBM is allowed (above the ambient lux threshold) and there's still 304 // time within the current window for additional HBM usage. We return false if there is an 305 // HDR layer because we don't want the brightness MAX to change for HDR, which has its 306 // brightness scaled in a different way than sunlight HBM that doesn't require changing 307 // the MAX. HDR also needs to work under manual brightness which never adjusts the 308 // brightness maximum; so we implement HDR-HBM in a way that doesn't adjust the max. 309 // See {@link #getHdrBrightnessValue}. 310 return !mIsHdrLayerPresent 311 && (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange 312 && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode); 313 } 314 deviceSupportsHbm()315 private boolean deviceSupportsHbm() { 316 return mHbmData != null; 317 } 318 calculateRemainingTime(long currentTime)319 private long calculateRemainingTime(long currentTime) { 320 if (!deviceSupportsHbm()) { 321 return 0; 322 } 323 324 long timeAlreadyUsed = 0; 325 326 // First, lets see how much time we've taken for any currently running 327 // session of HBM. 328 if (mRunningStartTimeMillis > 0) { 329 if (mRunningStartTimeMillis > currentTime) { 330 Slog.e(TAG, "Start time set to the future. curr: " + currentTime 331 + ", start: " + mRunningStartTimeMillis); 332 mRunningStartTimeMillis = currentTime; 333 } 334 timeAlreadyUsed = currentTime - mRunningStartTimeMillis; 335 } 336 337 if (DEBUG) { 338 Slog.d(TAG, "Time already used after current session: " + timeAlreadyUsed); 339 } 340 341 // Next, lets iterate through the history of previous sessions and add those times. 342 final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis; 343 Iterator<HbmEvent> it = mEvents.iterator(); 344 while (it.hasNext()) { 345 final HbmEvent event = it.next(); 346 347 // If this event ended before the current Timing window, discard forever and ever. 348 if (event.endTimeMillis < windowstartTimeMillis) { 349 it.remove(); 350 continue; 351 } 352 353 final long startTimeMillis = Math.max(event.startTimeMillis, windowstartTimeMillis); 354 timeAlreadyUsed += event.endTimeMillis - startTimeMillis; 355 } 356 357 if (DEBUG) { 358 Slog.d(TAG, "Time already used after all sessions: " + timeAlreadyUsed); 359 } 360 361 return Math.max(0, mHbmData.timeMaxMillis - timeAlreadyUsed); 362 } 363 364 /** 365 * Recalculates the allowable HBM time. 366 */ recalculateTimeAllowance()367 private void recalculateTimeAllowance() { 368 final long currentTime = mClock.uptimeMillis(); 369 final long remainingTime = calculateRemainingTime(currentTime); 370 371 // We allow HBM if there is more than the minimum required time available 372 // or if brightness is already in the high range, if there is any time left at all. 373 final boolean isAllowedWithoutRestrictions = remainingTime >= mHbmData.timeMinMillis; 374 final boolean isOnlyAllowedToStayOn = !isAllowedWithoutRestrictions 375 && remainingTime > 0 && mBrightness > mHbmData.transitionPoint; 376 mIsTimeAvailable = isAllowedWithoutRestrictions || isOnlyAllowedToStayOn; 377 378 // Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or 379 // brightness change doesn't happen before then. 380 long nextTimeout = -1; 381 if (mBrightness > mHbmData.transitionPoint) { 382 // if we're in high-lux now, timeout when we run out of allowed time. 383 nextTimeout = currentTime + remainingTime; 384 } else if (!mIsTimeAvailable && mEvents.size() > 0) { 385 // If we are not allowed...timeout when the oldest event moved outside of the timing 386 // window by at least minTime. Basically, we're calculating the soonest time we can 387 // get {@code timeMinMillis} back to us. 388 final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis; 389 final HbmEvent lastEvent = mEvents.getLast(); 390 final long startTimePlusMinMillis = 391 Math.max(windowstartTimeMillis, lastEvent.startTimeMillis) 392 + mHbmData.timeMinMillis; 393 final long timeWhenMinIsGainedBack = 394 currentTime + (startTimePlusMinMillis - windowstartTimeMillis) - remainingTime; 395 nextTimeout = timeWhenMinIsGainedBack; 396 } 397 398 if (DEBUG) { 399 Slog.d(TAG, "HBM recalculated. IsAllowedWithoutRestrictions: " 400 + isAllowedWithoutRestrictions 401 + ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn 402 + ", remainingAllowedTime: " + remainingTime 403 + ", isLuxHigh: " + mIsInAllowedAmbientRange 404 + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed() 405 + ", isHdrLayerPresent: " + mIsHdrLayerPresent 406 + ", isAutoBrightnessEnabled: " + mIsAutoBrightnessEnabled 407 + ", mIsTimeAvailable: " + mIsTimeAvailable 408 + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange 409 + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit 410 + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode 411 + ", mBrightness: " + mBrightness 412 + ", RunningStartTimeMillis: " + mRunningStartTimeMillis 413 + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1) 414 + ", events: " + mEvents); 415 } 416 417 if (nextTimeout != -1) { 418 mHandler.removeCallbacks(mRecalcRunnable); 419 mHandler.postAtTime(mRecalcRunnable, nextTimeout + 1); 420 } 421 // Update the state of the world 422 updateHbmMode(); 423 } 424 updateHbmMode()425 private void updateHbmMode() { 426 int newHbmMode = calculateHighBrightnessMode(); 427 if (mHbmMode != newHbmMode) { 428 mHbmMode = newHbmMode; 429 mHbmChangeCallback.run(); 430 } 431 } 432 calculateHighBrightnessMode()433 private int calculateHighBrightnessMode() { 434 if (!deviceSupportsHbm()) { 435 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 436 } else if (mIsHdrLayerPresent) { 437 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR; 438 } else if (isCurrentlyAllowed()) { 439 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT; 440 } 441 442 return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF; 443 } 444 registerHdrListener(IBinder displayToken)445 private void registerHdrListener(IBinder displayToken) { 446 if (mRegisteredDisplayToken == displayToken) { 447 return; 448 } 449 450 unregisterHdrListener(); 451 mRegisteredDisplayToken = displayToken; 452 if (mRegisteredDisplayToken != null) { 453 mHdrListener.register(mRegisteredDisplayToken); 454 } 455 } 456 unregisterHdrListener()457 private void unregisterHdrListener() { 458 if (mRegisteredDisplayToken != null) { 459 mHdrListener.unregister(mRegisteredDisplayToken); 460 mIsHdrLayerPresent = false; 461 } 462 } 463 464 /** 465 * Represents an event in which High Brightness Mode was enabled. 466 */ 467 private static class HbmEvent { 468 public long startTimeMillis; 469 public long endTimeMillis; 470 HbmEvent(long startTimeMillis, long endTimeMillis)471 HbmEvent(long startTimeMillis, long endTimeMillis) { 472 this.startTimeMillis = startTimeMillis; 473 this.endTimeMillis = endTimeMillis; 474 } 475 476 @Override toString()477 public String toString() { 478 return "[Event: {" + startTimeMillis + ", " + endTimeMillis + "}, total: " 479 + ((endTimeMillis - startTimeMillis) / 1000) + "]"; 480 } 481 } 482 483 @VisibleForTesting 484 class HdrListener extends SurfaceControlHdrLayerInfoListener { 485 @Override onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, int maxH, int flags)486 public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, 487 int maxW, int maxH, int flags) { 488 mHandler.post(() -> { 489 mIsHdrLayerPresent = numberOfHdrLayers > 0 490 && (float) (maxW * maxH) 491 >= ((float) (mWidth * mHeight) * HDR_PERCENT_OF_SCREEN_REQUIRED); 492 // Calling the brightness update so that we can recalculate 493 // brightness with HDR in mind. 494 onBrightnessChanged(mBrightness); 495 }); 496 } 497 } 498 499 private final class SkinThermalStatusObserver extends IThermalEventListener.Stub { 500 private final Injector mInjector; 501 private final Handler mHandler; 502 503 private IThermalService mThermalService; 504 private boolean mStarted; 505 SkinThermalStatusObserver(Injector injector, Handler handler)506 SkinThermalStatusObserver(Injector injector, Handler handler) { 507 mInjector = injector; 508 mHandler = handler; 509 } 510 511 @Override notifyThrottling(Temperature temp)512 public void notifyThrottling(Temperature temp) { 513 if (DEBUG) { 514 Slog.d(TAG, "New thermal throttling status " 515 + ", current thermal status = " + temp.getStatus() 516 + ", threshold = " + mHbmData.thermalStatusLimit); 517 } 518 mHandler.post(() -> { 519 mIsThermalStatusWithinLimit = temp.getStatus() <= mHbmData.thermalStatusLimit; 520 // This recalculates HbmMode and runs mHbmChangeCallback if the mode has changed 521 updateHbmMode(); 522 }); 523 } 524 startObserving()525 void startObserving() { 526 if (mStarted) { 527 if (DEBUG) { 528 Slog.d(TAG, "Thermal status observer already started"); 529 } 530 return; 531 } 532 mThermalService = mInjector.getThermalService(); 533 if (mThermalService == null) { 534 Slog.w(TAG, "Could not observe thermal status. Service not available"); 535 return; 536 } 537 try { 538 // We get a callback immediately upon registering so there's no need to query 539 // for the current value. 540 mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN); 541 mStarted = true; 542 } catch (RemoteException e) { 543 Slog.e(TAG, "Failed to register thermal status listener", e); 544 } 545 } 546 stopObserving()547 void stopObserving() { 548 mIsThermalStatusWithinLimit = true; 549 if (!mStarted) { 550 if (DEBUG) { 551 Slog.d(TAG, "Stop skipped because thermal status observer not started"); 552 } 553 return; 554 } 555 try { 556 mThermalService.unregisterThermalEventListener(this); 557 mStarted = false; 558 } catch (RemoteException e) { 559 Slog.e(TAG, "Failed to unregister thermal status listener", e); 560 } 561 mThermalService = null; 562 } 563 dump(PrintWriter writer)564 void dump(PrintWriter writer) { 565 writer.println(" SkinThermalStatusObserver:"); 566 writer.println(" mStarted: " + mStarted); 567 if (mThermalService != null) { 568 writer.println(" ThermalService available"); 569 } else { 570 writer.println(" ThermalService not available"); 571 } 572 } 573 } 574 575 private final class SettingsObserver extends ContentObserver { 576 private final Uri mLowPowerModeSetting = Settings.Global.getUriFor( 577 Settings.Global.LOW_POWER_MODE); 578 private boolean mStarted; 579 SettingsObserver(Handler handler)580 SettingsObserver(Handler handler) { 581 super(handler); 582 } 583 584 @Override onChange(boolean selfChange, Uri uri)585 public void onChange(boolean selfChange, Uri uri) { 586 updateLowPower(); 587 } 588 startObserving()589 void startObserving() { 590 if (!mStarted) { 591 mContext.getContentResolver().registerContentObserver(mLowPowerModeSetting, 592 false /*notifyForDescendants*/, this, UserHandle.USER_ALL); 593 mStarted = true; 594 updateLowPower(); 595 } 596 } 597 stopObserving()598 void stopObserving() { 599 mIsBlockedByLowPowerMode = false; 600 if (mStarted) { 601 mContext.getContentResolver().unregisterContentObserver(this); 602 mStarted = false; 603 } 604 } 605 updateLowPower()606 private void updateLowPower() { 607 final boolean isLowPowerMode = isLowPowerMode(); 608 if (isLowPowerMode == mIsBlockedByLowPowerMode) { 609 return; 610 } 611 if (DEBUG) { 612 Slog.d(TAG, "Settings.Global.LOW_POWER_MODE enabled: " + isLowPowerMode); 613 } 614 mIsBlockedByLowPowerMode = isLowPowerMode; 615 // this recalculates HbmMode and runs mHbmChangeCallback if the mode has changed 616 updateHbmMode(); 617 } 618 isLowPowerMode()619 private boolean isLowPowerMode() { 620 return Settings.Global.getInt( 621 mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) != 0; 622 } 623 } 624 625 public static class Injector { getClock()626 public Clock getClock() { 627 return SystemClock::uptimeMillis; 628 } 629 getThermalService()630 public IThermalService getThermalService() { 631 return IThermalService.Stub.asInterface( 632 ServiceManager.getService(Context.THERMAL_SERVICE)); 633 } 634 } 635 } 636