• 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.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.UserHandle;
29 import android.provider.Settings;
30 import android.util.MathUtils;
31 import android.util.Slog;
32 import android.util.TimeUtils;
33 import android.view.SurfaceControlHdrLayerInfoListener;
34 
35 import com.android.internal.annotations.VisibleForTesting;
36 import com.android.internal.util.FrameworkStatsLog;
37 import com.android.server.display.DisplayDeviceConfig.HighBrightnessModeData;
38 import com.android.server.display.DisplayManagerService.Clock;
39 
40 import java.io.PrintWriter;
41 import java.util.ArrayDeque;
42 import java.util.Iterator;
43 
44 /**
45  * Controls the status of high-brightness mode for devices that support it. This class assumes that
46  * an instance is always created even if a device does not support high-brightness mode (HBM); in
47  * the case where it is not supported, the majority of the logic is skipped. On devices that support
48  * HBM, we keep track of the ambient lux as well as historical usage of HBM to determine when HBM is
49  * allowed and not. This class's output is simply a brightness-range maximum value (queried via
50  * {@link #getCurrentBrightnessMax}) that changes depending on whether HBM is enabled or not.
51  */
52 class HighBrightnessModeController {
53     private static final String TAG = "HighBrightnessModeController";
54 
55     private static final boolean DEBUG = false;
56 
57     @VisibleForTesting
58     static final float HBM_TRANSITION_POINT_INVALID = Float.POSITIVE_INFINITY;
59 
60     private static final float DEFAULT_MAX_DESIRED_HDR_SDR_RATIO = 1.0f;
61 
62     public interface HdrBrightnessDeviceConfig {
63         // maxDesiredHdrSdrRatio will restrict the HDR brightness if the ratio is less than
64         // Float.POSITIVE_INFINITY
getHdrBrightnessFromSdr(float sdrBrightness, float maxDesiredHdrSdrRatio)65         float getHdrBrightnessFromSdr(float sdrBrightness, float maxDesiredHdrSdrRatio);
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 Context mContext;
75     private final SettingsObserver mSettingsObserver;
76     private final Injector mInjector;
77 
78     private HdrListener mHdrListener;
79 
80     @Nullable
81     private HighBrightnessModeData mHbmData;
82     private HdrBrightnessDeviceConfig mHdrBrightnessCfg;
83     private IBinder mRegisteredDisplayToken;
84 
85     private boolean mIsInAllowedAmbientRange = false;
86     private boolean mIsTimeAvailable = false;
87     private boolean mIsAutoBrightnessEnabled = false;
88     private boolean mIsAutoBrightnessOffByState = false;
89 
90     // The following values are typically reported by DisplayPowerController.
91     // This value includes brightness throttling effects.
92     private float mBrightness;
93     // This value excludes brightness throttling effects.
94     private float mUnthrottledBrightness;
95     private @BrightnessInfo.BrightnessMaxReason int mThrottlingReason =
96         BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
97 
98     private int mHbmMode = BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
99     private boolean mIsHdrLayerPresent = false;
100     // mMaxDesiredHdrSdrRatio should only be applied when there is a valid backlight->nits mapping
101     private float mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
102     private boolean mIsBlockedByLowPowerMode = false;
103     private int mWidth;
104     private int mHeight;
105     private float mAmbientLux;
106     private int mDisplayStatsId;
107     private int mHbmStatsState = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
108 
109     /**
110      * If HBM is currently running, this is the start time and set of all events,
111      * for the current HBM session.
112      */
113     @Nullable
114     private HighBrightnessModeMetadata mHighBrightnessModeMetadata;
115 
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)116     HighBrightnessModeController(Handler handler, int width, int height, IBinder displayToken,
117             String displayUniqueId, float brightnessMin, float brightnessMax,
118             HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
119             Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
120         this(new Injector(), handler, width, height, displayToken, displayUniqueId, brightnessMin,
121             brightnessMax, hbmData, hdrBrightnessCfg, hbmChangeCallback, hbmMetadata, context);
122     }
123 
124     @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)125     HighBrightnessModeController(Injector injector, Handler handler, int width, int height,
126             IBinder displayToken, String displayUniqueId, float brightnessMin, float brightnessMax,
127             HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg,
128             Runnable hbmChangeCallback, HighBrightnessModeMetadata hbmMetadata, Context context) {
129         mInjector = injector;
130         mContext = context;
131         mClock = injector.getClock();
132         mHandler = handler;
133         mBrightness = brightnessMin;
134         mBrightnessMin = brightnessMin;
135         mBrightnessMax = brightnessMax;
136         mHbmChangeCallback = hbmChangeCallback;
137         mHighBrightnessModeMetadata = hbmMetadata;
138         mSettingsObserver = new SettingsObserver(mHandler);
139         mRecalcRunnable = this::recalculateTimeAllowance;
140         mHdrListener = new HdrListener();
141 
142         resetHbmData(width, height, displayToken, displayUniqueId, hbmData, hdrBrightnessCfg);
143     }
144 
setAutoBrightnessEnabled(int state)145     void setAutoBrightnessEnabled(int state) {
146         final boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
147         mIsAutoBrightnessOffByState =
148                 state == AutomaticBrightnessController.AUTO_BRIGHTNESS_OFF_DUE_TO_DISPLAY_STATE;
149         if (!deviceSupportsHbm() || isEnabled == mIsAutoBrightnessEnabled) {
150             return;
151         }
152         if (DEBUG) {
153             Slog.d(TAG, "setAutoBrightnessEnabled( " + isEnabled + " )");
154         }
155         mIsAutoBrightnessEnabled = isEnabled;
156         mIsInAllowedAmbientRange = false; // reset when auto-brightness switches
157         recalculateTimeAllowance();
158     }
159 
getCurrentBrightnessMin()160     float getCurrentBrightnessMin() {
161         return mBrightnessMin;
162     }
163 
getCurrentBrightnessMax()164     float getCurrentBrightnessMax() {
165         if (!deviceSupportsHbm() || isCurrentlyAllowed()) {
166             // Either the device doesn't support HBM, or HBM range is currently allowed (device
167             // it in a high-lux environment). In either case, return the highest brightness
168             // level supported by the device.
169             return mBrightnessMax;
170         } else {
171             // Hbm is not allowed, only allow up to the brightness where we
172             // transition to high brightness mode.
173             return mHbmData.transitionPoint;
174         }
175     }
176 
getNormalBrightnessMax()177     float getNormalBrightnessMax() {
178         return deviceSupportsHbm() ? mHbmData.transitionPoint : mBrightnessMax;
179     }
180 
getHdrBrightnessValue()181     float getHdrBrightnessValue() {
182         if (mHdrBrightnessCfg != null) {
183             float hdrBrightness = mHdrBrightnessCfg.getHdrBrightnessFromSdr(
184                     mBrightness, mMaxDesiredHdrSdrRatio);
185             if (hdrBrightness != PowerManager.BRIGHTNESS_INVALID) {
186                 return hdrBrightness;
187             }
188         }
189 
190         // For HDR brightness, we take the current brightness and scale it to the max. The reason
191         // we do this is because we want brightness to go to HBM max when it would normally go
192         // to normal max, meaning it should not wait to go to 10000 lux (or whatever the transition
193         // point happens to be) in order to go full HDR. Likewise, HDR on manual brightness should
194         // automatically scale the brightness without forcing the user to adjust to higher values.
195         return MathUtils.map(getCurrentBrightnessMin(), getCurrentBrightnessMax(),
196                 mBrightnessMin, mBrightnessMax, mBrightness);
197     }
198 
onAmbientLuxChange(float ambientLux)199     void onAmbientLuxChange(float ambientLux) {
200         mAmbientLux = ambientLux;
201         if (!deviceSupportsHbm() || !mIsAutoBrightnessEnabled) {
202             return;
203         }
204 
205         final boolean isHighLux = (ambientLux >= mHbmData.minimumLux);
206         if (isHighLux != mIsInAllowedAmbientRange) {
207             mIsInAllowedAmbientRange = isHighLux;
208             recalculateTimeAllowance();
209         }
210     }
211 
onBrightnessChanged(float brightness, float unthrottledBrightness, @BrightnessInfo.BrightnessMaxReason int throttlingReason)212     void onBrightnessChanged(float brightness, float unthrottledBrightness,
213             @BrightnessInfo.BrightnessMaxReason int throttlingReason) {
214         if (!deviceSupportsHbm()) {
215             return;
216         }
217         mBrightness = brightness;
218         mUnthrottledBrightness = unthrottledBrightness;
219         mThrottlingReason = throttlingReason;
220 
221         // If we are starting or ending a high brightness mode session, store the current
222         // session in mRunningStartTimeMillis, or the old one in mEvents.
223         final long runningStartTime = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
224         final boolean wasHbmDrainingAvailableTime = runningStartTime != -1;
225         final boolean shouldHbmDrainAvailableTime = mBrightness > mHbmData.transitionPoint
226                 && !mIsHdrLayerPresent;
227         if (wasHbmDrainingAvailableTime != shouldHbmDrainAvailableTime) {
228             final long currentTime = mClock.uptimeMillis();
229             if (shouldHbmDrainAvailableTime) {
230                 mHighBrightnessModeMetadata.setRunningStartTimeMillis(currentTime);
231             } else {
232                 final HbmEvent hbmEvent = new HbmEvent(runningStartTime, currentTime);
233                 mHighBrightnessModeMetadata.addHbmEvent(hbmEvent);
234                 mHighBrightnessModeMetadata.setRunningStartTimeMillis(-1);
235 
236                 if (DEBUG) {
237                     Slog.d(TAG, "New HBM event: "
238                             + mHighBrightnessModeMetadata.getHbmEventQueue().peekFirst());
239                 }
240             }
241         }
242 
243         recalculateTimeAllowance();
244     }
245 
getHighBrightnessMode()246     int getHighBrightnessMode() {
247         return mHbmMode;
248     }
249 
getTransitionPoint()250     float getTransitionPoint() {
251         if (deviceSupportsHbm()) {
252             return mHbmData.transitionPoint;
253         } else {
254             return HBM_TRANSITION_POINT_INVALID;
255         }
256     }
257 
stop()258     void stop() {
259         registerHdrListener(null /*displayToken*/);
260         mSettingsObserver.stopObserving();
261     }
262 
setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo)263     void setHighBrightnessModeMetadata(HighBrightnessModeMetadata hbmInfo) {
264         mHighBrightnessModeMetadata = hbmInfo;
265     }
266 
resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId, HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg)267     void resetHbmData(int width, int height, IBinder displayToken, String displayUniqueId,
268             HighBrightnessModeData hbmData, HdrBrightnessDeviceConfig hdrBrightnessCfg) {
269         mWidth = width;
270         mHeight = height;
271         mHbmData = hbmData;
272         mHdrBrightnessCfg = hdrBrightnessCfg;
273         mDisplayStatsId = displayUniqueId.hashCode();
274 
275         unregisterHdrListener();
276         mSettingsObserver.stopObserving();
277         if (deviceSupportsHbm()) {
278             registerHdrListener(displayToken);
279             recalculateTimeAllowance();
280             if (!mHbmData.allowInLowPowerMode) {
281                 mIsBlockedByLowPowerMode = false;
282                 mSettingsObserver.startObserving();
283             }
284         }
285     }
286 
dump(PrintWriter pw)287     void dump(PrintWriter pw) {
288         mHandler.runWithScissors(() -> dumpLocal(pw), 1000);
289     }
290 
291     @VisibleForTesting
getHdrListener()292     HdrListener getHdrListener() {
293         return mHdrListener;
294     }
295 
dumpLocal(PrintWriter pw)296     private void dumpLocal(PrintWriter pw) {
297         pw.println("HighBrightnessModeController:");
298         pw.println("  mBrightness=" + mBrightness);
299         pw.println("  mUnthrottledBrightness=" + mUnthrottledBrightness);
300         pw.println("  mThrottlingReason=" + BrightnessInfo.briMaxReasonToString(mThrottlingReason));
301         pw.println("  mCurrentMin=" + getCurrentBrightnessMin());
302         pw.println("  mCurrentMax=" + getCurrentBrightnessMax());
303         pw.println("  mHbmMode=" + BrightnessInfo.hbmToString(mHbmMode)
304                 + (mHbmMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
305                 ? "(" + getHdrBrightnessValue() + ")" : ""));
306         pw.println("  mHbmStatsState=" + hbmStatsStateToString(mHbmStatsState));
307         pw.println("  mHbmData=" + mHbmData);
308         pw.println("  mAmbientLux=" + mAmbientLux
309                 + (mIsAutoBrightnessEnabled ? "" : " (old/invalid)"));
310         pw.println("  mIsInAllowedAmbientRange=" + mIsInAllowedAmbientRange);
311         pw.println("  mIsAutoBrightnessEnabled=" + mIsAutoBrightnessEnabled);
312         pw.println("  mIsAutoBrightnessOffByState=" + mIsAutoBrightnessOffByState);
313         pw.println("  mIsHdrLayerPresent=" + mIsHdrLayerPresent);
314         pw.println("  mBrightnessMin=" + mBrightnessMin);
315         pw.println("  mBrightnessMax=" + mBrightnessMax);
316         pw.println("  remainingTime=" + calculateRemainingTime(mClock.uptimeMillis()));
317         pw.println("  mIsTimeAvailable= " + mIsTimeAvailable);
318         pw.println("  mIsBlockedByLowPowerMode=" + mIsBlockedByLowPowerMode);
319         pw.println("  width*height=" + mWidth + "*" + mHeight);
320 
321         if (mHighBrightnessModeMetadata != null) {
322             pw.println("  mRunningStartTimeMillis="
323                     + TimeUtils.formatUptime(
324                     mHighBrightnessModeMetadata.getRunningStartTimeMillis()));
325             pw.println("  mEvents=");
326             final long currentTime = mClock.uptimeMillis();
327             long lastStartTime = currentTime;
328             long runningStartTimeMillis = mHighBrightnessModeMetadata.getRunningStartTimeMillis();
329             if (runningStartTimeMillis != -1) {
330                 lastStartTime = dumpHbmEvent(pw, new HbmEvent(runningStartTimeMillis, currentTime));
331             }
332             for (HbmEvent event : mHighBrightnessModeMetadata.getHbmEventQueue()) {
333                 if (lastStartTime > event.getEndTimeMillis()) {
334                     pw.println("    event: [normal brightness]: "
335                             + TimeUtils.formatDuration(lastStartTime - event.getEndTimeMillis()));
336                 }
337                 lastStartTime = dumpHbmEvent(pw, event);
338             }
339         } else {
340             pw.println("  mHighBrightnessModeMetadata=null");
341         }
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                 && !mIsBlockedByLowPowerMode);
364     }
365 
deviceSupportsHbm()366     private boolean deviceSupportsHbm() {
367         return mHbmData != null && mHighBrightnessModeMetadata != 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                     + ", mMaxDesiredHdrSdrRatio: " + mMaxDesiredHdrSdrRatio
462                     + ", isAutoBrightnessEnabled: " +  mIsAutoBrightnessEnabled
463                     + ", mIsTimeAvailable: " + mIsTimeAvailable
464                     + ", mIsInAllowedAmbientRange: " + mIsInAllowedAmbientRange
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         int state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF;
495         if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR
496                 && getHdrBrightnessValue() > mHbmData.transitionPoint) {
497             state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR;
498         } else if (newMode == BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT
499                 && mBrightness > mHbmData.transitionPoint) {
500             state = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT;
501         }
502         if (state == mHbmStatsState) {
503             return;
504         }
505 
506         int reason =
507                 FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_TRANSITION_REASON_UNKNOWN;
508         final boolean oldHbmSv = (mHbmStatsState
509                 == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
510         final boolean newHbmSv =
511                 (state == FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT);
512         if (oldHbmSv && !newHbmSv) {
513             // If more than one conditions are flipped and turn off HBM sunlight
514             // visibility, only one condition will be reported to make it simple.
515             if (!mIsAutoBrightnessEnabled && mIsAutoBrightnessOffByState) {
516                 reason = FrameworkStatsLog
517                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_DISPLAY_OFF;
518             } else if (!mIsAutoBrightnessEnabled) {
519                 reason = FrameworkStatsLog
520                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_AUTOBRIGHTNESS_OFF;
521             } else if (!mIsInAllowedAmbientRange) {
522                 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LUX_DROP;
523             } else if (!mIsTimeAvailable) {
524                 reason = FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_TIME_LIMIT;
525             } else if (isThermalThrottlingActive()) {
526                 reason = FrameworkStatsLog
527                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_THERMAL_LIMIT;
528             } else if (mIsHdrLayerPresent) {
529                 reason = FrameworkStatsLog
530                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_HDR_PLAYING;
531             } else if (mIsBlockedByLowPowerMode) {
532                 reason = FrameworkStatsLog
533                                  .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_BATTERY_SAVE_ON;
534             } else if (mBrightness <= mHbmData.transitionPoint) {
535                 // This must be after external thermal check.
536                 reason = FrameworkStatsLog
537                             .DISPLAY_HBM_STATE_CHANGED__REASON__HBM_SV_OFF_LOW_REQUESTED_BRIGHTNESS;
538             }
539         }
540 
541         mInjector.reportHbmStateChange(mDisplayStatsId, state, reason);
542         mHbmStatsState = state;
543     }
544 
545     @VisibleForTesting
isThermalThrottlingActive()546     boolean isThermalThrottlingActive() {
547         // We would've liked HBM, but we got NBM (normal brightness mode) because of thermals.
548         return mUnthrottledBrightness > mHbmData.transitionPoint
549                 && mBrightness <= mHbmData.transitionPoint
550                 && mThrottlingReason == BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL;
551     }
552 
hbmStatsStateToString(int hbmStatsState)553     private String hbmStatsStateToString(int hbmStatsState) {
554         switch (hbmStatsState) {
555         case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_OFF:
556             return "HBM_OFF";
557         case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_HDR:
558             return "HBM_ON_HDR";
559         case FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED__STATE__HBM_ON_SUNLIGHT:
560             return "HBM_ON_SUNLIGHT";
561         default:
562             return String.valueOf(hbmStatsState);
563         }
564     }
565 
calculateHighBrightnessMode()566     private int calculateHighBrightnessMode() {
567         if (!deviceSupportsHbm()) {
568             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
569         } else if (mIsHdrLayerPresent) {
570             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_HDR;
571         } else if (isCurrentlyAllowed()) {
572             return BrightnessInfo.HIGH_BRIGHTNESS_MODE_SUNLIGHT;
573         }
574 
575         return BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF;
576     }
577 
registerHdrListener(IBinder displayToken)578     private void registerHdrListener(IBinder displayToken) {
579         if (mRegisteredDisplayToken == displayToken) {
580             return;
581         }
582 
583         unregisterHdrListener();
584         mRegisteredDisplayToken = displayToken;
585         if (mRegisteredDisplayToken != null) {
586             mHdrListener.register(mRegisteredDisplayToken);
587         }
588     }
589 
unregisterHdrListener()590     private void unregisterHdrListener() {
591         if (mRegisteredDisplayToken != null) {
592             mHdrListener.unregister(mRegisteredDisplayToken);
593             mIsHdrLayerPresent = false;
594         }
595     }
596 
597     @VisibleForTesting
598     class HdrListener extends SurfaceControlHdrLayerInfoListener {
599         @Override
onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio)600         public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers,
601                 int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio) {
602             mHandler.post(() -> {
603                 mIsHdrLayerPresent = numberOfHdrLayers > 0
604                         && (float) (maxW * maxH) >= ((float) (mWidth * mHeight)
605                                    * mHbmData.minimumHdrPercentOfScreen);
606 
607                 final float candidateDesiredHdrSdrRatio =
608                         mIsHdrLayerPresent && mHdrBrightnessCfg != null
609                                 ? maxDesiredHdrSdrRatio
610                                 : DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
611 
612                 if (candidateDesiredHdrSdrRatio >= 1.0f) {
613                     mMaxDesiredHdrSdrRatio = candidateDesiredHdrSdrRatio;
614                 } else {
615                     Slog.w(TAG, "Ignoring invalid desired HDR/SDR Ratio: "
616                             + candidateDesiredHdrSdrRatio);
617                     mMaxDesiredHdrSdrRatio = DEFAULT_MAX_DESIRED_HDR_SDR_RATIO;
618                 }
619 
620                 // Calling the brightness update so that we can recalculate
621                 // brightness with HDR in mind.
622                 onBrightnessChanged(mBrightness, mUnthrottledBrightness, mThrottlingReason);
623             });
624         }
625     }
626 
627     private final class SettingsObserver extends ContentObserver {
628         private final Uri mLowPowerModeSetting = Settings.Global.getUriFor(
629                 Settings.Global.LOW_POWER_MODE);
630         private boolean mStarted;
631 
SettingsObserver(Handler handler)632         SettingsObserver(Handler handler) {
633             super(handler);
634         }
635 
636         @Override
onChange(boolean selfChange, Uri uri)637         public void onChange(boolean selfChange, Uri uri) {
638             updateLowPower();
639         }
640 
startObserving()641         void startObserving() {
642             if (!mStarted) {
643                 mContext.getContentResolver().registerContentObserver(mLowPowerModeSetting,
644                         false /*notifyForDescendants*/, this, UserHandle.USER_ALL);
645                 mStarted = true;
646                 updateLowPower();
647             }
648         }
649 
stopObserving()650         void stopObserving() {
651             mIsBlockedByLowPowerMode = false;
652             if (mStarted) {
653                 mContext.getContentResolver().unregisterContentObserver(this);
654                 mStarted = false;
655             }
656         }
657 
updateLowPower()658         private void updateLowPower() {
659             final boolean isLowPowerMode = isLowPowerMode();
660             if (isLowPowerMode == mIsBlockedByLowPowerMode) {
661                 return;
662             }
663             if (DEBUG) {
664                 Slog.d(TAG, "Settings.Global.LOW_POWER_MODE enabled: " + isLowPowerMode);
665             }
666             mIsBlockedByLowPowerMode = isLowPowerMode;
667             // this recalculates HbmMode and runs mHbmChangeCallback if the mode has changed
668             updateHbmMode();
669         }
670 
isLowPowerMode()671         private boolean isLowPowerMode() {
672             return Settings.Global.getInt(
673                     mContext.getContentResolver(), Settings.Global.LOW_POWER_MODE, 0) != 0;
674         }
675     }
676 
677     public static class Injector {
getClock()678         public Clock getClock() {
679             return SystemClock::uptimeMillis;
680         }
681 
reportHbmStateChange(int display, int state, int reason)682         public void reportHbmStateChange(int display, int state, int reason) {
683             FrameworkStatsLog.write(
684                     FrameworkStatsLog.DISPLAY_HBM_STATE_CHANGED, display, state, reason);
685         }
686     }
687 }
688