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