• 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) {
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 
reset()182     private void reset() {
183         if (mMaxBrightness == PowerManager.BRIGHTNESS_MAX
184                 && mDesiredMaxBrightness == PowerManager.BRIGHTNESS_MAX && mTransitionRate == -1f
185                 && mDesiredTransitionRate == -1f) { // already done reset, do nothing
186             return;
187         }
188         mHandler.removeCallbacks(mDebouncer);
189         mMaxBrightness = PowerManager.BRIGHTNESS_MAX;
190         mDesiredMaxBrightness = PowerManager.BRIGHTNESS_MAX;
191         mDesiredTransitionRate = -1f;
192         mTransitionRate = -1f;
193         mUseSlowTransition = false;
194         mClamperChangeListener.onChanged();
195     }
196 
recalculateBrightnessCap(HdrBrightnessData data, float ambientLux, boolean hdrVisible)197     private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux,
198             boolean hdrVisible) {
199         // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled.
200         // Temporary disabling this Clamper if auto brightness is off, to avoid capping
201         // brightness based on stale ambient lux. The issue is tracked here: b/322445088
202         if (data == null || !hdrVisible || !mAutoBrightnessEnabled) {
203             reset();
204             return;
205         }
206 
207         float expectedMaxBrightness = findBrightnessLimit(data, ambientLux);
208         if (mMaxBrightness == expectedMaxBrightness) {
209             mDesiredMaxBrightness = mMaxBrightness;
210             mDesiredTransitionRate = -1f;
211             mTransitionRate = -1f;
212             mHandler.removeCallbacks(mDebouncer);
213         } else if (mDesiredMaxBrightness != expectedMaxBrightness) {
214             mDesiredMaxBrightness = expectedMaxBrightness;
215             long debounceTime;
216             if (mDesiredMaxBrightness > mMaxBrightness) {
217                 debounceTime = mHdrBrightnessData.mBrightnessIncreaseDebounceMillis;
218                 mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampIncrease;
219             } else {
220                 debounceTime = mHdrBrightnessData.mBrightnessDecreaseDebounceMillis;
221                 mDesiredTransitionRate = mHdrBrightnessData.mScreenBrightnessRampDecrease;
222             }
223 
224             mHandler.removeCallbacks(mDebouncer);
225             mHandler.postDelayed(mDebouncer, debounceTime);
226         }
227         // do nothing if expectedMaxBrightness == mDesiredMaxBrightness
228         // && expectedMaxBrightness != mMaxBrightness
229     }
230 
findBrightnessLimit(HdrBrightnessData data, float ambientLux)231     private float findBrightnessLimit(HdrBrightnessData data, float ambientLux) {
232         float foundAmbientBoundary = Float.MAX_VALUE;
233         float foundMaxBrightness = PowerManager.BRIGHTNESS_MAX;
234         for (Map.Entry<Float, Float> brightnessPoint :
235                 data.mMaxBrightnessLimits.entrySet()) {
236             float ambientBoundary = brightnessPoint.getKey();
237             // find ambient lux upper boundary closest to current ambient lux
238             if (ambientBoundary > ambientLux && ambientBoundary < foundAmbientBoundary) {
239                 foundMaxBrightness = brightnessPoint.getValue();
240                 foundAmbientBoundary = ambientBoundary;
241             }
242         }
243         return foundMaxBrightness;
244     }
245 
246     @FunctionalInterface
247     interface HdrListener {
onHdrVisible(boolean visible)248         void onHdrVisible(boolean visible);
249     }
250 
251     static class HdrLayerInfoListener extends SurfaceControlHdrLayerInfoListener {
252         private final HdrListener mHdrListener;
253 
254         private final Handler mHandler;
255 
256         private float mHdrMinPixels = Float.MAX_VALUE;
257 
HdrLayerInfoListener(HdrListener hdrListener, Handler handler)258         HdrLayerInfoListener(HdrListener hdrListener, Handler handler) {
259             mHdrListener = hdrListener;
260             mHandler = handler;
261         }
262 
263         @Override
onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW, int maxH, int flags, float maxDesiredHdrSdrRatio)264         public void onHdrInfoChanged(IBinder displayToken, int numberOfHdrLayers, int maxW,
265                 int maxH, int flags, float maxDesiredHdrSdrRatio) {
266             mHandler.post(() ->
267                     mHdrListener.onHdrVisible(
268                             numberOfHdrLayers > 0 && (float) (maxW * maxH) >= mHdrMinPixels));
269         }
270     }
271 
272     static class Injector {
getHdrListener(HdrListener hdrListener, Handler handler)273         HdrLayerInfoListener getHdrListener(HdrListener hdrListener, Handler handler) {
274             return new HdrLayerInfoListener(hdrListener, handler);
275         }
276     }
277 }
278