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