1 /* 2 * Copyright (C) 2022 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; 18 19 import android.annotation.NonNull; 20 import android.content.Context; 21 import android.hardware.display.BrightnessInfo; 22 import android.hardware.display.DisplayManager; 23 import android.os.Handler; 24 import android.os.HandlerExecutor; 25 import android.os.IThermalEventListener; 26 import android.os.IThermalService; 27 import android.os.PowerManager; 28 import android.os.RemoteException; 29 import android.os.ServiceManager; 30 import android.os.Temperature; 31 import android.provider.DeviceConfig; 32 import android.provider.DeviceConfigInterface; 33 import android.util.Slog; 34 35 import com.android.internal.annotations.VisibleForTesting; 36 import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData; 37 import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel; 38 39 import java.io.PrintWriter; 40 import java.util.ArrayList; 41 import java.util.HashMap; 42 import java.util.List; 43 import java.util.concurrent.Executor; 44 45 /** 46 * This class monitors various conditions, such as skin temperature throttling status, and limits 47 * the allowed brightness range accordingly. 48 */ 49 class BrightnessThrottler { 50 private static final String TAG = "BrightnessThrottler"; 51 private static final boolean DEBUG = false; 52 53 private static final int THROTTLING_INVALID = -1; 54 55 private final Injector mInjector; 56 private final Handler mHandler; 57 // We need a separate handler for unit testing. These two handlers are the same throughout the 58 // non-test code. 59 private final Handler mDeviceConfigHandler; 60 private final Runnable mThrottlingChangeCallback; 61 private final SkinThermalStatusObserver mSkinThermalStatusObserver; 62 private final DeviceConfigListener mDeviceConfigListener; 63 private final DeviceConfigInterface mDeviceConfig; 64 65 private int mThrottlingStatus; 66 private BrightnessThrottlingData mThrottlingData; 67 private BrightnessThrottlingData mDdcThrottlingData; 68 private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX; 69 private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason = 70 BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; 71 private String mUniqueDisplayId; 72 73 // The most recent string that has been set from DeviceConfig 74 private String mBrightnessThrottlingDataString; 75 76 // This is a collection of brightness throttling data that has been written as overrides from 77 // the DeviceConfig. This will always take priority over the display device config data. 78 private HashMap<String, BrightnessThrottlingData> mBrightnessThrottlingDataOverride = 79 new HashMap<>(1); 80 BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback, String uniqueDisplayId)81 BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData, 82 Runnable throttlingChangeCallback, String uniqueDisplayId) { 83 this(new Injector(), handler, handler, throttlingData, throttlingChangeCallback, 84 uniqueDisplayId); 85 } 86 87 @VisibleForTesting BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler, BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback, String uniqueDisplayId)88 BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler, 89 BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback, 90 String uniqueDisplayId) { 91 mInjector = injector; 92 93 mHandler = handler; 94 mDeviceConfigHandler = deviceConfigHandler; 95 mThrottlingData = throttlingData; 96 mDdcThrottlingData = throttlingData; 97 mThrottlingChangeCallback = throttlingChangeCallback; 98 mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler); 99 100 mUniqueDisplayId = uniqueDisplayId; 101 mDeviceConfig = injector.getDeviceConfig(); 102 mDeviceConfigListener = new DeviceConfigListener(); 103 104 resetThrottlingData(mThrottlingData, mUniqueDisplayId); 105 } 106 deviceSupportsThrottling()107 boolean deviceSupportsThrottling() { 108 return mThrottlingData != null; 109 } 110 getBrightnessCap()111 float getBrightnessCap() { 112 return mBrightnessCap; 113 } 114 getBrightnessMaxReason()115 int getBrightnessMaxReason() { 116 return mBrightnessMaxReason; 117 } 118 isThrottled()119 boolean isThrottled() { 120 return mBrightnessMaxReason != BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; 121 } 122 stop()123 void stop() { 124 mSkinThermalStatusObserver.stopObserving(); 125 mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener); 126 // We're asked to stop throttling, so reset brightness restrictions. 127 mBrightnessCap = PowerManager.BRIGHTNESS_MAX; 128 mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; 129 130 // We set throttling status to an invalid value here so that we act on the first throttling 131 // value received from the thermal service after registration, even if that throttling value 132 // is THROTTLING_NONE. 133 mThrottlingStatus = THROTTLING_INVALID; 134 } 135 resetThrottlingData()136 private void resetThrottlingData() { 137 resetThrottlingData(mDdcThrottlingData, mUniqueDisplayId); 138 } 139 resetThrottlingData(BrightnessThrottlingData throttlingData, String displayId)140 void resetThrottlingData(BrightnessThrottlingData throttlingData, String displayId) { 141 stop(); 142 143 mUniqueDisplayId = displayId; 144 mDdcThrottlingData = throttlingData; 145 mDeviceConfigListener.startListening(); 146 reloadBrightnessThrottlingDataOverride(); 147 mThrottlingData = mBrightnessThrottlingDataOverride.getOrDefault(mUniqueDisplayId, 148 throttlingData); 149 150 if (deviceSupportsThrottling()) { 151 mSkinThermalStatusObserver.startObserving(); 152 } 153 } 154 verifyAndConstrainBrightnessCap(float brightness)155 private float verifyAndConstrainBrightnessCap(float brightness) { 156 if (brightness < PowerManager.BRIGHTNESS_MIN) { 157 Slog.e(TAG, "brightness " + brightness + " is lower than the minimum possible " 158 + "brightness " + PowerManager.BRIGHTNESS_MIN); 159 brightness = PowerManager.BRIGHTNESS_MIN; 160 } 161 162 if (brightness > PowerManager.BRIGHTNESS_MAX) { 163 Slog.e(TAG, "brightness " + brightness + " is higher than the maximum possible " 164 + "brightness " + PowerManager.BRIGHTNESS_MAX); 165 brightness = PowerManager.BRIGHTNESS_MAX; 166 } 167 168 return brightness; 169 } 170 thermalStatusChanged(@emperature.ThrottlingStatus int newStatus)171 private void thermalStatusChanged(@Temperature.ThrottlingStatus int newStatus) { 172 if (mThrottlingStatus != newStatus) { 173 mThrottlingStatus = newStatus; 174 updateThrottling(); 175 } 176 } 177 updateThrottling()178 private void updateThrottling() { 179 if (!deviceSupportsThrottling()) { 180 return; 181 } 182 183 float brightnessCap = PowerManager.BRIGHTNESS_MAX; 184 int brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; 185 186 if (mThrottlingStatus != THROTTLING_INVALID) { 187 // Throttling levels are sorted by increasing severity 188 for (ThrottlingLevel level : mThrottlingData.throttlingLevels) { 189 if (level.thermalStatus <= mThrottlingStatus) { 190 brightnessCap = level.brightness; 191 brightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; 192 } else { 193 // Throttling levels that are greater than the current status are irrelevant 194 break; 195 } 196 } 197 } 198 199 if (mBrightnessCap != brightnessCap || mBrightnessMaxReason != brightnessMaxReason) { 200 mBrightnessCap = verifyAndConstrainBrightnessCap(brightnessCap); 201 mBrightnessMaxReason = brightnessMaxReason; 202 203 if (DEBUG) { 204 Slog.d(TAG, "State changed: mBrightnessCap = " + mBrightnessCap 205 + ", mBrightnessMaxReason = " 206 + BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason)); 207 } 208 209 if (mThrottlingChangeCallback != null) { 210 mThrottlingChangeCallback.run(); 211 } 212 } 213 } 214 dump(PrintWriter pw)215 void dump(PrintWriter pw) { 216 mHandler.runWithScissors(() -> dumpLocal(pw), 1000); 217 } 218 dumpLocal(PrintWriter pw)219 private void dumpLocal(PrintWriter pw) { 220 pw.println("BrightnessThrottler:"); 221 pw.println(" mThrottlingData=" + mThrottlingData); 222 pw.println(" mDdcThrottlingData=" + mDdcThrottlingData); 223 pw.println(" mUniqueDisplayId=" + mUniqueDisplayId); 224 pw.println(" mThrottlingStatus=" + mThrottlingStatus); 225 pw.println(" mBrightnessCap=" + mBrightnessCap); 226 pw.println(" mBrightnessMaxReason=" + 227 BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason)); 228 pw.println(" mBrightnessThrottlingDataOverride=" + mBrightnessThrottlingDataOverride); 229 pw.println(" mBrightnessThrottlingDataString=" + mBrightnessThrottlingDataString); 230 231 mSkinThermalStatusObserver.dump(pw); 232 } 233 getBrightnessThrottlingDataString()234 private String getBrightnessThrottlingDataString() { 235 return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, 236 DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, 237 /* defaultValue= */ null); 238 } 239 parseAndSaveData(@onNull String strArray, @NonNull HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData)240 private boolean parseAndSaveData(@NonNull String strArray, 241 @NonNull HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData) { 242 boolean validConfig = true; 243 String[] items = strArray.split(","); 244 int i = 0; 245 246 try { 247 String uniqueDisplayId = items[i++]; 248 249 // number of throttling points 250 int noOfThrottlingPoints = Integer.parseInt(items[i++]); 251 List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints); 252 253 // throttling level and point 254 for (int j = 0; j < noOfThrottlingPoints; j++) { 255 String severity = items[i++]; 256 int status = parseThermalStatus(severity); 257 258 float brightnessPoint = parseBrightness(items[i++]); 259 260 throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint)); 261 } 262 BrightnessThrottlingData toSave = 263 DisplayDeviceConfig.BrightnessThrottlingData.create(throttlingLevels); 264 tempBrightnessThrottlingData.put(uniqueDisplayId, toSave); 265 } catch (NumberFormatException | IndexOutOfBoundsException 266 | UnknownThermalStatusException e) { 267 validConfig = false; 268 Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e); 269 } 270 271 if (i != items.length) { 272 validConfig = false; 273 } 274 275 return validConfig; 276 } 277 reloadBrightnessThrottlingDataOverride()278 public void reloadBrightnessThrottlingDataOverride() { 279 HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData = 280 new HashMap<>(1); 281 mBrightnessThrottlingDataString = getBrightnessThrottlingDataString(); 282 boolean validConfig = true; 283 mBrightnessThrottlingDataOverride.clear(); 284 if (mBrightnessThrottlingDataString != null) { 285 String[] throttlingDataSplits = mBrightnessThrottlingDataString.split(";"); 286 for (String s : throttlingDataSplits) { 287 if (!parseAndSaveData(s, tempBrightnessThrottlingData)) { 288 validConfig = false; 289 break; 290 } 291 } 292 293 if (validConfig) { 294 mBrightnessThrottlingDataOverride.putAll(tempBrightnessThrottlingData); 295 tempBrightnessThrottlingData.clear(); 296 } 297 298 } else { 299 Slog.w(TAG, "DeviceConfig BrightnessThrottlingData is null"); 300 } 301 } 302 303 /** 304 * Listens to config data change and updates the brightness throttling data using 305 * DisplayManager#KEY_BRIGHTNESS_THROTTLING_DATA. 306 * The format should be a string similar to: "local:4619827677550801152,2,moderate,0.5,severe, 307 * 0.379518072;local:4619827677550801151,1,moderate,0.75" 308 * In this order: 309 * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>] 310 * Where the latter part is repeated for each throttling level, and the entirety is repeated 311 * for each display, separated by a semicolon. 312 */ 313 public class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener { 314 public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler); 315 startListening()316 public void startListening() { 317 mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, 318 mExecutor, this); 319 } 320 321 @Override onPropertiesChanged(DeviceConfig.Properties properties)322 public void onPropertiesChanged(DeviceConfig.Properties properties) { 323 reloadBrightnessThrottlingDataOverride(); 324 resetThrottlingData(); 325 } 326 } 327 parseBrightness(String intVal)328 private float parseBrightness(String intVal) throws NumberFormatException { 329 float value = Float.parseFloat(intVal); 330 if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) { 331 throw new NumberFormatException("Brightness constraint value out of bounds."); 332 } 333 return value; 334 } 335 parseThermalStatus(@onNull String value)336 @PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value) 337 throws UnknownThermalStatusException { 338 switch (value) { 339 case "none": 340 return PowerManager.THERMAL_STATUS_NONE; 341 case "light": 342 return PowerManager.THERMAL_STATUS_LIGHT; 343 case "moderate": 344 return PowerManager.THERMAL_STATUS_MODERATE; 345 case "severe": 346 return PowerManager.THERMAL_STATUS_SEVERE; 347 case "critical": 348 return PowerManager.THERMAL_STATUS_CRITICAL; 349 case "emergency": 350 return PowerManager.THERMAL_STATUS_EMERGENCY; 351 case "shutdown": 352 return PowerManager.THERMAL_STATUS_SHUTDOWN; 353 default: 354 throw new UnknownThermalStatusException("Invalid Thermal Status: " + value); 355 } 356 } 357 358 private static class UnknownThermalStatusException extends Exception { UnknownThermalStatusException(String message)359 UnknownThermalStatusException(String message) { 360 super(message); 361 } 362 } 363 364 private final class SkinThermalStatusObserver extends IThermalEventListener.Stub { 365 private final Injector mInjector; 366 private final Handler mHandler; 367 368 private IThermalService mThermalService; 369 private boolean mStarted; 370 SkinThermalStatusObserver(Injector injector, Handler handler)371 SkinThermalStatusObserver(Injector injector, Handler handler) { 372 mInjector = injector; 373 mHandler = handler; 374 } 375 376 @Override notifyThrottling(Temperature temp)377 public void notifyThrottling(Temperature temp) { 378 if (DEBUG) { 379 Slog.d(TAG, "New thermal throttling status = " + temp.getStatus()); 380 } 381 mHandler.post(() -> { 382 final @Temperature.ThrottlingStatus int status = temp.getStatus(); 383 thermalStatusChanged(status); 384 }); 385 } 386 startObserving()387 void startObserving() { 388 if (mStarted) { 389 if (DEBUG) { 390 Slog.d(TAG, "Thermal status observer already started"); 391 } 392 return; 393 } 394 mThermalService = mInjector.getThermalService(); 395 if (mThermalService == null) { 396 Slog.e(TAG, "Could not observe thermal status. Service not available"); 397 return; 398 } 399 try { 400 // We get a callback immediately upon registering so there's no need to query 401 // for the current value. 402 mThermalService.registerThermalEventListenerWithType(this, Temperature.TYPE_SKIN); 403 mStarted = true; 404 } catch (RemoteException e) { 405 Slog.e(TAG, "Failed to register thermal status listener", e); 406 } 407 } 408 stopObserving()409 void stopObserving() { 410 if (!mStarted) { 411 if (DEBUG) { 412 Slog.d(TAG, "Stop skipped because thermal status observer not started"); 413 } 414 return; 415 } 416 try { 417 mThermalService.unregisterThermalEventListener(this); 418 mStarted = false; 419 } catch (RemoteException e) { 420 Slog.e(TAG, "Failed to unregister thermal status listener", e); 421 } 422 mThermalService = null; 423 } 424 dump(PrintWriter writer)425 void dump(PrintWriter writer) { 426 writer.println(" SkinThermalStatusObserver:"); 427 writer.println(" mStarted: " + mStarted); 428 if (mThermalService != null) { 429 writer.println(" ThermalService available"); 430 } else { 431 writer.println(" ThermalService not available"); 432 } 433 } 434 } 435 436 public static class Injector { getThermalService()437 public IThermalService getThermalService() { 438 return IThermalService.Stub.asInterface( 439 ServiceManager.getService(Context.THERMAL_SERVICE)); 440 } 441 442 @NonNull getDeviceConfig()443 public DeviceConfigInterface getDeviceConfig() { 444 return DeviceConfigInterface.REAL; 445 } 446 } 447 } 448