• 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.internal.util.FrameworkStatsLog;
41 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
42 import com.android.server.display.DisplayManagerService.Clock;
43 
44 import java.io.PrintWriter;
45 import java.util.ArrayDeque;
46 import java.util.Iterator;
47 
48 /**
49  * Controls the status of high-brightness mode for devices that support it. This class assumes that
50  * an instance is always created even if a device does not support high-brightness mode (HBM); in
51  * the case where it is not supported, the majority of the logic is skipped. On devices that support
52  * HBM, we keep track of the ambient lux as well as historical usage of HBM to determine when HBM is
53  * allowed and not. This class's output is simply a brightness-range maximum value (queried via
54  * {@link #getCurrentBrightnessMax}) that changes depending on whether HBM is enabled or not.
55  */
56 class HighBrightnessModeController {
57     private static final String TAG = "HighBrightnessModeController";
58 
59     private static final boolean DEBUG = false;
60 
61     @VisibleForTesting
62     static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
63 
64     public interface HdrBrightnessDeviceConfig {
getHdrBrightnessFromSdr(float sdrBrightness)65         float getHdrBrightnessFromSdr(float sdrBrightness);
66     }
67 
68     private final float mBrightnessMin;
69     private final float mBrightnessMax;
70     private final Handler mHandler;
71     private final Runnable mHbmChangeCallback;
72     private final Runnable mRecalcRunnable;
73     private final Clock mClock;
74     private final SkinThermalStatusObserver mSkinThermalStatusObserver;
75     private final Context mContext;
76     private final SettingsObserver mSettingsObserver;
77     private final Injector mInjector;
78 
79     private HdrListener mHdrListener;
80     private HighBrightnessModeData mHbmData;
81     private HdrBrightnessDeviceConfig mHdrBrightnessCfg;
82     private IBinder mRegisteredDisplayToken;
83 
84     private boolean mIsInAllowedAmbientRange = false;
85     private boolean mIsTimeAvailable = false;
86     private boolean mIsAutoBrightnessEnabled = false;
87     private boolean mIsAutoBrightnessOffByState = false;
88 
89     // The following values are typically reported by DisplayPowerController.
90     // This value includes brightness throttling effects.
91     private float mBrightness;
92     // This value excludes brightness throttling effects.
93     private float mUnthrottledBrightness;
94     private @BrightnessInfo.BrightnessMaxReason int mThrottlingReason =
95         BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
96 
97     private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
98     private boolean mIsHdrLayerPresent = false;
99     private boolean mIsThermalStatusWithinLimit = true;
100     private boolean mIsBlockedByLowPowerMode = false;
101     private int mWidth;
102     private int mHeight;
103     private float mAmbientLux;
104     private int mDisplayStatsId;
105     private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
106 
107     /**
108      * If HBM is currently running, this is the start time and set of all events,
109      * for the current HBM session.
110      */
111     private HighBrightnessModeMetadata mHighBrightnessModeMetadata = null;
112 
HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context)113     HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
114             String displayUniqueId, float brightnessMin, float brightnessMax,
115             HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
116             Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
117         this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin,
118             brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, context);
119     }
120 
121     @VisibleForTesting
HighBrightnessModeController(Injector injector, Handler handler, int width, int height, IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg, Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context)122     HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
123             IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax,
124             HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
125             Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
126         mInjector = injector;
127         mContext = context;
128         mClock = injector.getClock();
129         mHandler = handler;
130         mBrightness = brightnessMin;
131         mBrightnessMin = brightnessMin;
132         mBrightnessMax = brightnessMax;
133         mHbmChangeCallback = hbmChangeCallback;
134         mHighBrightnessModeMetadata = hbmMetadata;
135         mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
136         mSettingsObserver = new SettingsObserver(mHandler);
137         mRecalcRunnable = this::recalculateTimeAllowance;
138         mHdrListener = new HdrListener();
139 
140         resetHbmData(width, height, displayToken, displayUniqueId, hbmData, hdrBrightnessCfg);
141     }
142 
setAutoBrightnessEnabled(int state)143     void setAutoBrightnessEnabled(int state) {
144         final boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
145         mIsAutoBrightnessOffByState =
146                 state == AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
147         if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) {
148             return;
149         }
150         if (DEBUG) {
151             Slog.d(TAG, "setAutoBrightnessEnabled( " + isEnabled + " )");
152         }
153         mIsAutoBrightnessEnabled = isEnabled;
154         mIsInAllowedAmbientRange = false; // reset when auto-brightness switches
155         recalculateTimeAllowance();
156     }
157 
getCurrentBrightnessMin()158     float getCurrentBrightnessMin() {
159         return mBrightnessMin;
160     }
161 
getCurrentBrightnessMax()162     float getCurrentBrightnessMax() {
163         if (!deviceSupportsHbm() || isCurrentlyAllowed()) {
164             // Either the device doesn't support HBM, or HBM range is currently allowed (device
165             // it in a high-lux environment). In either case, return the highest brightness
166             // level supported by the device.
167             return mBrightnessMax;
168         } else {
169             // Hbm is not allowed, only allow up to the brightness where we
170             // transition to high brightness mode.
171             return mHbmData.transitionPoint;
172         }
173     }
174 
getNormalBrightnessMax()175     float getNormalBrightnessMax() {
176         return deviceSupportsHbm() ? mHbmData.transitionPoint : mBrightnessMax;
177     }
178 
getHdrBrightnessValue()179     float getHdrBrightnessValue() {
180         if (mHdrBrightnessCfg != null) {
181             float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr(mBrightness);
182             if (hdrBrightness != PowerManager.BRIGHTNESS_INVALID) {
183                 return hdrBrightness;
184             }
185         }
186 
187         // For HDR brightness, we take the current brightness and scale it to the max. The reason
188         // we do this is because we want brightness to go to HBM max when it would normally go
189         // to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition
190         // point happens to be) in order to go full HDR. Likewise, HDR on manual brightness should
191         // automatically scale the brightness without forcing the user to adjust to higher values.
192         return MathUtils.map(getCurrentBrightnessMin(), getCurrentBrightnessMax(),
193                 mBrightnessMin, mBrightnessMax, mBrightness);
194     }
195 
onAmbientLuxChange(float ambientLux)196     void onAmbientLuxChange(float ambientLux) {
197         mAmbientLux = ambientLux;
198         if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) {
199             return;
200         }
201 
202         final boolean isHighLux = (ambientLux >= mHbmData.minimumLux);
203         if (isHighLux != mIsInAllowedAmbientRange) {
204             mIsInAllowedAmbientRange = isHighLux;
205             recalculateTimeAllowance();
206         }
207     }
208 
onBrightnessChanged(float brightness, float unthrottledBrightness, @BrightnessInfo.BrightnessMaxReason int throttlingReason)209     void onBrightnessChanged(float brightness, float unthrottledBrightness,
210             @BrightnessInfo.BrightnessMaxReason int throttlingReason) {
211         if (!deviceSupportsHbm()) {
212             return;
213         }
214         mBrightness = brightness;
215         mUnthrottledBrightness = unthrottledBrightness;
216         mThrottlingReason = throttlingReason;
217 
218         // If we are starting or ending a high brightness mode session, store the current
219         // session in mRunningStartTimeMillis, or the old one in mEvents.
220         final long runningStartTime = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
221         final boolean wasHbmDrainingAvailableTime = runningStartTime != -1;
222         final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint
223                 && !mIsHdrLayerPresent;
224         if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) {
225             final long currentTime = mClock.uptimeMillis();
226             if (shouldHbmDrainAvailableTime) {
227                 mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
228             } else {
229                 final HbmEvent hbmEvent = new HbmEvent(runningStartTime, currentTime);
230                 mHighBrightnessModeMetadata.addHbmEvent(hbmEvent);
231                 mHighBrightnessModeMetadata.setRunningStartTimeMillis(-1);
232 
233                 if (DEBUG) {
234                     Slog.d(TAG, "New HBM event: "
235                             + mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst());
236                 }
237             }
238         }
239 
240         recalculateTimeAllowance();
241     }
242 
getHighBrightnessMode()243     int getHighBrightnessMode() {
244         return mHbmMode;
245     }
246 
getTransitionPoint()247     float getTransitionPoint() {
248         if (deviceSupportsHbm()) {
249             return mHbmData.transitionPoint;
250         } else {
251             return HBM_TRANSITION_POINT_INVALID;
252         }
253     }
254 
stop()255     void stop() {
256         registerHdrListener(null /*displayToken*/);
257         mSkinThermalStatusObserver.stopObserving();
258         mSettingsObserver.stopObserving();
259     }
260 
setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo)261     void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) {
262         mHighBrightnessModeMetadata = hbmInfo;
263     }
264 
resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg)265     void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId,
266             HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) {
267         mWidth = width;
268         mHeight = height;
269         mHbmData = hbmData;
270         mHdrBrightnessCfg = hdrBrightnessCfg;
271         mDisplayStatsId = displayUniqueId.hashCode();
272 
273         unregisterHdrListener();
274         mSkinThermalStatusObserver.stopObserving();
275         mSettingsObserver.stopObserving();
276         if (deviceSupportsHbm()) {
277             registerHdrListener(displayToken);
278             recalculateTimeAllowance();
279             if (mHbmData.thermalStatusLimit > PowerManager.THERMAL_STATUS_NONE) {
280                 mIsThermalStatusWithinLimit = true;
281                 mSkinThermalStatusObserver.startObserving();
282             }
283             if (!mHbmData.allowInLowPowerMode) {
284                 mIsBlockedByLowPowerMode = false;
285                 mSettingsObserver.startObserving();
286             }
287         }
288     }
289 
dump(PrintWriter pw)290     void dump(PrintWriter pw) {
291         mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
292     }
293 
294     @VisibleForTesting
getHdrListener()295     HdrListener getHdrListener() {
296         return mHdrListener;
297     }
298 
dumpLocal(PrintWriter pw)299     private void dumpLocal(PrintWriter pw) {
300         pw.println("HighBrightnessModeController:");
301         pw.println("  mBrightness=" + mBrightness);
302         pw.println("  mUnthrottledBrightness=" + mUnthrottledBrightness);
303         pw.println("  mThrottlingReason=" + BrightnessInfo.briMaxReasonToString(mThrottlingReason));
304         pw.println("  mCurrentMin=" + getCurrentBrightnessMin());
305         pw.println("  mCurrentMax=" + getCurrentBrightnessMax());
306         pw.println("  mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
307                 + (mHbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
308                 ? "(" + getHdrBrightnessValue() + ")" : ""));
309         pw.println("  mHbmStatsState=" + hbmStatsStateToString(mHbmStatsState));
310         pw.println("  mHbmData=" + mHbmData);
311         pw.println("  mAmbientLux=" + mAmbientLux
312                 + (mIsAutoBrightnessEnabled ? "" : " (old/invalid)"));
313         pw.println("  mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
314         pw.println("  mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
315         pw.println("  mIsAutoBrightnessOffByState=" + mIsAutoBrightnessOffByState);
316         pw.println("  mIsHdrLayerPresent=" + mIsHdrLayerPresent);
317         pw.println("  mBrightnessMin=" + mBrightnessMin);
318         pw.println("  mBrightnessMax=" + mBrightnessMax);
319         pw.println("  remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
320         pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
321         pw.println("  mRunningStartTimeMillis="
322                 + TimeUtils.formatUptime(mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
323         pw.println("  mIsThermalStatusWithinLimit=" + mIsThermalStatusWithinLimit);
324         pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
325         pw.println("  width*height=" + mWidth + "*" + mHeight);
326         pw.println("  mEvents=");
327         final long currentTime = mClock.uptimeMillis();
328         long lastStartTime = currentTime;
329         long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
330         if (runningStartTimeMillis != -1) {
331             lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime));
332         }
333         for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) {
334             if (lastStartTime > event.getEndTimeMillis()) {
335                 pw.println("    event: [normal brightness]: "
336                         + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis()));
337             }
338             lastStartTime = dumpHbmEvent(pw, event);
339         }
340 
341         mSkinThermalStatusObserver.dump(pw);
342     }
343 
dumpHbmEvent(PrintWriter pw, HbmEvent event)344     private long dumpHbmEvent(PrintWriter pw, HbmEvent event) {
345         final long duration = event.getEndTimeMillis() - event.getStartTimeMillis();
346         pw.println("    event: ["
347                 + TimeUtils.formatUptime(event.getStartTimeMillis()) + ", "
348                 + TimeUtils.formatUptime(event.getEndTimeMillis()) + "] ("
349                 + TimeUtils.formatDuration(duration) + ")");
350         return event.getStartTimeMillis();
351     }
352 
isCurrentlyAllowed()353     private boolean isCurrentlyAllowed() {
354         // Returns true if HBM is allowed (above the ambient lux threshold) and there's still
355         // time within the current window for additional HBM usage. We return false if there is an
356         // HDR layer because we don't want the brightness MAX to change for HDR, which has its
357         // brightness scaled in a different way than sunlight HBM that doesn't require changing
358         // the MAX. HDR also needs to work under manual brightness which never adjusts the
359         // brightness maximum; so we implement HDR-HBM in a way that doesn't adjust the max.
360         // See {@link #getHdrBrightnessValue}.
361         return !mIsHdrLayerPresent
362                 && (mIsAutoBrightnessEnabled && mIsTimeAvailable && mIsInAllowedAmbientRange
363                 && mIsThermalStatusWithinLimit && !mIsBlockedByLowPowerMode);
364     }
365 
deviceSupportsHbm()366     private boolean deviceSupportsHbm() {
367         return mHbmData != null;
368     }
369 
calculateRemainingTime(long currentTime)370     private long calculateRemainingTime(long currentTime) {
371         if (!deviceSupportsHbm()) {
372             return 0;
373         }
374 
375         long timeAlreadyUsed = 0;
376 
377         // First, lets see how much time we've taken for any currently running
378         // session of HBM.
379         long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
380         if (runningStartTimeMillis > 0) {
381             if (runningStartTimeMillis > currentTime) {
382                 Slog.e(TAG, "Start time set to the future. curr: " + currentTime
383                         + ", start: " + runningStartTimeMillis);
384                 mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
385                 runningStartTimeMillis = currentTime;
386             }
387             timeAlreadyUsed = currentTime - runningStartTimeMillis;
388         }
389 
390         if (DEBUG) {
391             Slog.d(TAG, "Time already used after current session: " + timeAlreadyUsed);
392         }
393 
394         // Next, lets iterate through the history of previous sessions and add those times.
395         final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
396         Iterator<HbmEvent> it = mHighBrightnessModeMetadata.getHbmEventQueue().iterator();
397         while (it.hasNext()) {
398             final HbmEvent event = it.next();
399 
400             // If this event ended before the current Timing window, discard forever and ever.
401             if (event.getEndTimeMillis() < windowstartTimeMillis) {
402                 it.remove();
403                 continue;
404             }
405 
406             final long startTimeMillis = Math.max(event.getStartTimeMillis(),
407                             windowstartTimeMillis);
408             timeAlreadyUsed += event.getEndTimeMillis() - startTimeMillis;
409         }
410 
411         if (DEBUG) {
412             Slog.d(TAG, "Time already used after all sessions: " + timeAlreadyUsed);
413         }
414 
415         return Math.max(0, mHbmData.timeMaxMillis - timeAlreadyUsed);
416     }
417 
418     /**
419      * Recalculates the allowable HBM time.
420      */
recalculateTimeAllowance()421     private void recalculateTimeAllowance() {
422         final long currentTime = mClock.uptimeMillis();
423         final long remainingTime = calculateRemainingTime(currentTime);
424 
425         // We allow HBM if there is more than the minimum required time available
426         // or if brightness is already in the high range, if there is any time left at all.
427         final boolean isAllowedWithoutRestrictions = remainingTime >= mHbmData.timeMinMillis;
428         final boolean isOnlyAllowedToStayOn = !isAllowedWithoutRestrictions
429                 && remainingTime > 0 && mBrightness > mHbmData.transitionPoint;
430         mIsTimeAvailable = isAllowedWithoutRestrictions || isOnlyAllowedToStayOn;
431 
432         // Calculate the time at which we want to recalculate mIsTimeAvailable in case a lux or
433         // brightness change doesn't happen before then.
434         long nextTimeout = -1;
435         final ArrayDeque<HbmEvent> hbmEvents = mHighBrightnessModeMetadata.getHbmEventQueue();
436         if (mBrightness > mHbmData.transitionPoint) {
437             // if we're in high-lux now, timeout when we run out of allowed time.
438             nextTimeout = currentTime + remainingTime;
439         } else if (!mIsTimeAvailable && hbmEvents.size() > 0) {
440             // If we are not allowed...timeout when the oldest event moved outside of the timing
441             // window by at least minTime. Basically, we're calculating the soonest time we can
442             // get {@code timeMinMillis} back to us.
443             final long windowstartTimeMillis = currentTime - mHbmData.timeWindowMillis;
444             final HbmEvent lastEvent = hbmEvents.peekLast();
445             final long startTimePlusMinMillis =
446                     Math.max(windowstartTimeMillis, lastEvent.getStartTimeMillis())
447                     + mHbmData.timeMinMillis;
448             final long timeWhenMinIsGainedBack =
449                     currentTime + (startTimePlusMinMillis - windowstartTimeMillis) - remainingTime;
450             nextTimeout = timeWhenMinIsGainedBack;
451         }
452 
453         if (DEBUG) {
454             Slog.d(TAG, "HBM recalculated.  IsAllowedWithoutRestrictions: "
455                     + isAllowedWithoutRestrictions
456                     + ", isOnlyAllowedToStayOn: " + isOnlyAllowedToStayOn
457                     + ", remainingAllowedTime: " + remainingTime
458                     + ", isLuxHigh: " + mIsInAllowedAmbientRange
459                     + ", isHBMCurrentlyAllowed: " + isCurrentlyAllowed()
460                     + ", isHdrLayerPresent: " + mIsHdrLayerPresent
461                     + ", isAutoBrightnessEnabled: " +  mIsAutoBrightnessEnabled
462                     + ", mIsTimeAvailable: " + mIsTimeAvailable
463                     + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
464                     + ", mIsThermalStatusWithinLimit: " + mIsThermalStatusWithinLimit
465                     + ", mIsBlockedByLowPowerMode: " + mIsBlockedByLowPowerMode
466                     + ", mBrightness: " + mBrightness
467                     + ", mUnthrottledBrightness: " + mUnthrottledBrightness
468                     + ", mThrottlingReason: "
469                         + BrightnessInfo.briMaxReasonToString(mThrottlingReason)
470                     + ", RunningStartTimeMillis: "
471                         + mHighBrightnessModeMetadata.getRunningStartTimeMillis()
472                     + ", nextTimeout: " + (nextTimeout != -1 ? (nextTimeout - currentTime) : -1)
473                     + ", events: " + hbmEvents);
474         }
475 
476         if (nextTimeout != -1) {
477             mHandler.removeCallbacks(mRecalcRunnable);
478             mHandler.postAtTime(mRecalcRunnable, nextTimeout + 1);
479         }
480         // Update the state of the world
481         updateHbmMode();
482     }
483 
updateHbmMode()484     private void updateHbmMode() {
485         int newHbmMode = calculateHighBrightnessMode();
486         updateHbmStats(newHbmMode);
487         if (mHbmMode != newHbmMode) {
488             mHbmMode = newHbmMode;
489             mHbmChangeCallback.run();
490         }
491     }
492 
updateHbmStats(int newMode)493     private void updateHbmStats(int newMode) {
494         final float transitionPoint = mHbmData.transitionPoint;
495         int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
496         if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
497                 && getHdrBrightnessValue() > transitionPoint) {
498             state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
499         } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT
500                 && mBrightness > transitionPoint) {
501             state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
502         }
503         if (state == mHbmStatsState) {
504             return;
505         }
506 
507         int reason =
508                 FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN;
509         final boolean oldHbmSv = (mHbmStatsState
510                 == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
511         final boolean newHbmSv =
512                 (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
513         if (oldHbmSv && !newHbmSv) {
514             // HighBrightnessModeController (HBMC) currently supports throttling from two sources:
515             //     1. Internal, received from HBMC.SkinThermalStatusObserver.notifyThrottling()
516             //     2. External, received from HBMC.onBrightnessChanged()
517             // TODO(b/216373254): Deprecate internal throttling source
518             final boolean internalThermalThrottling = !mIsThermalStatusWithinLimit;
519             final boolean externalThermalThrottling =
520                 mUnthrottledBrightness > transitionPoint && // We would've liked HBM brightness...
521                 mBrightness <= transitionPoint &&           // ...but we got NBM, because of...
522                 mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; // ...thermals.
523 
524             // If more than one conditions are flipped and turn off HBM sunlight
525             // visibility, only one condition will be reported to make it simple.
526             if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) {
527                 reason = FrameworkStatsLog
528                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF;
529             } else if (!mIsAutoBrightnessEnabled) {
530                 reason = FrameworkStatsLog
531                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_AUTOBRIGHTNESS_OFF;
532             } else if (!mIsInAllowedAmbientRange) {
533                 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP;
534             } else if (!mIsTimeAvailable) {
535                 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT;
536             } else if (internalThermalThrottling || externalThermalThrottling) {
537                 reason = FrameworkStatsLog
538                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
539             } else if (mIsHdrLayerPresent) {
540                 reason = FrameworkStatsLog
541                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING;
542             } else if (mIsBlockedByLowPowerMode) {
543                 reason = FrameworkStatsLog
544                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON;
545             } else if (mBrightness <= mHbmData.transitionPoint) {
546                 // This must be after external thermal check.
547                 reason = FrameworkStatsLog
548                             .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS;
549             }
550         }
551 
552         mInjector.reportHbmStateChange(mDisplayStatsId, state, reason);
553         mHbmStatsState = state;
554     }
555 
hbmStatsStateToString(int hbmStatsState)556     private String hbmStatsStateToString(int hbmStatsState) {
557         switch (hbmStatsState) {
558         case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF:
559             return "HBM_OFF";
560         case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR:
561             return "HBM_ON_HDR";
562         case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT:
563             return "HBM_ON_SUNLIGHT";
564         default:
565             return String.valueOf(hbmStatsState);
566         }
567     }
568 
calculateHighBrightnessMode()569     private int calculateHighBrightnessMode() {
570         if (!deviceSupportsHbm()) {
571             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
572         } else if (mIsHdrLayerPresent) {
573             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
574         } else if (isCurrentlyAllowed()) {
575             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
576         }
577 
578         return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
579     }
580 
registerHdrListener(IBinder displayToken)581     private void registerHdrListener(IBinder displayToken) {
582         if (mRegisteredDisplayToken == displayToken) {
583             return;
584         }
585 
586         unregisterHdrListener();
587         mRegisteredDisplayToken = displayToken;
588         if (mRegisteredDisplayToken != null) {
589             mHdrListener.register(mRegisteredDisplayToken);
590         }
591     }
592 
unregisterHdrListener()593     private void unregisterHdrListener() {
594         if (mRegisteredDisplayToken != null) {
595             mHdrListener.unregister(mRegisteredDisplayToken);
596             mIsHdrLayerPresent = false;
597         }
598     }
599 
600     @VisibleForTesting
601     class HdrListener extends SurfaceControlHdrLayerInfoListener {
602         @Override
onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, int maxH, int flags)603         public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
604                 int maxW, int maxH, int flags) {
605             mHandler.post(() -> {
606                 mIsHdrLayerPresent = numberOfHdrLayers > 0
607                         && (float) (maxW * maxH) >= ((float) (mWidth * mHeight)
608                                    * mHbmData.minimumHdrPercentOfScreen);
609                 // Calling the brightness update so that we can recalculate
610                 // brightness with HDR in mind.
611                 onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason);
612             });
613         }
614     }
615 
616     private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
617         private final Injector mInjector;
618         private final Handler mHandler;
619 
620         private IThermalService mThermalService;
621         private boolean mStarted;
622 
SkinThermalStatusObserver(Injector injector, Handler handler)623         SkinThermalStatusObserver(Injector injector, Handler handler) {
624             mInjector = injector;
625             mHandler = handler;
626         }
627 
628         @Override
notifyThrottling(Temperature temp)629         public void notifyThrottling(Temperature temp) {
630             if (DEBUG) {
631                 Slog.d(TAG, "New thermal throttling status "
632                         + ", current thermal status = " + temp.getStatus()
633                         + ", threshold = " + mHbmData.thermalStatusLimit);
634             }
635             mHandler.post(() -> {
636                 mIsThermalStatusWithinLimit = temp.getStatus() <= mHbmData.thermalStatusLimit;
637                 // This recalculates HbmMode and runs mHbmChangeCallback if the mode has changed
638                 updateHbmMode();
639             });
640         }
641 
startObserving()642         void startObserving() {
643             if (mStarted) {
644                 if (DEBUG) {
645                     Slog.d(TAG, "Thermal status observer already started");
646                 }
647                 return;
648             }
649             mThermalService = mInjector.getThermalService();
650             if (mThermalService == null) {
651                 Slog.w(TAG, "Could not observe thermal status. Service not available");
652                 return;
653             }
654             try {
655                 // We get a callback immediately upon registering so there's no need to query
656                 // for the current value.
657                 mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN);
658                 mStarted = true;
659             } catch (RemoteException e) {
660                 Slog.e(TAG, "Failed to register thermal status listener", e);
661             }
662         }
663 
stopObserving()664         void stopObserving() {
665             mIsThermalStatusWithinLimit = true;
666             if (!mStarted) {
667                 if (DEBUG) {
668                     Slog.d(TAG, "Stop skipped because thermal status observer not started");
669                 }
670                 return;
671             }
672             try {
673                 mThermalService.unregisterThermalEventListener(this);
674                 mStarted = false;
675             } catch (RemoteException e) {
676                 Slog.e(TAG, "Failed to unregister thermal status listener", e);
677             }
678             mThermalService = null;
679         }
680 
dump(PrintWriter writer)681         void dump(PrintWriter writer) {
682             writer.println("  SkinThermalStatusObserver:");
683             writer.println("    mStarted: " + mStarted);
684             if (mThermalService != null) {
685                 writer.println("    ThermalService available");
686             } else {
687                 writer.println("    ThermalService not available");
688             }
689         }
690     }
691 
692     private final class SettingsObserver extends ContentObserver {
693         private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(
694                 Settings.Global.LOW_POWER_MODE);
695         private boolean mStarted;
696 
SettingsObserver(Handler handler)697         SettingsObserver(Handler handler) {
698             super(handler);
699         }
700 
701         @Override
onChange(boolean selfChange, Uri uri)702         public void onChange(boolean selfChange, Uri uri) {
703             updateLowPower();
704         }
705 
startObserving()706         void startObserving() {
707             if (!mStarted) {
708                 mContext.getContentResolver().registerContentObserver(mLowPowerModeSetting,
709                         false /*notifyForDescendants*/, this, UserHandle.USER_ALL);
710                 mStarted = true;
711                 updateLowPower();
712             }
713         }
714 
stopObserving()715         void stopObserving() {
716             mIsBlockedByLowPowerMode = false;
717             if (mStarted) {
718                 mContext.getContentResolver().unregisterContentObserver(this);
719                 mStarted = false;
720             }
721         }
722 
updateLowPower()723         private void updateLowPower() {
724             final boolean isLowPowerMode = isLowPowerMode();
725             if (isLowPowerMode == mIsBlockedByLowPowerMode) {
726                 return;
727             }
728             if (DEBUG) {
729                 Slog.d(TAG, "Settings.Global.LOW_POWER_MODE enabled: " + isLowPowerMode);
730             }
731             mIsBlockedByLowPowerMode = isLowPowerMode;
732             // this recalculates HbmMode and runs mHbmChangeCallback if the mode has changed
733             updateHbmMode();
734         }
735 
isLowPowerMode()736         private boolean isLowPowerMode() {
737             return Settings.Global.getInt(
738                     mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) != 0;
739         }
740     }
741 
742     public static class Injector {
getClock()743         public Clock getClock() {
744             return SystemClock::uptimeMillis;
745         }
746 
getThermalService()747         public IThermalService getThermalService() {
748             return IThermalService.Stub.asInterface(
749                     ServiceManager.getService(Context.THERMAL_SERVICE));
750         }
751 
reportHbmStateChange(int display, int state, int reason)752         public void reportHbmStateChange(int display, int state, int reason) {
753             FrameworkStatsLog.write(
754                     FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason);
755         }
756     }
757 }
758