• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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