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