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 static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID; 20 import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.Context; 25 import android.hardware.display.BrightnessInfo; 26 import android.hardware.display.DisplayManagerInternal; 27 import android.os.Handler; 28 import android.os.IThermalEventListener; 29 import android.os.IThermalService; 30 import android.os.PowerManager; 31 import android.os.RemoteException; 32 import android.os.ServiceManager; 33 import android.os.Temperature; 34 import android.provider.DeviceConfigInterface; 35 import android.util.Slog; 36 37 import com.android.internal.annotations.VisibleForTesting; 38 import com.android.server.display.DisplayBrightnessState; 39 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; 40 import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; 41 import com.android.server.display.brightness.BrightnessReason; 42 import com.android.server.display.config.SensorData; 43 import com.android.server.display.feature.DeviceConfigParameterProvider; 44 import com.android.server.display.utils.DeviceConfigParsingUtils; 45 import com.android.server.display.utils.SensorUtils; 46 47 import java.io.PrintWriter; 48 import java.util.List; 49 import java.util.Map; 50 import java.util.Objects; 51 import java.util.function.BiFunction; 52 import java.util.function.Function; 53 54 55 class BrightnessThermalModifier implements BrightnessStateModifier, 56 BrightnessClamperController.DisplayDeviceDataListener, 57 BrightnessClamperController.StatefulModifier, 58 BrightnessClamperController.DeviceConfigListener { 59 60 private static final String TAG = "BrightnessThermalClamper"; 61 @NonNull 62 private final ThermalStatusObserver mThermalStatusObserver; 63 @NonNull 64 private final DeviceConfigParameterProvider mConfigParameterProvider; 65 // data from DeviceConfig, for all displays, for all dataSets 66 // mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData)) 67 @NonNull 68 protected final Handler mHandler; 69 @NonNull 70 protected final BrightnessClamperController.ClamperChangeListener mChangeListener; 71 72 @NonNull 73 private Map<String, Map<String, ThermalBrightnessThrottlingData>> 74 mThermalThrottlingDataOverride = Map.of(); 75 // data from DisplayDeviceConfig, for particular display+dataSet 76 @Nullable 77 private ThermalBrightnessThrottlingData mThermalThrottlingDataFromDeviceConfig = null; 78 // Active data, if mDataOverride contains data for mUniqueDisplayId, mDataId, then use it, 79 // otherwise mDataFromDeviceConfig 80 @Nullable 81 private ThermalBrightnessThrottlingData mThermalThrottlingDataActive = null; 82 @Nullable 83 private String mUniqueDisplayId = null; 84 @Nullable 85 private String mDataId = null; 86 @Temperature.ThrottlingStatus 87 private int mThrottlingStatus = Temperature.THROTTLING_NONE; 88 private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; 89 private boolean mApplied = false; 90 91 private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> { 92 try { 93 int status = DeviceConfigParsingUtils.parseThermalStatus(key); 94 float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value); 95 return new ThrottlingLevel(status, brightnessPoint); 96 } catch (IllegalArgumentException iae) { 97 return null; 98 } 99 }; 100 101 private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData> 102 mDataSetMapper = ThermalBrightnessThrottlingData::create; 103 104 BrightnessThermalModifier(Handler handler, ClamperChangeListener listener, BrightnessClamperController.DisplayDeviceData data)105 BrightnessThermalModifier(Handler handler, ClamperChangeListener listener, 106 BrightnessClamperController.DisplayDeviceData data) { 107 this(new Injector(), handler, listener, data); 108 } 109 110 @VisibleForTesting BrightnessThermalModifier(Injector injector, @NonNull Handler handler, @NonNull ClamperChangeListener listener, @NonNull BrightnessClamperController.DisplayDeviceData data)111 BrightnessThermalModifier(Injector injector, @NonNull Handler handler, 112 @NonNull ClamperChangeListener listener, 113 @NonNull BrightnessClamperController.DisplayDeviceData data) { 114 mHandler = handler; 115 mChangeListener = listener; 116 mConfigParameterProvider = injector.getDeviceConfigParameterProvider(); 117 mThermalStatusObserver = new ThermalStatusObserver(injector, handler); 118 mHandler.post(() -> { 119 setDisplayData(data); 120 loadOverrideData(); 121 }); 122 } 123 //region BrightnessStateModifier 124 @Override apply(DisplayManagerInternal.DisplayPowerRequest request, DisplayBrightnessState.Builder stateBuilder)125 public void apply(DisplayManagerInternal.DisplayPowerRequest request, 126 DisplayBrightnessState.Builder stateBuilder) { 127 if (stateBuilder.getMaxBrightness() > mBrightnessCap) { 128 stateBuilder.setMaxBrightness(mBrightnessCap); 129 stateBuilder.setBrightness(Math.min(stateBuilder.getBrightness(), mBrightnessCap)); 130 stateBuilder.setBrightnessMaxReason(BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL); 131 stateBuilder.getBrightnessReason().addModifier(BrightnessReason.MODIFIER_THROTTLED); 132 // set fast change only when modifier is activated. 133 // this will allow auto brightness to apply slow change even when modifier is active 134 if (!mApplied) { 135 stateBuilder.setIsSlowChange(false); 136 } 137 mApplied = true; 138 } else { 139 mApplied = false; 140 } 141 } 142 143 @Override stop()144 public void stop() { 145 mThermalStatusObserver.stopObserving(); 146 } 147 148 @Override dump(PrintWriter writer)149 public void dump(PrintWriter writer) { 150 writer.println("BrightnessThermalClamper:"); 151 writer.println(" mThrottlingStatus: " + mThrottlingStatus); 152 writer.println(" mUniqueDisplayId: " + mUniqueDisplayId); 153 writer.println(" mDataId: " + mDataId); 154 writer.println(" mDataOverride: " + mThermalThrottlingDataOverride); 155 writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig); 156 writer.println(" mDataActive: " + mThermalThrottlingDataActive); 157 writer.println(" mBrightnessCap:" + mBrightnessCap); 158 writer.println(" mApplied:" + mApplied); 159 mThermalStatusObserver.dump(writer); 160 } 161 162 @Override shouldListenToLightSensor()163 public boolean shouldListenToLightSensor() { 164 return false; 165 } 166 167 @Override setAmbientLux(float lux)168 public void setAmbientLux(float lux) { 169 // noop 170 } 171 //endregion 172 173 //region DisplayDeviceDataListener 174 @Override onDisplayChanged(BrightnessClamperController.DisplayDeviceData data)175 public void onDisplayChanged(BrightnessClamperController.DisplayDeviceData data) { 176 mHandler.post(() -> { 177 setDisplayData(data); 178 recalculateActiveData(); 179 }); 180 } 181 //endregion 182 183 //region StatefulModifier 184 @Override applyStateChange( BrightnessClamperController.ModifiersAggregatedState aggregatedState)185 public void applyStateChange( 186 BrightnessClamperController.ModifiersAggregatedState aggregatedState) { 187 if (aggregatedState.mMaxBrightness > mBrightnessCap) { 188 aggregatedState.mMaxBrightness = mBrightnessCap; 189 aggregatedState.mMaxBrightnessReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; 190 } 191 } 192 //endregion 193 194 //region DeviceConfigListener 195 @Override onDeviceConfigChanged()196 public void onDeviceConfigChanged() { 197 mHandler.post(() -> { 198 loadOverrideData(); 199 recalculateActiveData(); 200 }); 201 } 202 //endregion 203 recalculateActiveData()204 private void recalculateActiveData() { 205 if (mUniqueDisplayId == null || mDataId == null) { 206 return; 207 } 208 mThermalThrottlingDataActive = mThermalThrottlingDataOverride 209 .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId, 210 mThermalThrottlingDataFromDeviceConfig); 211 212 recalculateBrightnessCap(); 213 } 214 loadOverrideData()215 private void loadOverrideData() { 216 String throttlingDataOverride = mConfigParameterProvider.getBrightnessThrottlingData(); 217 mThermalThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap( 218 throttlingDataOverride, mDataPointMapper, mDataSetMapper); 219 } 220 setDisplayData(@onNull ThermalData data)221 private void setDisplayData(@NonNull ThermalData data) { 222 mUniqueDisplayId = data.getUniqueDisplayId(); 223 mDataId = data.getThermalThrottlingDataId(); 224 mThermalThrottlingDataFromDeviceConfig = data.getThermalBrightnessThrottlingData(); 225 if (mThermalThrottlingDataFromDeviceConfig == null && !DEFAULT_ID.equals(mDataId)) { 226 Slog.wtf(TAG, 227 "Thermal throttling data is missing for thermalThrottlingDataId=" + mDataId); 228 } 229 mThermalStatusObserver.registerSensor(data.getTempSensor()); 230 } 231 recalculateBrightnessCap()232 private void recalculateBrightnessCap() { 233 float brightnessCap = PowerManager.BRIGHTNESS_MAX; 234 if (mThermalThrottlingDataActive != null) { 235 // Throttling levels are sorted by increasing severity 236 for (ThrottlingLevel level : mThermalThrottlingDataActive.throttlingLevels) { 237 if (level.thermalStatus <= mThrottlingStatus) { 238 brightnessCap = level.brightness; 239 } else { 240 // Throttling levels that are greater than the current status are irrelevant 241 break; 242 } 243 } 244 } 245 246 if (brightnessCap != mBrightnessCap) { 247 mBrightnessCap = brightnessCap; 248 mChangeListener.onChanged(); 249 } 250 } 251 thermalStatusChanged(@emperature.ThrottlingStatus int status)252 private void thermalStatusChanged(@Temperature.ThrottlingStatus int status) { 253 if (mThrottlingStatus != status) { 254 mThrottlingStatus = status; 255 recalculateBrightnessCap(); 256 } 257 } 258 259 private final class ThermalStatusObserver extends IThermalEventListener.Stub { 260 private final Injector mInjector; 261 private final Handler mHandler; 262 private IThermalService mThermalService; 263 private boolean mStarted; 264 private SensorData mObserverTempSensor; 265 ThermalStatusObserver(Injector injector, Handler handler)266 ThermalStatusObserver(Injector injector, Handler handler) { 267 mInjector = injector; 268 mHandler = handler; 269 mStarted = false; 270 } 271 registerSensor(SensorData tempSensor)272 void registerSensor(SensorData tempSensor) { 273 if (!mStarted || mObserverTempSensor == null) { 274 mObserverTempSensor = tempSensor; 275 registerThermalListener(); 276 return; 277 } 278 279 String curType = mObserverTempSensor.type; 280 mObserverTempSensor = tempSensor; 281 if (Objects.equals(curType, tempSensor.type)) { 282 Slog.d(TAG, "Thermal status observer already started"); 283 return; 284 } 285 stopObserving(); 286 registerThermalListener(); 287 } 288 registerThermalListener()289 void registerThermalListener() { 290 mThermalService = mInjector.getThermalService(); 291 if (mThermalService == null) { 292 Slog.e(TAG, "Could not observe thermal status. Service not available"); 293 return; 294 } 295 int temperatureType = SensorUtils.getSensorTemperatureType(mObserverTempSensor); 296 try { 297 // We get a callback immediately upon registering so there's no need to query 298 // for the current value. 299 mThermalService.registerThermalEventListenerWithType(this, temperatureType); 300 mStarted = true; 301 } catch (RemoteException e) { 302 Slog.e(TAG, "Failed to register thermal status listener", e); 303 } 304 } 305 306 @Override notifyThrottling(Temperature temp)307 public void notifyThrottling(Temperature temp) { 308 Slog.d(TAG, "New thermal throttling status = " + temp.getStatus()); 309 if (mObserverTempSensor.name != null 310 && !mObserverTempSensor.name.equals(temp.getName())) { 311 Slog.i(TAG, "Skipping thermal throttling notification as monitored sensor: " 312 + mObserverTempSensor.name 313 + " != notified sensor: " 314 + temp.getName()); 315 return; 316 } 317 @Temperature.ThrottlingStatus int status = temp.getStatus(); 318 mHandler.post(() -> thermalStatusChanged(status)); 319 } 320 stopObserving()321 void stopObserving() { 322 if (!mStarted) { 323 return; 324 } 325 try { 326 mThermalService.unregisterThermalEventListener(this); 327 mStarted = false; 328 } catch (RemoteException e) { 329 Slog.e(TAG, "Failed to unregister thermal status listener", e); 330 } 331 mThermalService = null; 332 } 333 dump(PrintWriter writer)334 void dump(PrintWriter writer) { 335 writer.println(" ThermalStatusObserver:"); 336 writer.println(" mStarted: " + mStarted); 337 writer.println(" mObserverTempSensor: " + mObserverTempSensor); 338 if (mThermalService != null) { 339 writer.println(" ThermalService available"); 340 } else { 341 writer.println(" ThermalService not available"); 342 } 343 } 344 } 345 346 interface ThermalData { 347 @NonNull getUniqueDisplayId()348 String getUniqueDisplayId(); 349 350 @Nullable getThermalThrottlingDataId()351 String getThermalThrottlingDataId(); 352 353 @Nullable getThermalBrightnessThrottlingData()354 ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData(); 355 356 @NonNull getTempSensor()357 SensorData getTempSensor(); 358 } 359 360 @VisibleForTesting 361 static class Injector { getThermalService()362 IThermalService getThermalService() { 363 return IThermalService.Stub.asInterface( 364 ServiceManager.getService(Context.THERMAL_SERVICE)); 365 } 366 getDeviceConfigParameterProvider()367 DeviceConfigParameterProvider getDeviceConfigParameterProvider() { 368 return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); 369 } 370 } 371 } 372