• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.brightness.clamper;
18 
19 import android.annotation.Nullable;
20 import android.annotation.SuppressLint;
21 import android.os.Handler;
22 import android.os.IBinder;
23 import android.os.PowerManager;
24 import android.view.SurfaceControlHdrLayerInfoListener;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.server.display.AutomaticBrightnessController;
28 import com.android.server.display.config.HdrBrightnessData;
29 
30 import java.io.PrintWriter;
31 import java.util.Map;
32 
33 public class HdrClamper {
34 
35     private final BrightnessClamperController.ClamperChangeListener mClamperChangeListener;
36 
37     private final Handler mHandler;
38 
39     private final Runnable mDebouncer;
40 
41     private final HdrLayerInfoListener mHdrListener;
42 
43     @Nullable
44     private HdrBrightnessData mHdrBrightnessData = null;
45 
46     @Nullable
47     private IBinder mRegisteredDisplayToken = null;
48 
49     private float mAmbientLux = Float.MAX_VALUE;
50 
51     private boolean mHdrVisible = false;
52 
53     private float mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
54     private float mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX;
55 
56     // brightness change speed, in units per seconds,
57     private float mTransitionRate = -1f;
58     private float mDesiredTransitionRate = -1f;
59 
60     private boolean mAutoBrightnessEnabled = false;
61 
62     /**
63      * Indicates that maxBrightness is changed, and we should use slow transition
64      */
65     private boolean mUseSlowTransition = false;
66 
HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener, Handler handler)67     public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
68             Handler handler) {
69         this(clamperChangeListener, handler, new Injector());
70     }
71 
72     @VisibleForTesting
HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener, Handler handler, Injector injector)73     public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener,
74             Handler handler, Injector injector) {
75         mClamperChangeListener = clamperChangeListener;
76         mHandler = handler;
77         mDebouncer = () -> {
78             mTransitionRate = mDesiredTransitionRate;
79             mMaxBrightness = mDesiredMaxBrightness;
80             mUseSlowTransition = true;
81             mClamperChangeListener.onChanged();
82         };
83         mHdrListener = injector.getHdrListener((visible) -> {
84             mHdrVisible = visible;
85             recalculateBrightnessCap(mHdrBrightnessData, mAmbientLux, mHdrVisible);
86         }, handler);
87     }
88 
89     /**
90      * Applies clamping
91      * Called in same looper: mHandler.getLooper()
92      */
clamp(float brightness)93     public float clamp(float brightness) {
94         return Math.min(brightness, mMaxBrightness);
95     }
96 
97     @VisibleForTesting
getMaxBrightness()98     public float getMaxBrightness() {
99         return mMaxBrightness;
100     }
101 
102     // Called in same looper: mHandler.getLooper()
getTransitionRate()103     public float getTransitionRate() {
104         float expectedTransitionRate =  mUseSlowTransition ? mTransitionRate : -1;
105         mUseSlowTransition = false;
106         return  expectedTransitionRate;
107     }
108 
109     /**
110      * Updates brightness cap in response to ambient lux change.
111      * Called by ABC in same looper: mHandler.getLooper()
112      */
onAmbientLuxChange(float ambientLux)113     public void onAmbientLuxChange(float ambientLux) {
114         mAmbientLux = ambientLux;
115         recalculateBrightnessCap(mHdrBrightnessData, ambientLux, mHdrVisible);
116     }
117 
118     /**
119      * Updates brightness cap config.
120      * Called in same looper: mHandler.getLooper()
121      */
122     @SuppressLint("AndroidFrameworkRequiresPermission")
resetHdrConfig(HdrBrightnessData data, int width, int height, float minimumHdrPercentOfScreen, IBinder displayToken)123     public void resetHdrConfig(HdrBrightnessData data, int width, int height,
124             float minimumHdrPercentOfScreen, IBinder displayToken) {
125         mHdrBrightnessData = data;
126         mHdrListener.mHdrMinPixels = (float) (width * height) * minimumHdrPercentOfScreen;
127         if (displayToken != mRegisteredDisplayToken) { // token changed, resubscribe
128             if (mRegisteredDisplayToken != null) { // previous token not null, unsubscribe
129                 mHdrListener.unregister(mRegisteredDisplayToken);
130                 mHdrVisible = false;
131                 mRegisteredDisplayToken = null;
132             }
133             // new token not null and hdr min % of the screen is set, subscribe.
134             // e.g. for virtual display, HBM data will be missing and HdrListener
135             // should not be registered
136             if (displayToken != null && mHdrListener.mHdrMinPixels >= 0 && hasBrightnessLimits())  {
137                 mHdrListener.register(displayToken);
138                 mRegisteredDisplayToken = displayToken;
139             }
140         }
141         recalculateBrightnessCap(data, mAmbientLux, mHdrVisible);
142     }
143 
144     /**
145      * Sets state of auto brightness to temporary disabling this Clamper if auto brightness is off.
146      * The issue is tracked here: b/322445088
147      */
setAutoBrightnessState(int state)148     public void setAutoBrightnessState(int state) {
149         boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED;
150         if (isEnabled != mAutoBrightnessEnabled) {
151             mAutoBrightnessEnabled = isEnabled;
152             recalculateBrightnessCap(mHdrBrightnessData, mAmbientLux, mHdrVisible);
153         }
154     }
155 
156     /** Clean up all resources */
157     @SuppressLint("AndroidFrameworkRequiresPermission")
stop()158     public void stop() {
159         if (mRegisteredDisplayToken != null) {
160             mHdrListener.unregister(mRegisteredDisplayToken);
161         }
162     }
163 
164     /**
165      * Dumps the state of HdrClamper.
166      */
dump(PrintWriter pw)167     public void dump(PrintWriter pw) {
168         pw.println("HdrClamper:");
169         pw.println("  mMaxBrightness=" + mMaxBrightness);
170         pw.println("  mDesiredMaxBrightness=" + mDesiredMaxBrightness);
171         pw.println("  mTransitionRate=" + mTransitionRate);
172         pw.println("  mDesiredTransitionRate=" + mDesiredTransitionRate);
173         pw.println("  mHdrVisible=" + mHdrVisible);
174         pw.println("  mHdrListener.mHdrMinPixels=" + mHdrListener.mHdrMinPixels);
175         pw.println("  mHdrBrightnessData=" + (mHdrBrightnessData == null ? "null"
176                 : mHdrBrightnessData.toString()));
177         pw.println("  mHdrListener registered=" + (mRegisteredDisplayToken != null));
178         pw.println("  mAmbientLux=" + mAmbientLux);
179         pw.println("  mAutoBrightnessEnabled=" + mAutoBrightnessEnabled);
180     }
181 
hasBrightnessLimits()182     private boolean hasBrightnessLimits() {
183         return mHdrBrightnessData != null && !mHdrBrightnessData.maxBrightnessLimits.isEmpty();
184     }
185 
reset()186     private void reset() {
187         if (mMaxBrightness == PowerManager.BRIGHTNESS_MAX
188                 && mDesiredMaxBrightness == PowerManager.BRIGHTNESS_MAX && mTransitionRate == -1f
189                 && mDesiredTransitionRate == -1f) { // already done reset, do nothing
190             return;
191         }
192         mHandler.removeCallbacks(mDebouncer);
193         mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
194         mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX;
195         mDesiredTransitionRate = -1f;
196         mTransitionRate = -1f;
197         mUseSlowTransition = false;
198         mClamperChangeListener.onChanged();
199     }
200 
recalculateBrightnessCap(HdrBrightnessData data, float ambientLux, boolean hdrVisible)201     private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux,
202             boolean hdrVisible) {
203         // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
204         // Temporary disabling this Clamper if auto brightness is off, to avoid capping
205         // brightness based on stale ambient lux. The issue is tracked here: b/322445088
206         if (data == null || !hdrVisible || !mAutoBrightnessEnabled) {
207             reset();
208             return;
209         }
210 
211         float expectedMaxBrightness = findBrightnessLimit(data, ambientLux);
212         if (mMaxBrightness == expectedMaxBrightness) {
213             mDesiredMaxBrightness = mMaxBrightness;
214             mDesiredTransitionRate = -1f;
215             mTransitionRate = -1f;
216             mHandler.removeCallbacks(mDebouncer);
217         } else if (mDesiredMaxBrightness != expectedMaxBrightness) {
218             mDesiredMaxBrightness = expectedMaxBrightness;
219             long debounceTime;
220             if (mDesiredMaxBrightness > mMaxBrightness) {
221                 debounceTime = mHdrBrightnessData.brightnessIncreaseDebounceMillis;
222                 mDesiredTransitionRate = mHdrBrightnessData.screenBrightnessRampIncrease;
223             } else {
224                 debounceTime = mHdrBrightnessData.brightnessDecreaseDebounceMillis;
225                 mDesiredTransitionRate = mHdrBrightnessData.screenBrightnessRampDecrease;
226             }
227 
228             mHandler.removeCallbacks(mDebouncer);
229             mHandler.postDelayed(mDebouncer, debounceTime);
230         }
231         // do nothing if expectedMaxBrightness == mDesiredMaxBrightness
232         // && expectedMaxBrightness != mMaxBrightness
233     }
234 
findBrightnessLimit(HdrBrightnessData data, float ambientLux)235     private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) {
236         float foundAmbientBoundary = Float.MAX_VALUE;
237         float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
238         for (Map.Entry<Float, Float> brightnessPoint :
239                 data.maxBrightnessLimits.entrySet()) {
240             float ambientBoundary = brightnessPoint.getKey();
241             // find ambient lux upper boundary closest to current ambient lux
242             if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) {
243                 foundMaxBrightness = brightnessPoint.getValue();
244                 foundAmbientBoundary = ambientBoundary;
245             }
246         }
247         return foundMaxBrightness;
248     }
249 
250     @FunctionalInterface
251     interface HdrListener {
onHdrVisible(boolean visible)252         void onHdrVisible(boolean visible);
253     }
254 
255     static class HdrLayerInfoListener extends SurfaceControlHdrLayerInfoListener {
256         private final HdrListener mHdrListener;
257 
258         private final Handler mHandler;
259 
260         private float mHdrMinPixels = Float.MAX_VALUE;
261 
HdrLayerInfoListener(HdrListener hdrListener, Handler handler)262         HdrLayerInfoListener(HdrListener hdrListener, Handler handler) {
263             mHdrListener = hdrListener;
264             mHandler = handler;
265         }
266 
267         @Override
onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio)268         public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW,
269                 int maxH, int flags, float maxDesiredHdrSdrRatio) {
270             mHandler.post(() ->
271                     mHdrListener.onHdrVisible(
272                             numberOfHdrLayers > 0 && (float) (maxW * maxH) >= mHdrMinPixels));
273         }
274     }
275 
276     static class Injector {
getHdrListener(HdrListener hdrListener, Handler handler)277         HdrLayerInfoListener getHdrListener(HdrListener hdrListener, Handler handler) {
278             return new HdrLayerInfoListener(hdrListener, handler);
279         }
280     }
281 }
282