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:
Oleg Petšjonkin
2023-07-13 13:57:19 +00:00
committed by Automerger Merge Worker
10 changed files with 1155 additions and 111 deletions

View File

@@ -38,17 +38,24 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData;
import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData.ThrottlingLevel;
import com.android.server.display.feature.DeviceConfigParameterProvider; import com.android.server.display.feature.DeviceConfigParameterProvider;
import com.android.server.display.utils.DeviceConfigParsingUtils;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.Executor; 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 * This class monitors various conditions, such as skin temperature throttling status, and limits
* the allowed brightness range accordingly. * the allowed brightness range accordingly.
*
* @deprecated will be replaced by
* {@link com.android.server.display.brightness.clamper.BrightnessThermalClamper}
*/ */
@Deprecated
class BrightnessThrottler { class BrightnessThrottler {
private static final String TAG = "BrightnessThrottler"; private static final String TAG = "BrightnessThrottler";
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
@@ -93,8 +100,21 @@ class BrightnessThrottler {
// time the underlying display device changes. // time the underlying display device changes.
// This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData. // This map is indexed by uniqueDisplayId, to provide maps for throttlingId -> throttlingData.
// HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >> // HashMap< uniqueDisplayId, HashMap< throttlingDataId, ThermalBrightnessThrottlingData >>
private final HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> private final Map<String, Map<String, ThermalBrightnessThrottlingData>>
mThermalBrightnessThrottlingDataOverride = new HashMap<>(1); 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, BrightnessThrottler(Handler handler, Runnable throttlingChangeCallback, String uniqueDisplayId,
String throttlingDataId, String throttlingDataId,
@@ -257,79 +277,15 @@ class BrightnessThrottler {
// 456,2,moderate,0.9,critical,0.7,id_2 // 456,2,moderate,0.9,critical,0.7,id_2
// displayId, number, <state, val> * number // displayId, number, <state, val> * number
// displayId, <number, <state, val> * number>, throttlingId // 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() { private void loadThermalBrightnessThrottlingDataFromDeviceConfig() {
HashMap<String, HashMap<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
new HashMap<>(1);
mThermalBrightnessThrottlingDataString = mThermalBrightnessThrottlingDataString =
mConfigParameterProvider.getBrightnessThrottlingData(); mConfigParameterProvider.getBrightnessThrottlingData();
boolean validConfig = true;
mThermalBrightnessThrottlingDataOverride.clear(); mThermalBrightnessThrottlingDataOverride.clear();
if (mThermalBrightnessThrottlingDataString != null) { if (mThermalBrightnessThrottlingDataString != null) {
String[] throttlingDataSplits = mThermalBrightnessThrottlingDataString.split(";"); Map<String, Map<String, ThermalBrightnessThrottlingData>> tempThrottlingData =
for (String s : throttlingDataSplits) { DeviceConfigParsingUtils.parseDeviceConfigMap(
if (!parseAndAddData(s, tempThrottlingData)) { mThermalBrightnessThrottlingDataString, mDataPointMapper, mDataSetMapper);
validConfig = false;
break;
}
}
if (validConfig) {
mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData); mThermalBrightnessThrottlingDataOverride.putAll(tempThrottlingData);
tempThrottlingData.clear();
}
} else { } else {
Slog.w(TAG, "DeviceConfig ThermalBrightnessThrottlingData is null"); 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 class SkinThermalStatusObserver extends IThermalEventListener.Stub {
private final Injector mInjector; private final Injector mInjector;
private final Handler mHandler; private final Handler mHandler;

View File

@@ -458,7 +458,7 @@ public class DisplayDeviceConfig {
public static final String QUIRK_CAN_SET_BRIGHTNESS_VIA_HWC = "canSetBrightnessViaHwc"; 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 float BRIGHTNESS_DEFAULT = 0.5f;
private static final String ETC_DIR = "etc"; private static final String ETC_DIR = "etc";
@@ -3127,11 +3127,15 @@ public class DisplayDeviceConfig {
public static class ThermalBrightnessThrottlingData { public static class ThermalBrightnessThrottlingData {
public List<ThrottlingLevel> throttlingLevels; public List<ThrottlingLevel> throttlingLevels;
static class ThrottlingLevel { /**
* thermal status to brightness cap holder
*/
public static class ThrottlingLevel {
public @PowerManager.ThermalStatus int thermalStatus; public @PowerManager.ThermalStatus int thermalStatus;
public float brightness; public float brightness;
ThrottlingLevel(@PowerManager.ThermalStatus int thermalStatus, float brightness) { public ThrottlingLevel(
@PowerManager.ThermalStatus int thermalStatus, float brightness) {
this.thermalStatus = thermalStatus; this.thermalStatus = thermalStatus;
this.brightness = brightness; this.brightness = brightness;
} }

View File

@@ -48,6 +48,7 @@ import android.os.Trace;
import android.os.UserHandle; import android.os.UserHandle;
import android.provider.Settings; import android.provider.Settings;
import android.util.FloatProperty; import android.util.FloatProperty;
import android.util.IndentingPrintWriter;
import android.util.Log; import android.util.Log;
import android.util.MathUtils; import android.util.MathUtils;
import android.util.MutableFloat; 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.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog; import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.RingBuffer; import com.android.internal.util.RingBuffer;
import com.android.server.LocalServices; import com.android.server.LocalServices;
import com.android.server.am.BatteryStatsService; 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.BrightnessReason;
import com.android.server.display.brightness.BrightnessUtils; import com.android.server.display.brightness.BrightnessUtils;
import com.android.server.display.brightness.DisplayBrightnessController; 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.brightness.strategy.AutomaticBrightnessStrategy;
import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal;
import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener;
@@ -380,6 +381,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
private final BrightnessThrottler mBrightnessThrottler; private final BrightnessThrottler mBrightnessThrottler;
private final BrightnessClamperController mBrightnessClamperController;
private final Runnable mOnBrightnessChangeRunnable; private final Runnable mOnBrightnessChangeRunnable;
private final BrightnessEvent mLastBrightnessEvent; private final BrightnessEvent mLastBrightnessEvent;
@@ -554,6 +557,13 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault, mDisplayId, mLogicalDisplay.getDisplayInfoLocked().brightnessDefault,
brightnessSetting, () -> postBrightnessChangeRunnable(), brightnessSetting, () -> postBrightnessChangeRunnable(),
new HandlerExecutor(mHandler)); new HandlerExecutor(mHandler));
mBrightnessClamperController = new BrightnessClamperController(mHandler,
modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData(
mUniqueDisplayId,
mThermalBrightnessThrottlingDataId,
mDisplayDeviceConfig
));
// Seed the cached brightness // Seed the cached brightness
saveBrightnessInfo(getScreenBrightnessSetting()); saveBrightnessInfo(getScreenBrightnessSetting());
@@ -782,6 +792,10 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
final String thermalBrightnessThrottlingDataId = final String thermalBrightnessThrottlingDataId =
mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId;
mBrightnessClamperController.onDisplayChanged(
new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId,
mThermalBrightnessThrottlingDataId, config));
mHandler.postAtTime(() -> { mHandler.postAtTime(() -> {
boolean changed = false; boolean changed = false;
if (mDisplayDevice != device) { if (mDisplayDevice != device) {
@@ -1187,6 +1201,7 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
mDisplayPowerProximityStateController.cleanup(); mDisplayPowerProximityStateController.cleanup();
mBrightnessRangeController.stop(); mBrightnessRangeController.stop();
mBrightnessThrottler.stop(); mBrightnessThrottler.stop();
mBrightnessClamperController.stop();
mHandler.removeCallbacksAndMessages(null); mHandler.removeCallbacksAndMessages(null);
// Release any outstanding wakelocks we're still holding because of pending messages. // Release any outstanding wakelocks we're still holding because of pending messages.
@@ -1519,6 +1534,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal
// allowed range. // allowed range.
float animateValue = clampScreenBrightness(brightnessState); 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 // 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 // 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. // (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) { if (mDisplayStateController != null) {
mDisplayStateController.dumpsys(pw); mDisplayStateController.dumpsys(pw);
} }
pw.println();
if (mBrightnessClamperController != null) {
mBrightnessClamperController.dump(ipw);
}
} }

View File

@@ -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
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}