diff options
10 files changed, 1155 insertions, 111 deletions
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java index c421ec04d6f5..59844e1afd1c 100644 --- a/services/core/java/com/android/server/display/BrightnessThrottler.java +++ b/services/core/java/com/android/server/display/BrightnessThrottler.java @@ -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; diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index d9cb299db69f..c25b25312c89 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -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; } diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 7043af863301..1061fab51398 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -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); + } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java new file mode 100644 index 000000000000..9345a3d97122 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -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 + } +} diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java new file mode 100644 index 000000000000..d0f28c3bea81 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -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); + } + } +} diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java new file mode 100644 index 000000000000..8ae962b83c69 --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessThermalClamper.java @@ -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); + } + } +} diff --git a/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java new file mode 100644 index 000000000000..a8034c58689d --- /dev/null +++ b/services/core/java/com/android/server/display/utils/DeviceConfigParsingUtils.java @@ -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; + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java new file mode 100644 index 000000000000..37d0f6250aaf --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessThermalClamperTest.java @@ -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; + } + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java new file mode 100644 index 000000000000..5e28e63cfa49 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/utils/DeviceConfigParsingUtilsTest.java @@ -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()); + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java index 4494b0c412dc..4494b0c412dc 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/SensorUtilsTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/utils/SensorUtilsTest.java |