Merge "Introducing clampers to DPC" into udc-qpr-dev am: 83db2408ad
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23784984 Change-Id: Id300eb02deefa14fcd702fbaf39f44835cd1432c Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -38,17 +38,24 @@ import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
|
||||
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
|
||||
import com.android.server.display.feature.DeviceConfigParameterProvider;
|
||||
import com.android.server.display.utils.DeviceConfigParsingUtils;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* This class monitors various conditions, such as skin temperature throttling status, and limits
|
||||
* the allowed brightness range accordingly.
|
||||
*
|
||||
* @deprecated will be replaced by
|
||||
* {@link com.android.server.display.brightness.clamper.BrightnessThermalClamper}
|
||||
*/
|
||||
@Deprecated
|
||||
class BrightnessThrottler {
|
||||
private static final String TAG = "BrightnessThrottler";
|
||||
private static final boolean DEBUG = false;
|
||||
@@ -93,8 +100,21 @@ class BrightnessThrottler {
|
||||
// time the underlying display device changes.
|
||||
// This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData.
|
||||
// HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >>
|
||||
private final HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
|
||||
mThermalBrightnessThrottlingDataOverride = new HashMap<>(1);
|
||||
private final Map<String, Map<String, ThermalBrightnessThrottlingData>>
|
||||
mThermalBrightnessThrottlingDataOverride = new HashMap<>();
|
||||
|
||||
private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
|
||||
try {
|
||||
int status = DeviceConfigParsingUtils.parseThermalStatus(key);
|
||||
float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
|
||||
return new ThrottlingLevel(status, brightnessPoint);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
|
||||
mDataSetMapper = ThermalBrightnessThrottlingData::create;
|
||||
|
||||
BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
|
||||
String throttlingDataId,
|
||||
@@ -257,79 +277,15 @@ class BrightnessThrottler {
|
||||
// 456,2,moderate,0.9,critical,0.7,id_2
|
||||
// displayId, number, <state, val> * number
|
||||
// displayId, <number, <state, val> * number>, throttlingId
|
||||
private boolean parseAndAddData(@NonNull String strArray,
|
||||
@NonNull HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>>
|
||||
displayIdToThrottlingIdToBtd) {
|
||||
boolean validConfig = true;
|
||||
String[] items = strArray.split(",");
|
||||
int i = 0;
|
||||
|
||||
try {
|
||||
String uniqueDisplayId = items[i++];
|
||||
|
||||
// number of throttling points
|
||||
int noOfThrottlingPoints = Integer.parseInt(items[i++]);
|
||||
List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints);
|
||||
|
||||
// throttling level and point
|
||||
for (int j = 0; j < noOfThrottlingPoints; j++) {
|
||||
String severity = items[i++];
|
||||
int status = parseThermalStatus(severity);
|
||||
float brightnessPoint = parseBrightness(items[i++]);
|
||||
throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint));
|
||||
}
|
||||
|
||||
String throttlingDataId = (i < items.length) ? items[i++] : DEFAULT_ID;
|
||||
ThermalBrightnessThrottlingData throttlingLevelsData =
|
||||
DisplayDeviceConfig.ThermalBrightnessThrottlingData.create(throttlingLevels);
|
||||
|
||||
// Add throttlingLevelsData to inner map where necessary.
|
||||
HashMap<String, ThermalBrightnessThrottlingData> throttlingMapForDisplay =
|
||||
displayIdToThrottlingIdToBtd.get(uniqueDisplayId);
|
||||
if (throttlingMapForDisplay == null) {
|
||||
throttlingMapForDisplay = new HashMap<>();
|
||||
throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
|
||||
displayIdToThrottlingIdToBtd.put(uniqueDisplayId, throttlingMapForDisplay);
|
||||
} else if (throttlingMapForDisplay.containsKey(throttlingDataId)) {
|
||||
Slog.e(TAG, "Throttling data for display " + uniqueDisplayId
|
||||
+ "contains duplicate throttling ids: '" + throttlingDataId + "'");
|
||||
return false;
|
||||
} else {
|
||||
throttlingMapForDisplay.put(throttlingDataId, throttlingLevelsData);
|
||||
}
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException
|
||||
| UnknownThermalStatusException e) {
|
||||
Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e);
|
||||
validConfig = false;
|
||||
}
|
||||
|
||||
if (i != items.length) {
|
||||
validConfig = false;
|
||||
}
|
||||
return validConfig;
|
||||
}
|
||||
|
||||
private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
|
||||
HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
|
||||
new HashMap<>(1);
|
||||
mThermalBrightnessThrottlingDataString =
|
||||
mConfigParameterProvider.getBrightnessThrottlingData();
|
||||
boolean validConfig = true;
|
||||
mThermalBrightnessThrottlingDataOverride.clear();
|
||||
if (mThermalBrightnessThrottlingDataString != null) {
|
||||
String[] throttlingDataSplits = mThermalBrightnessThrottlingDataString.split(";");
|
||||
for (String s : throttlingDataSplits) {
|
||||
if (!parseAndAddData(s, tempThrottlingData)) {
|
||||
validConfig = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (validConfig) {
|
||||
mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
|
||||
tempThrottlingData.clear();
|
||||
}
|
||||
|
||||
Map<String, Map<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
|
||||
DeviceConfigParsingUtils.parseDeviceConfigMap(
|
||||
mThermalBrightnessThrottlingDataString, mDataPointMapper, mDataSetMapper);
|
||||
mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
|
||||
} else {
|
||||
Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null");
|
||||
}
|
||||
@@ -395,42 +351,6 @@ class BrightnessThrottler {
|
||||
}
|
||||
}
|
||||
|
||||
private float parseBrightness(String intVal) throws NumberFormatException {
|
||||
float value = Float.parseFloat(intVal);
|
||||
if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
|
||||
throw new NumberFormatException("Brightness constraint value out of bounds.");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value)
|
||||
throws UnknownThermalStatusException {
|
||||
switch (value) {
|
||||
case "none":
|
||||
return PowerManager.THERMAL_STATUS_NONE;
|
||||
case "light":
|
||||
return PowerManager.THERMAL_STATUS_LIGHT;
|
||||
case "moderate":
|
||||
return PowerManager.THERMAL_STATUS_MODERATE;
|
||||
case "severe":
|
||||
return PowerManager.THERMAL_STATUS_SEVERE;
|
||||
case "critical":
|
||||
return PowerManager.THERMAL_STATUS_CRITICAL;
|
||||
case "emergency":
|
||||
return PowerManager.THERMAL_STATUS_EMERGENCY;
|
||||
case "shutdown":
|
||||
return PowerManager.THERMAL_STATUS_SHUTDOWN;
|
||||
default:
|
||||
throw new UnknownThermalStatusException("Invalid Thermal Status: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
private static class UnknownThermalStatusException extends Exception {
|
||||
UnknownThermalStatusException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
|
||||
private final Injector mInjector;
|
||||
private final Handler mHandler;
|
||||
|
||||
@@ -458,7 +458,7 @@ public class DisplayDeviceConfig {
|
||||
|
||||
public static final String QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC = "canSetBrightnessViaHwc";
|
||||
|
||||
static final String DEFAULT_ID = "default";
|
||||
public static final String DEFAULT_ID = "default";
|
||||
|
||||
private static final float BRIGHTNESS_DEFAULT = 0.5f;
|
||||
private static final String ETC_DIR = "etc";
|
||||
@@ -3127,11 +3127,15 @@ public class DisplayDeviceConfig {
|
||||
public static class ThermalBrightnessThrottlingData {
|
||||
public List<ThrottlingLevel> throttlingLevels;
|
||||
|
||||
static class ThrottlingLevel {
|
||||
/**
|
||||
* thermal status to brightness cap holder
|
||||
*/
|
||||
public static class ThrottlingLevel {
|
||||
public @PowerManager.ThermalStatus int thermalStatus;
|
||||
public float brightness;
|
||||
|
||||
ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) {
|
||||
public ThrottlingLevel(
|
||||
@PowerManager.ThermalStatus int thermalStatus, float brightness) {
|
||||
this.thermalStatus = thermalStatus;
|
||||
this.brightness = brightness;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import android.os.Trace;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.FloatProperty;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Log;
|
||||
import android.util.MathUtils;
|
||||
import android.util.MutableFloat;
|
||||
@@ -64,7 +65,6 @@ import com.android.internal.display.BrightnessSynchronizer;
|
||||
import com.android.internal.logging.MetricsLogger;
|
||||
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
|
||||
import com.android.internal.util.FrameworkStatsLog;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.internal.util.RingBuffer;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.am.BatteryStatsService;
|
||||
@@ -73,6 +73,7 @@ import com.android.server.display.brightness.BrightnessEvent;
|
||||
import com.android.server.display.brightness.BrightnessReason;
|
||||
import com.android.server.display.brightness.BrightnessUtils;
|
||||
import com.android.server.display.brightness.DisplayBrightnessController;
|
||||
import com.android.server.display.brightness.clamper.BrightnessClamperController;
|
||||
import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy;
|
||||
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
|
||||
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
|
||||
@@ -380,6 +381,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
|
||||
|
||||
private final BrightnessThrottler mBrightnessThrottler;
|
||||
|
||||
private final BrightnessClamperController mBrightnessClamperController;
|
||||
|
||||
private final Runnable mOnBrightnessChangeRunnable;
|
||||
|
||||
private final BrightnessEvent mLastBrightnessEvent;
|
||||
@@ -554,6 +557,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
|
||||
mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
|
||||
brightnessSetting, () -> postBrightnessChangeRunnable(),
|
||||
new HandlerExecutor(mHandler));
|
||||
|
||||
mBrightnessClamperController = new BrightnessClamperController(mHandler,
|
||||
modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
|
||||
mUniqueDisplayId,
|
||||
mThermalBrightnessThrottlingDataId,
|
||||
mDisplayDeviceConfig
|
||||
));
|
||||
// Seed the cached brightness
|
||||
saveBrightnessInfo(getScreenBrightnessSetting());
|
||||
|
||||
@@ -782,6 +792,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
|
||||
final String thermalBrightnessThrottlingDataId =
|
||||
mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
|
||||
|
||||
mBrightnessClamperController.onDisplayChanged(
|
||||
new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
|
||||
mThermalBrightnessThrottlingDataId, config));
|
||||
|
||||
mHandler.postAtTime(() -> {
|
||||
boolean changed = false;
|
||||
if (mDisplayDevice != device) {
|
||||
@@ -1187,6 +1201,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
|
||||
mDisplayPowerProximityStateController.cleanup();
|
||||
mBrightnessRangeController.stop();
|
||||
mBrightnessThrottler.stop();
|
||||
mBrightnessClamperController.stop();
|
||||
mHandler.removeCallbacksAndMessages(null);
|
||||
|
||||
// Release any outstanding wakelocks we're still holding because of pending messages.
|
||||
@@ -1519,6 +1534,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
|
||||
// allowed range.
|
||||
float animateValue = clampScreenBrightness(brightnessState);
|
||||
|
||||
animateValue = mBrightnessClamperController.clamp(animateValue);
|
||||
|
||||
// If there are any HDR layers on the screen, we have a special brightness value that we
|
||||
// use instead. We still preserve the calculated brightness for Standard Dynamic Range
|
||||
// (SDR) layers, but the main brightness value will be the one for HDR.
|
||||
@@ -2411,6 +2428,11 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
|
||||
if (mDisplayStateController != null) {
|
||||
mDisplayStateController.dumpsys(pw);
|
||||
}
|
||||
|
||||
pw.println();
|
||||
if (mBrightnessClamperController != null) {
|
||||
mBrightnessClamperController.dump(ipw);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.display.brightness.clamper;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
abstract class BrightnessClamper<T> {
|
||||
|
||||
protected float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
|
||||
protected boolean mIsActive = false;
|
||||
|
||||
float getBrightnessCap() {
|
||||
return mBrightnessCap;
|
||||
}
|
||||
|
||||
boolean isActive() {
|
||||
return mIsActive;
|
||||
}
|
||||
|
||||
void dump(PrintWriter writer) {
|
||||
writer.println("BrightnessClamper:" + getType());
|
||||
writer.println(" mBrightnessCap: " + mBrightnessCap);
|
||||
writer.println(" mIsActive: " + mIsActive);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
abstract Type getType();
|
||||
|
||||
abstract void onDeviceConfigChanged();
|
||||
|
||||
abstract void onDisplayChanged(T displayData);
|
||||
|
||||
abstract void stop();
|
||||
|
||||
enum Type {
|
||||
THERMAL
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.display.brightness.clamper;
|
||||
|
||||
import static com.android.server.display.brightness.clamper.BrightnessClamper.Type;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerExecutor;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.DeviceConfig;
|
||||
import android.provider.DeviceConfigInterface;
|
||||
import android.util.IndentingPrintWriter;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.display.DisplayDeviceConfig;
|
||||
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
|
||||
import com.android.server.display.feature.DeviceConfigParameterProvider;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Clampers controller, all in DisplayControllerHandler
|
||||
*/
|
||||
public class BrightnessClamperController {
|
||||
|
||||
private static final boolean ENABLED = false;
|
||||
|
||||
private final DeviceConfigParameterProvider mDeviceConfigParameterProvider;
|
||||
private final Handler mHandler;
|
||||
private final ClamperChangeListener mClamperChangeListenerExternal;
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers = new ArrayList<>();
|
||||
private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
|
||||
properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged);
|
||||
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
|
||||
@Nullable
|
||||
private Type mClamperType = null;
|
||||
|
||||
public BrightnessClamperController(Handler handler,
|
||||
ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
|
||||
this(new Injector(), handler, clamperChangeListener, data);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
BrightnessClamperController(Injector injector, Handler handler,
|
||||
ClamperChangeListener clamperChangeListener, DisplayDeviceData data) {
|
||||
mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider();
|
||||
mHandler = handler;
|
||||
mClamperChangeListenerExternal = clamperChangeListener;
|
||||
mExecutor = new HandlerExecutor(handler);
|
||||
|
||||
Runnable clamperChangeRunnableInternal = this::recalculateBrightnessCap;
|
||||
|
||||
ClamperChangeListener clamperChangeListenerInternal = () -> {
|
||||
if (!mHandler.hasCallbacks(clamperChangeRunnableInternal)) {
|
||||
mHandler.post(clamperChangeRunnableInternal);
|
||||
}
|
||||
};
|
||||
|
||||
if (ENABLED) {
|
||||
mClampers.add(
|
||||
new BrightnessThermalClamper(handler, clamperChangeListenerInternal, data));
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called when display changed. Forwards the call to individual clampers
|
||||
*/
|
||||
public void onDisplayChanged(DisplayDeviceData data) {
|
||||
mClampers.forEach(clamper -> clamper.onDisplayChanged(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies clamping
|
||||
* Called in DisplayControllerHandler
|
||||
*/
|
||||
public float clamp(float value) {
|
||||
return Math.min(value, mBrightnessCap);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to dump ClampersController state.
|
||||
*/
|
||||
public void dump(PrintWriter writer) {
|
||||
writer.println("BrightnessClampersController:");
|
||||
writer.println(" mBrightnessCap: " + mBrightnessCap);
|
||||
writer.println(" mClamperType: " + mClamperType);
|
||||
IndentingPrintWriter ipw = new IndentingPrintWriter(writer, " ");
|
||||
mClampers.forEach(clamper -> clamper.dump(ipw));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be called when the ClamperController is no longer in use.
|
||||
* Called in DisplayControllerHandler
|
||||
*/
|
||||
public void stop() {
|
||||
mDeviceConfigParameterProvider.removeOnPropertiesChangedListener(
|
||||
mOnPropertiesChangedListener);
|
||||
mClampers.forEach(BrightnessClamper::stop);
|
||||
}
|
||||
|
||||
|
||||
// Called in DisplayControllerHandler
|
||||
private void recalculateBrightnessCap() {
|
||||
float brightnessCap = PowerManager.BRIGHTNESS_MAX;
|
||||
Type clamperType = null;
|
||||
|
||||
BrightnessClamper<?> minClamper = mClampers.stream()
|
||||
.filter(BrightnessClamper::isActive)
|
||||
.min((clamper1, clamper2) -> Float.compare(clamper1.getBrightnessCap(),
|
||||
clamper2.getBrightnessCap())).orElse(null);
|
||||
|
||||
if (minClamper != null) {
|
||||
brightnessCap = minClamper.getBrightnessCap();
|
||||
clamperType = minClamper.getType();
|
||||
}
|
||||
|
||||
if (mBrightnessCap != brightnessCap || mClamperType != clamperType) {
|
||||
mBrightnessCap = brightnessCap;
|
||||
mClamperType = clamperType;
|
||||
mClamperChangeListenerExternal.onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void start() {
|
||||
mDeviceConfigParameterProvider.addOnPropertiesChangedListener(
|
||||
mExecutor, mOnPropertiesChangedListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clampers change listener
|
||||
*/
|
||||
public interface ClamperChangeListener {
|
||||
/**
|
||||
* Notifies that clamper state changed
|
||||
*/
|
||||
void onChanged();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class Injector {
|
||||
DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
|
||||
return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Data for clampers
|
||||
*/
|
||||
public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData {
|
||||
@NonNull
|
||||
private final String mUniqueDisplayId;
|
||||
@NonNull
|
||||
private final String mThermalThrottlingDataId;
|
||||
|
||||
private final DisplayDeviceConfig mDisplayDeviceConfig;
|
||||
|
||||
public DisplayDeviceData(@NonNull String uniqueDisplayId,
|
||||
@NonNull String thermalThrottlingDataId,
|
||||
@NonNull DisplayDeviceConfig displayDeviceConfig) {
|
||||
mUniqueDisplayId = uniqueDisplayId;
|
||||
mThermalThrottlingDataId = thermalThrottlingDataId;
|
||||
mDisplayDeviceConfig = displayDeviceConfig;
|
||||
}
|
||||
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getUniqueDisplayId() {
|
||||
return mUniqueDisplayId;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getThermalThrottlingDataId() {
|
||||
return mThermalThrottlingDataId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
|
||||
return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get(
|
||||
mThermalThrottlingDataId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.display.brightness.clamper;
|
||||
|
||||
import static com.android.server.display.DisplayDeviceConfig.DEFAULT_ID;
|
||||
import static com.android.server.display.brightness.clamper.BrightnessClamperController.ClamperChangeListener;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.IThermalEventListener;
|
||||
import android.os.IThermalService;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.Temperature;
|
||||
import android.provider.DeviceConfigInterface;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
|
||||
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
|
||||
import com.android.server.display.feature.DeviceConfigParameterProvider;
|
||||
import com.android.server.display.utils.DeviceConfigParsingUtils;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
|
||||
class BrightnessThermalClamper extends
|
||||
BrightnessClamper<BrightnessThermalClamper.ThermalData> {
|
||||
|
||||
private static final String TAG = "BrightnessThermalClamper";
|
||||
|
||||
@Nullable
|
||||
private final IThermalService mThermalService;
|
||||
@NonNull
|
||||
private final DeviceConfigParameterProvider mConfigParameterProvider;
|
||||
@NonNull
|
||||
private final Handler mHandler;
|
||||
@NonNull
|
||||
private final ClamperChangeListener mChangelistener;
|
||||
// data from DeviceConfig, for all displays, for all dataSets
|
||||
// mapOf(uniqueDisplayId to mapOf(dataSetId to ThermalBrightnessThrottlingData))
|
||||
@NonNull
|
||||
private Map<String, Map<String, ThermalBrightnessThrottlingData>>
|
||||
mThermalThrottlingDataOverride = Map.of();
|
||||
// data from DisplayDeviceConfig, for particular display+dataSet
|
||||
@Nullable
|
||||
private ThermalBrightnessThrottlingData mThermalThrottlingDataFromDeviceConfig = null;
|
||||
// Active data, if mDataOverride contains data for mUniqueDisplayId, mDataId, then use it,
|
||||
// otherwise mDataFromDeviceConfig
|
||||
@Nullable
|
||||
private ThermalBrightnessThrottlingData mThermalThrottlingDataActive = null;
|
||||
private boolean mStarted = false;
|
||||
@Nullable
|
||||
private String mUniqueDisplayId = null;
|
||||
@Nullable
|
||||
private String mDataId = null;
|
||||
@Temperature.ThrottlingStatus
|
||||
private int mThrottlingStatus = Temperature.THROTTLING_NONE;
|
||||
|
||||
private final IThermalEventListener mThermalEventListener = new IThermalEventListener.Stub() {
|
||||
@Override
|
||||
public void notifyThrottling(Temperature temperature) {
|
||||
@Temperature.ThrottlingStatus int status = temperature.getStatus();
|
||||
mHandler.post(() -> thermalStatusChanged(status));
|
||||
}
|
||||
};
|
||||
|
||||
private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> {
|
||||
try {
|
||||
int status = DeviceConfigParsingUtils.parseThermalStatus(key);
|
||||
float brightnessPoint = DeviceConfigParsingUtils.parseBrightness(value);
|
||||
return new ThrottlingLevel(status, brightnessPoint);
|
||||
} catch (IllegalArgumentException iae) {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
private final Function<List<ThrottlingLevel>, ThermalBrightnessThrottlingData>
|
||||
mDataSetMapper = ThermalBrightnessThrottlingData::create;
|
||||
|
||||
|
||||
BrightnessThermalClamper(Handler handler, ClamperChangeListener listener,
|
||||
ThermalData thermalData) {
|
||||
this(new Injector(), handler, listener, thermalData);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
BrightnessThermalClamper(Injector injector, Handler handler,
|
||||
ClamperChangeListener listener, ThermalData thermalData) {
|
||||
mThermalService = injector.getThermalService();
|
||||
mConfigParameterProvider = injector.getDeviceConfigParameterProvider();
|
||||
mHandler = handler;
|
||||
mChangelistener = listener;
|
||||
mHandler.post(() -> {
|
||||
setDisplayData(thermalData);
|
||||
loadOverrideData();
|
||||
start();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
Type getType() {
|
||||
return Type.THERMAL;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDeviceConfigChanged() {
|
||||
mHandler.post(() -> {
|
||||
loadOverrideData();
|
||||
recalculateActiveData();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDisplayChanged(ThermalData data) {
|
||||
mHandler.post(() -> {
|
||||
setDisplayData(data);
|
||||
recalculateActiveData();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
void stop() {
|
||||
if (!mStarted) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mThermalService.unregisterThermalEventListener(mThermalEventListener);
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to unregister thermal status listener", e);
|
||||
}
|
||||
mStarted = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
void dump(PrintWriter writer) {
|
||||
writer.println("BrightnessThermalClamper:");
|
||||
writer.println(" mStarted: " + mStarted);
|
||||
if (mThermalService != null) {
|
||||
writer.println(" ThermalService available");
|
||||
} else {
|
||||
writer.println(" ThermalService not available");
|
||||
}
|
||||
writer.println(" mThrottlingStatus: " + mThrottlingStatus);
|
||||
writer.println(" mUniqueDisplayId: " + mUniqueDisplayId);
|
||||
writer.println(" mDataId: " + mDataId);
|
||||
writer.println(" mDataOverride: " + mThermalThrottlingDataOverride);
|
||||
writer.println(" mDataFromDeviceConfig: " + mThermalThrottlingDataFromDeviceConfig);
|
||||
writer.println(" mDataActive: " + mThermalThrottlingDataActive);
|
||||
super.dump(writer);
|
||||
}
|
||||
|
||||
private void recalculateActiveData() {
|
||||
if (mUniqueDisplayId == null || mDataId == null) {
|
||||
return;
|
||||
}
|
||||
mThermalThrottlingDataActive = mThermalThrottlingDataOverride
|
||||
.getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId,
|
||||
mThermalThrottlingDataFromDeviceConfig);
|
||||
|
||||
recalculateBrightnessCap();
|
||||
}
|
||||
|
||||
private void loadOverrideData() {
|
||||
String throttlingDataOverride = mConfigParameterProvider.getBrightnessThrottlingData();
|
||||
mThermalThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap(
|
||||
throttlingDataOverride, mDataPointMapper, mDataSetMapper);
|
||||
}
|
||||
|
||||
private void setDisplayData(@NonNull ThermalData data) {
|
||||
mUniqueDisplayId = data.getUniqueDisplayId();
|
||||
mDataId = data.getThermalThrottlingDataId();
|
||||
mThermalThrottlingDataFromDeviceConfig = data.getThermalBrightnessThrottlingData();
|
||||
if (mThermalThrottlingDataFromDeviceConfig == null && !DEFAULT_ID.equals(mDataId)) {
|
||||
Slog.wtf(TAG,
|
||||
"Thermal throttling data is missing for thermalThrottlingDataId=" + mDataId);
|
||||
}
|
||||
}
|
||||
|
||||
private void recalculateBrightnessCap() {
|
||||
float brightnessCap = PowerManager.BRIGHTNESS_MAX;
|
||||
boolean isActive = false;
|
||||
|
||||
if (mThermalThrottlingDataActive != null) {
|
||||
// Throttling levels are sorted by increasing severity
|
||||
for (ThrottlingLevel level : mThermalThrottlingDataActive.throttlingLevels) {
|
||||
if (level.thermalStatus <= mThrottlingStatus) {
|
||||
brightnessCap = level.brightness;
|
||||
isActive = true;
|
||||
} else {
|
||||
// Throttling levels that are greater than the current status are irrelevant
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (brightnessCap != mBrightnessCap || mIsActive != isActive) {
|
||||
mBrightnessCap = brightnessCap;
|
||||
mIsActive = isActive;
|
||||
mChangelistener.onChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void thermalStatusChanged(@Temperature.ThrottlingStatus int status) {
|
||||
if (mThrottlingStatus != status) {
|
||||
mThrottlingStatus = status;
|
||||
recalculateBrightnessCap();
|
||||
}
|
||||
}
|
||||
|
||||
private void start() {
|
||||
if (mThermalService == null) {
|
||||
Slog.e(TAG, "Could not observe thermal status. Service not available");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// We get a callback immediately upon registering so there's no need to query
|
||||
// for the current value.
|
||||
mThermalService.registerThermalEventListenerWithType(mThermalEventListener,
|
||||
Temperature.TYPE_SKIN);
|
||||
mStarted = true;
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Failed to register thermal status listener", e);
|
||||
}
|
||||
}
|
||||
|
||||
interface ThermalData {
|
||||
@NonNull
|
||||
String getUniqueDisplayId();
|
||||
|
||||
@NonNull
|
||||
String getThermalThrottlingDataId();
|
||||
|
||||
@Nullable
|
||||
ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
static class Injector {
|
||||
IThermalService getThermalService() {
|
||||
return IThermalService.Stub.asInterface(
|
||||
ServiceManager.getService(Context.THERMAL_SERVICE));
|
||||
}
|
||||
|
||||
DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
|
||||
return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.display.utils;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.PowerManager;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.display.DisplayDeviceConfig;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Provides utility methods for DeviceConfig string parsing
|
||||
*/
|
||||
public class DeviceConfigParsingUtils {
|
||||
private static final String TAG = "DeviceConfigParsingUtils";
|
||||
|
||||
/**
|
||||
* Parses map from device config
|
||||
* Data format:
|
||||
* (displayId:String,numberOfPoints:Int,(state:T,value:Float){numberOfPoints},
|
||||
* dataSetId:String)?;)+
|
||||
* result : mapOf(displayId to mapOf(dataSetId to V))
|
||||
*/
|
||||
@NonNull
|
||||
public static <T, V> Map<String, Map<String, V>> parseDeviceConfigMap(
|
||||
@Nullable String data,
|
||||
@NonNull BiFunction<String, String, T> dataPointMapper,
|
||||
@NonNull Function<List<T>, V> dataSetMapper) {
|
||||
if (data == null) {
|
||||
return Map.of();
|
||||
}
|
||||
Map<String, Map<String, V>> result = new HashMap<>();
|
||||
String[] dataSets = data.split(";"); // by displayId + dataSetId
|
||||
for (String dataSet : dataSets) {
|
||||
String[] items = dataSet.split(",");
|
||||
int noOfItems = items.length;
|
||||
// Validate number of items, at least: displayId,1,key1,value1
|
||||
if (noOfItems < 4) {
|
||||
Slog.e(TAG, "Invalid dataSet(not enough items):" + dataSet, new Throwable());
|
||||
return Map.of();
|
||||
}
|
||||
int i = 0;
|
||||
String uniqueDisplayId = items[i++];
|
||||
|
||||
String numberOfPointsString = items[i++];
|
||||
int numberOfPoints;
|
||||
try {
|
||||
numberOfPoints = Integer.parseInt(numberOfPointsString);
|
||||
} catch (NumberFormatException nfe) {
|
||||
Slog.e(TAG, "Invalid dataSet(invalid number of points):" + dataSet, nfe);
|
||||
return Map.of();
|
||||
}
|
||||
// Validate number of itmes based on numberOfPoints:
|
||||
// displayId,numberOfPoints,(key,value) x numberOfPoints,dataSetId(optional)
|
||||
int expectedMinItems = 2 + numberOfPoints * 2;
|
||||
if (noOfItems < expectedMinItems || noOfItems > expectedMinItems + 1) {
|
||||
Slog.e(TAG, "Invalid dataSet(wrong number of points):" + dataSet, new Throwable());
|
||||
return Map.of();
|
||||
}
|
||||
// Construct data points
|
||||
List<T> dataPoints = new ArrayList<>();
|
||||
for (int j = 0; j < numberOfPoints; j++) {
|
||||
String key = items[i++];
|
||||
String value = items[i++];
|
||||
T dataPoint = dataPointMapper.apply(key, value);
|
||||
if (dataPoint == null) {
|
||||
Slog.e(TAG,
|
||||
"Invalid dataPoint ,key=" + key + ",value=" + value + ",dataSet="
|
||||
+ dataSet, new Throwable());
|
||||
return Map.of();
|
||||
}
|
||||
dataPoints.add(dataPoint);
|
||||
}
|
||||
// Construct dataSet
|
||||
V dataSetMapped = dataSetMapper.apply(dataPoints);
|
||||
if (dataSetMapped == null) {
|
||||
Slog.e(TAG, "Invalid dataSetMapped dataPoints=" + dataPoints + ",dataSet="
|
||||
+ dataSet, new Throwable());
|
||||
return Map.of();
|
||||
}
|
||||
// Get dataSetId and dataSets map for displayId
|
||||
String dataSetId = (i < items.length) ? items[i] : DisplayDeviceConfig.DEFAULT_ID;
|
||||
Map<String, V> byDisplayId = result.computeIfAbsent(uniqueDisplayId,
|
||||
k -> new HashMap<>());
|
||||
|
||||
// Try to store dataSet in datasets for display
|
||||
if (byDisplayId.put(dataSetId, dataSetMapped) != null) {
|
||||
Slog.e(TAG, "Duplicate dataSetId=" + dataSetId + ",data=" + data, new Throwable());
|
||||
return Map.of();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses thermal string value from device config
|
||||
*/
|
||||
@PowerManager.ThermalStatus
|
||||
public static int parseThermalStatus(@NonNull String value) throws IllegalArgumentException {
|
||||
switch (value) {
|
||||
case "none":
|
||||
return PowerManager.THERMAL_STATUS_NONE;
|
||||
case "light":
|
||||
return PowerManager.THERMAL_STATUS_LIGHT;
|
||||
case "moderate":
|
||||
return PowerManager.THERMAL_STATUS_MODERATE;
|
||||
case "severe":
|
||||
return PowerManager.THERMAL_STATUS_SEVERE;
|
||||
case "critical":
|
||||
return PowerManager.THERMAL_STATUS_CRITICAL;
|
||||
case "emergency":
|
||||
return PowerManager.THERMAL_STATUS_EMERGENCY;
|
||||
case "shutdown":
|
||||
return PowerManager.THERMAL_STATUS_SHUTDOWN;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid Thermal Status: " + value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses brightness value from device config
|
||||
*/
|
||||
public static float parseBrightness(String stringVal) throws IllegalArgumentException {
|
||||
float value = Float.parseFloat(stringVal);
|
||||
if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
|
||||
throw new IllegalArgumentException("Brightness value out of bounds: " + stringVal);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.display.brightness.clamper;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.os.IThermalEventListener;
|
||||
import android.os.IThermalService;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.Temperature;
|
||||
import android.provider.DeviceConfig;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.annotations.Keep;
|
||||
import com.android.server.display.DisplayDeviceConfig;
|
||||
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
|
||||
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
|
||||
import com.android.server.display.feature.DeviceConfigParameterProvider;
|
||||
import com.android.server.testutils.FakeDeviceConfigInterface;
|
||||
import com.android.server.testutils.TestHandler;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import junitparams.JUnitParamsRunner;
|
||||
import junitparams.Parameters;
|
||||
|
||||
@RunWith(JUnitParamsRunner.class)
|
||||
public class BrightnessThermalClamperTest {
|
||||
|
||||
private static final float FLOAT_TOLERANCE = 0.001f;
|
||||
|
||||
private static final String DISPLAY_ID = "displayId";
|
||||
@Mock
|
||||
private IThermalService mMockThermalService;
|
||||
@Mock
|
||||
private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener;
|
||||
|
||||
private final FakeDeviceConfigInterface mFakeDeviceConfigInterface =
|
||||
new FakeDeviceConfigInterface();
|
||||
private final TestHandler mTestHandler = new TestHandler(null);
|
||||
private BrightnessThermalClamper mClamper;
|
||||
@Before
|
||||
public void setUp() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
mClamper = new BrightnessThermalClamper(new TestInjector(), mTestHandler,
|
||||
mMockClamperChangeListener, new TestThermalData());
|
||||
mTestHandler.flush();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTypeIsThermal() {
|
||||
assertEquals(BrightnessClamper.Type.THERMAL, mClamper.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoThrottlingData() {
|
||||
assertFalse(mClamper.isActive());
|
||||
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
@Keep
|
||||
private static Object[][] testThrottlingData() {
|
||||
// throttlingLevels, throttlingStatus, expectedActive, expectedBrightness
|
||||
return new Object[][] {
|
||||
// no throttling data
|
||||
{List.of(), Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
|
||||
// throttlingStatus < min throttling data
|
||||
{List.of(
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
|
||||
Temperature.THROTTLING_LIGHT, false, PowerManager.BRIGHTNESS_MAX},
|
||||
// throttlingStatus = min throttling data
|
||||
{List.of(
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
|
||||
Temperature.THROTTLING_MODERATE, true, 0.5f},
|
||||
// throttlingStatus between min and max throttling data
|
||||
{List.of(
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
|
||||
Temperature.THROTTLING_SEVERE, true, 0.5f},
|
||||
// throttlingStatus = max throttling data
|
||||
{List.of(
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
|
||||
Temperature.THROTTLING_CRITICAL, true, 0.1f},
|
||||
// throttlingStatus > max throttling data
|
||||
{List.of(
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_MODERATE, 0.5f),
|
||||
new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 0.1f)),
|
||||
Temperature.THROTTLING_EMERGENCY, true, 0.1f},
|
||||
};
|
||||
}
|
||||
@Test
|
||||
@Parameters(method = "testThrottlingData")
|
||||
public void testNotifyThrottlingAfterOnDisplayChange(List<ThrottlingLevel> throttlingLevels,
|
||||
@Temperature.ThrottlingStatus int throttlingStatus,
|
||||
boolean expectedActive, float expectedBrightness) throws RemoteException {
|
||||
IThermalEventListener thermalEventListener = captureThermalEventListener();
|
||||
mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
|
||||
mTestHandler.flush();
|
||||
assertFalse(mClamper.isActive());
|
||||
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
|
||||
|
||||
thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
|
||||
mTestHandler.flush();
|
||||
assertEquals(expectedActive, mClamper.isActive());
|
||||
assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters(method = "testThrottlingData")
|
||||
public void testOnDisplayChangeAfterNotifyThrottlng(List<ThrottlingLevel> throttlingLevels,
|
||||
@Temperature.ThrottlingStatus int throttlingStatus,
|
||||
boolean expectedActive, float expectedBrightness) throws RemoteException {
|
||||
IThermalEventListener thermalEventListener = captureThermalEventListener();
|
||||
thermalEventListener.notifyThrottling(createTemperature(throttlingStatus));
|
||||
mTestHandler.flush();
|
||||
assertFalse(mClamper.isActive());
|
||||
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
|
||||
|
||||
mClamper.onDisplayChanged(new TestThermalData(throttlingLevels));
|
||||
mTestHandler.flush();
|
||||
assertEquals(expectedActive, mClamper.isActive());
|
||||
assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverrideData() throws RemoteException {
|
||||
IThermalEventListener thermalEventListener = captureThermalEventListener();
|
||||
thermalEventListener.notifyThrottling(createTemperature(Temperature.THROTTLING_SEVERE));
|
||||
mTestHandler.flush();
|
||||
assertFalse(mClamper.isActive());
|
||||
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
|
||||
|
||||
mClamper.onDisplayChanged(new TestThermalData(
|
||||
List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 0.5f))));
|
||||
mTestHandler.flush();
|
||||
assertTrue(mClamper.isActive());
|
||||
assertEquals(0.5f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
|
||||
|
||||
overrideThrottlingData("displayId,1,emergency,0.4");
|
||||
mClamper.onDeviceConfigChanged();
|
||||
mTestHandler.flush();
|
||||
|
||||
assertFalse(mClamper.isActive());
|
||||
assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
|
||||
|
||||
overrideThrottlingData("displayId,1,moderate,0.4");
|
||||
mClamper.onDeviceConfigChanged();
|
||||
mTestHandler.flush();
|
||||
|
||||
assertTrue(mClamper.isActive());
|
||||
assertEquals(0.4f, mClamper.getBrightnessCap(), FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
private IThermalEventListener captureThermalEventListener() throws RemoteException {
|
||||
ArgumentCaptor<IThermalEventListener> captor = ArgumentCaptor.forClass(
|
||||
IThermalEventListener.class);
|
||||
verify(mMockThermalService).registerThermalEventListenerWithType(captor.capture(), eq(
|
||||
Temperature.TYPE_SKIN));
|
||||
return captor.getValue();
|
||||
}
|
||||
|
||||
private Temperature createTemperature(@Temperature.ThrottlingStatus int status) {
|
||||
return new Temperature(100, Temperature.TYPE_SKIN, "test_temperature", status);
|
||||
}
|
||||
|
||||
private void overrideThrottlingData(String data) {
|
||||
mFakeDeviceConfigInterface.putProperty(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
|
||||
DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA, data);
|
||||
}
|
||||
|
||||
private class TestInjector extends BrightnessThermalClamper.Injector {
|
||||
@Override
|
||||
IThermalService getThermalService() {
|
||||
return mMockThermalService;
|
||||
}
|
||||
|
||||
@Override
|
||||
DeviceConfigParameterProvider getDeviceConfigParameterProvider() {
|
||||
return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface);
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestThermalData implements BrightnessThermalClamper.ThermalData {
|
||||
|
||||
private final String mUniqueDisplayId;
|
||||
private final String mDataId;
|
||||
private final ThermalBrightnessThrottlingData mData;
|
||||
|
||||
private TestThermalData() {
|
||||
this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null);
|
||||
}
|
||||
|
||||
private TestThermalData(List<ThrottlingLevel> data) {
|
||||
this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data);
|
||||
}
|
||||
private TestThermalData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) {
|
||||
mUniqueDisplayId = uniqueDisplayId;
|
||||
mDataId = dataId;
|
||||
mData = ThermalBrightnessThrottlingData.create(data);
|
||||
}
|
||||
@NonNull
|
||||
@Override
|
||||
public String getUniqueDisplayId() {
|
||||
return mUniqueDisplayId;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getThermalThrottlingDataId() {
|
||||
return mDataId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ThermalBrightnessThrottlingData getThermalBrightnessThrottlingData() {
|
||||
return mData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.display.utils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
|
||||
import android.os.PowerManager;
|
||||
import android.util.Pair;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.internal.annotations.Keep;
|
||||
import com.android.server.display.DisplayDeviceConfig;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import junitparams.JUnitParamsRunner;
|
||||
import junitparams.Parameters;
|
||||
|
||||
@SmallTest
|
||||
@RunWith(JUnitParamsRunner.class)
|
||||
public class DeviceConfigParsingUtilsTest {
|
||||
private static final String VALID_DATA_STRING = "display1,1,key1,value1";
|
||||
private static final float FLOAT_TOLERANCE = 0.001f;
|
||||
|
||||
private final BiFunction<String, String, Pair<String, String>> mDataPointToPair = Pair::create;
|
||||
private final Function<List<Pair<String, String>>, List<Pair<String, String>>>
|
||||
mDataSetIdentity = (dataSet) -> dataSet;
|
||||
|
||||
@Keep
|
||||
private static Object[][] parseDeviceConfigMapData() {
|
||||
// dataString, expectedMap
|
||||
return new Object[][]{
|
||||
// null
|
||||
{null, Map.of()},
|
||||
// empty string
|
||||
{"", Map.of()},
|
||||
// 1 display, 1 incomplete data point
|
||||
{"display1,1,key1", Map.of()},
|
||||
// 1 display,2 data points required, only 1 present
|
||||
{"display1,2,key1,value1", Map.of()},
|
||||
// 1 display, 1 data point, dataSetId and some extra data
|
||||
{"display1,1,key1,value1,setId1,extraData", Map.of()},
|
||||
// 1 display, random string instead of number of data points
|
||||
{"display1,one,key1,value1", Map.of()},
|
||||
// 1 display, 1 data point no dataSetId
|
||||
{VALID_DATA_STRING, Map.of("display1", Map.of(DisplayDeviceConfig.DEFAULT_ID,
|
||||
List.of(Pair.create("key1", "value1"))))},
|
||||
// 1 display, 1 data point, dataSetId
|
||||
{"display1,1,key1,value1,setId1", Map.of("display1", Map.of("setId1",
|
||||
List.of(Pair.create("key1", "value1"))))},
|
||||
// 1 display, 2 data point, dataSetId
|
||||
{"display1,2,key1,value1,key2,value2,setId1", Map.of("display1", Map.of("setId1",
|
||||
List.of(Pair.create("key1", "value1"), Pair.create("key2", "value2"))))},
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters(method = "parseDeviceConfigMapData")
|
||||
public void testParseDeviceConfigMap(String dataString,
|
||||
Map<String, Map<String, List<Pair<String, String>>>> expectedMap) {
|
||||
Map<String, Map<String, List<Pair<String, String>>>> result =
|
||||
DeviceConfigParsingUtils.parseDeviceConfigMap(dataString, mDataPointToPair,
|
||||
mDataSetIdentity);
|
||||
|
||||
assertEquals(expectedMap, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataPointMapperReturnsNull() {
|
||||
Map<String, Map<String, List<Pair<String, String>>>> result =
|
||||
DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, (s1, s2) -> null,
|
||||
mDataSetIdentity);
|
||||
|
||||
assertEquals(Map.of(), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataSetMapperReturnsNull() {
|
||||
Map<String, Map<String, List<Pair<String, String>>>> result =
|
||||
DeviceConfigParsingUtils.parseDeviceConfigMap(VALID_DATA_STRING, mDataPointToPair,
|
||||
(dataSet) -> null);
|
||||
|
||||
assertEquals(Map.of(), result);
|
||||
}
|
||||
|
||||
@Keep
|
||||
private static Object[][] parseThermalStatusData() {
|
||||
// thermalStatusString, expectedThermalStatus
|
||||
return new Object[][]{
|
||||
{"none", PowerManager.THERMAL_STATUS_NONE},
|
||||
{"light", PowerManager.THERMAL_STATUS_LIGHT},
|
||||
{"moderate", PowerManager.THERMAL_STATUS_MODERATE},
|
||||
{"severe", PowerManager.THERMAL_STATUS_SEVERE},
|
||||
{"critical", PowerManager.THERMAL_STATUS_CRITICAL},
|
||||
{"emergency", PowerManager.THERMAL_STATUS_EMERGENCY},
|
||||
{"shutdown", PowerManager.THERMAL_STATUS_SHUTDOWN},
|
||||
};
|
||||
}
|
||||
|
||||
@Test
|
||||
@Parameters(method = "parseThermalStatusData")
|
||||
public void testParseThermalStatus(String thermalStatusString,
|
||||
@PowerManager.ThermalStatus int expectedThermalStatus) {
|
||||
int result = DeviceConfigParsingUtils.parseThermalStatus(thermalStatusString);
|
||||
|
||||
assertEquals(expectedThermalStatus, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseThermalStatus_illegalStatus() {
|
||||
Throwable result = assertThrows(IllegalArgumentException.class,
|
||||
() -> DeviceConfigParsingUtils.parseThermalStatus("invalid_status"));
|
||||
|
||||
assertEquals("Invalid Thermal Status: invalid_status", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseBrightness() {
|
||||
float result = DeviceConfigParsingUtils.parseBrightness("0.65");
|
||||
|
||||
assertEquals(0.65, result, FLOAT_TOLERANCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseBrightness_lessThanMin() {
|
||||
Throwable result = assertThrows(IllegalArgumentException.class,
|
||||
() -> DeviceConfigParsingUtils.parseBrightness("-0.65"));
|
||||
|
||||
assertEquals("Brightness value out of bounds: -0.65", result.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseBrightness_moreThanMax() {
|
||||
Throwable result = assertThrows(IllegalArgumentException.class,
|
||||
() -> DeviceConfigParsingUtils.parseBrightness("1.65"));
|
||||
|
||||
assertEquals("Brightness value out of bounds: 1.65", result.getMessage());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user