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