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