diff --git a/core/java/android/hardware/display/BrightnessInfo.java b/core/java/android/hardware/display/BrightnessInfo.java index 99f3d156cfa95dd7c8da4fc12847abb8a39250fa..53a9a75fbca066d254920d16965ac2c38e4a83ef 100644 --- a/core/java/android/hardware/display/BrightnessInfo.java +++ b/core/java/android/hardware/display/BrightnessInfo.java @@ -59,7 +59,8 @@ public final class BrightnessInfo implements Parcelable { @IntDef(prefix = {"BRIGHTNESS_MAX_REASON_"}, value = { BRIGHTNESS_MAX_REASON_NONE, - BRIGHTNESS_MAX_REASON_THERMAL + BRIGHTNESS_MAX_REASON_THERMAL, + BRIGHTNESS_MAX_REASON_POWER_IC }) @Retention(RetentionPolicy.SOURCE) public @interface BrightnessMaxReason {} @@ -74,6 +75,11 @@ public final class BrightnessInfo implements Parcelable { */ public static final int BRIGHTNESS_MAX_REASON_THERMAL = 1; + /** + * Maximum brightness is restricted due to power throttling. + */ + public static final int BRIGHTNESS_MAX_REASON_POWER_IC = 2; + /** Brightness */ public final float brightness; @@ -144,6 +150,8 @@ public final class BrightnessInfo implements Parcelable { return "none"; case BRIGHTNESS_MAX_REASON_THERMAL: return "thermal"; + case BRIGHTNESS_MAX_REASON_POWER_IC: + return "power IC"; } return "invalid"; } diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index f347909348c89592f198fafea539756ee48c7173..a4593be8c2e41892039a8ff0107594ef37993b2b 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -1818,6 +1818,12 @@ public final class DisplayManager { */ String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data"; + /** + * Key for the power throttling data as a String formatted, from the display + * device config. + */ + String KEY_POWER_THROTTLING_DATA = "power_throttling_data"; + /** * Key for new power controller feature flag. If enabled new DisplayPowerController will * be used. diff --git a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java index 472c1f58dc8abc049c8c49fbcdc23aeba1b0f234..1ac3a12fad21d6ecaa5a5a89db070ea6ad513208 100644 --- a/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java +++ b/services/core/java/com/android/server/display/DeviceStateToLayoutMap.java @@ -135,7 +135,8 @@ class DeviceStateToLayoutMap { leadDisplayAddress, d.getBrightnessThrottlingMapId(), d.getRefreshRateZoneId(), - d.getRefreshRateThermalThrottlingMapId()); + d.getRefreshRateThermalThrottlingMapId(), + d.getPowerThrottlingMapId()); } layout.postProcessLocked(); } diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java index 1652871963b9396a4518e9bcc2115d05c5e492d7..7d9c0182a6913d67422d003862103c7cdfa3701c 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController2.java +++ b/services/core/java/com/android/server/display/DisplayPowerController2.java @@ -506,7 +506,6 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal mTag = "DisplayPowerController2[" + mDisplayId + "]"; mThermalBrightnessThrottlingDataId = logicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; - mDisplayDevice = mLogicalDisplay.getPrimaryDisplayDeviceLocked(); mUniqueDisplayId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId(); mDisplayStatsId = mUniqueDisplayId.hashCode(); @@ -566,8 +565,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal modeChangeCallback::run, new BrightnessClamperController.DisplayDeviceData( mUniqueDisplayId, mThermalBrightnessThrottlingDataId, - mDisplayDeviceConfig - ), mContext); + logicalDisplay.getPowerThrottlingDataIdLocked(), + mDisplayDeviceConfig), mContext, flags); // Seed the cached brightness saveBrightnessInfo(getScreenBrightnessSetting()); mAutomaticBrightnessStrategy = @@ -821,10 +820,8 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal .getDisplayDeviceInfoLocked().type == Display.TYPE_INTERNAL; final String thermalBrightnessThrottlingDataId = mLogicalDisplay.getDisplayInfoLocked().thermalBrightnessThrottlingDataId; - - mBrightnessClamperController.onDisplayChanged( - new BrightnessClamperController.DisplayDeviceData(mUniqueDisplayId, - mThermalBrightnessThrottlingDataId, config)); + final String powerThrottlingDataId = + mLogicalDisplay.getPowerThrottlingDataIdLocked(); mHandler.postAtTime(() -> { boolean changed = false; @@ -858,6 +855,14 @@ final class DisplayPowerController2 implements AutomaticBrightnessController.Cal } mIsDisplayInternal = isDisplayInternal; + // using local variables here, when mBrightnessThrottler is removed, + // mThermalBrightnessThrottlingDataId could be removed as well + // changed = true will be not needed - clampers are maintaining their state and + // will call updatePowerState if needed. + mBrightnessClamperController.onDisplayChanged( + new BrightnessClamperController.DisplayDeviceData(uniqueId, + thermalBrightnessThrottlingDataId, powerThrottlingDataId, config)); + if (changed) { updatePowerState(); } diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index bd82b81513df278f73a0c635748f022fd58b56fa..3d4209e0d6f343c06c38482644a11adf62cb8c4a 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -189,6 +189,11 @@ final class LogicalDisplay { @Nullable private SurfaceControl.RefreshRateRange mLayoutLimitedRefreshRate; + /** + * The ID of the power throttling data that should be used. + */ + private String mPowerThrottlingDataId; + /** * RefreshRateRange limitation for @Temperature.ThrottlingStatus */ @@ -205,6 +210,7 @@ final class LogicalDisplay { mIsEnabled = true; mIsInTransition = false; mThermalBrightnessThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; + mPowerThrottlingDataId = DisplayDeviceConfig.DEFAULT_ID; mBaseDisplayInfo.thermalBrightnessThrottlingDataId = mThermalBrightnessThrottlingDataId; } @@ -910,6 +916,25 @@ final class LogicalDisplay { } } + /** + * @param powerThrottlingDataId The ID of the brightness throttling data that this + * display should use. + */ + public void setPowerThrottlingDataIdLocked(String powerThrottlingDataId) { + if (!Objects.equals(powerThrottlingDataId, mPowerThrottlingDataId)) { + mPowerThrottlingDataId = powerThrottlingDataId; + mDirty = true; + } + } + + /** + * Returns powerThrottlingDataId which is the ID of the brightness + * throttling data that this display should use. + */ + public String getPowerThrottlingDataIdLocked() { + return mPowerThrottlingDataId; + } + /** * Sets the display of which this display is a follower, regarding brightness or other * properties. If set to {@link Layout#NO_LEAD_DISPLAY}, this display does not follow any @@ -976,6 +1001,7 @@ final class LogicalDisplay { pw.println("mLeadDisplayId=" + mLeadDisplayId); pw.println("mLayoutLimitedRefreshRate=" + mLayoutLimitedRefreshRate); pw.println("mThermalRefreshRateThrottling=" + mThermalRefreshRateThrottling); + pw.println("mPowerThrottlingDataId=" + mPowerThrottlingDataId); } @Override diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index b3b16ade054664a4bfb222a3ad73bf4468c7c629..c55bc62f5ae6be7d5860ade9f3c06a01bc31f02b 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -1123,13 +1123,15 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { displayLayout.getRefreshRateThermalThrottlingMapId() ) ); - setEnabledLocked(newDisplay, displayLayout.isEnabled()); newDisplay.setThermalBrightnessThrottlingDataIdLocked( displayLayout.getThermalBrightnessThrottlingMapId() == null ? DisplayDeviceConfig.DEFAULT_ID : displayLayout.getThermalBrightnessThrottlingMapId()); - + newDisplay.setPowerThrottlingDataIdLocked( + displayLayout.getPowerThrottlingMapId() == null + ? DisplayDeviceConfig.DEFAULT_ID + : displayLayout.getPowerThrottlingMapId()); newDisplay.setDisplayGroupNameLocked(displayLayout.getDisplayGroupName()); } } 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 index 54a280fddb6cf547547a15a3648295f5826594dd..68f72d3b085a361e82a91c24fbf23cf153e63d97 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamper.java @@ -52,7 +52,8 @@ abstract class BrightnessClamper<T> { abstract void stop(); - enum Type { - THERMAL + protected enum Type { + THERMAL, + POWER } } 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 index 14637afe4f76d75f3c4338c8767a753822bbc160..787f786a97dadfd3b0eeab539497d893d46d543d 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessClamperController.java @@ -34,9 +34,12 @@ import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData; import com.android.server.display.DisplayDeviceConfig.ThermalBrightnessThrottlingData; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.feature.DisplayManagerFlags; import java.io.PrintWriter; import java.util.ArrayList; @@ -48,11 +51,9 @@ import java.util.concurrent.Executor; */ public class BrightnessClamperController { private static final String TAG = "BrightnessClamperController"; - private final DeviceConfigParameterProvider mDeviceConfigParameterProvider; private final Handler mHandler; private final ClamperChangeListener mClamperChangeListenerExternal; - private final Executor mExecutor; private final List<BrightnessClamper<? super DisplayDeviceData>> mClampers; @@ -64,13 +65,15 @@ public class BrightnessClamperController { private boolean mClamperApplied = false; public BrightnessClamperController(Handler handler, - ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) { - this(new Injector(), handler, clamperChangeListener, data, context); + ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context, + DisplayManagerFlags flags) { + this(new Injector(), handler, clamperChangeListener, data, context, flags); } @VisibleForTesting BrightnessClamperController(Injector injector, Handler handler, - ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context) { + ClamperChangeListener clamperChangeListener, DisplayDeviceData data, Context context, + DisplayManagerFlags flags) { mDeviceConfigParameterProvider = injector.getDeviceConfigParameterProvider(); mHandler = handler; mClamperChangeListenerExternal = clamperChangeListener; @@ -84,7 +87,7 @@ public class BrightnessClamperController { } }; - mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data); + mClampers = injector.getClampers(handler, clamperChangeListenerInternal, data, flags); mModifiers = injector.getModifiers(context); mOnPropertiesChangedListener = properties -> mClampers.forEach(BrightnessClamper::onDeviceConfigChanged); @@ -144,6 +147,8 @@ public class BrightnessClamperController { return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; } else if (mClamperType == Type.THERMAL) { return BrightnessInfo.BRIGHTNESS_MAX_REASON_THERMAL; + } else if (mClamperType == Type.POWER) { + return BrightnessInfo.BRIGHTNESS_MAX_REASON_POWER_IC; } else { Slog.wtf(TAG, "BrightnessMaxReason not mapped for type=" + mClamperType); return BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE; @@ -193,6 +198,7 @@ public class BrightnessClamperController { mClamperType = clamperType; mClamperChangeListenerExternal.onChanged(); } + } private void start() { @@ -219,10 +225,15 @@ public class BrightnessClamperController { } List<BrightnessClamper<? super DisplayDeviceData>> getClampers(Handler handler, - ClamperChangeListener clamperChangeListener, DisplayDeviceData data) { + ClamperChangeListener clamperChangeListener, DisplayDeviceData data, + DisplayManagerFlags flags) { List<BrightnessClamper<? super DisplayDeviceData>> clampers = new ArrayList<>(); clampers.add( new BrightnessThermalClamper(handler, clamperChangeListener, data)); + if (flags.isPowerThrottlingClamperEnabled()) { + clampers.add(new BrightnessPowerClamper(handler, clamperChangeListener, + data)); + } return clampers; } @@ -235,21 +246,26 @@ public class BrightnessClamperController { } /** - * Data for clampers + * Config Data for clampers */ - public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData { + public static class DisplayDeviceData implements BrightnessThermalClamper.ThermalData, + BrightnessPowerClamper.PowerData { @NonNull private final String mUniqueDisplayId; @NonNull private final String mThermalThrottlingDataId; + @NonNull + private final String mPowerThrottlingDataId; private final DisplayDeviceConfig mDisplayDeviceConfig; public DisplayDeviceData(@NonNull String uniqueDisplayId, @NonNull String thermalThrottlingDataId, + @NonNull String powerThrottlingDataId, @NonNull DisplayDeviceConfig displayDeviceConfig) { mUniqueDisplayId = uniqueDisplayId; mThermalThrottlingDataId = thermalThrottlingDataId; + mPowerThrottlingDataId = powerThrottlingDataId; mDisplayDeviceConfig = displayDeviceConfig; } @@ -272,5 +288,24 @@ public class BrightnessClamperController { return mDisplayDeviceConfig.getThermalBrightnessThrottlingDataMapByThrottlingId().get( mThermalThrottlingDataId); } + + @NonNull + @Override + public String getPowerThrottlingDataId() { + return mPowerThrottlingDataId; + } + + @Nullable + @Override + public PowerThrottlingData getPowerThrottlingData() { + return mDisplayDeviceConfig.getPowerThrottlingDataMapByThrottlingId().get( + mPowerThrottlingDataId); + } + + @Nullable + @Override + public PowerThrottlingConfigData getPowerThrottlingConfigData() { + return mDisplayDeviceConfig.getPowerThrottlingConfigData(); + } } } diff --git a/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java new file mode 100644 index 0000000000000000000000000000000000000000..339b5896f3f72fd0cc4096ddf9c8679447eed83d --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/BrightnessPowerClamper.java @@ -0,0 +1,295 @@ +/* + * 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.os.Handler; +import android.os.PowerManager; +import android.os.Temperature; +import android.provider.DeviceConfigInterface; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.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 BrightnessPowerClamper extends + BrightnessClamper<BrightnessPowerClamper.PowerData> { + + private static final String TAG = "BrightnessPowerClamper"; + @NonNull + private final Injector mInjector; + @NonNull + private final DeviceConfigParameterProvider mConfigParameterProvider; + @NonNull + private final Handler mHandler; + @NonNull + private final ClamperChangeListener mChangeListener; + @Nullable + private PmicMonitor mPmicMonitor; + // data from DeviceConfig, for all displays, for all dataSets + // mapOf(uniqueDisplayId to mapOf(dataSetId to PowerThrottlingData)) + @NonNull + private Map<String, Map<String, PowerThrottlingData>> + mPowerThrottlingDataOverride = Map.of(); + // data from DisplayDeviceConfig, for particular display+dataSet + @Nullable + private PowerThrottlingData mPowerThrottlingDataFromDDC = null; + // Active data, if mPowerThrottlingDataOverride contains data for mUniqueDisplayId, + // mDataId, then use it, otherwise mPowerThrottlingDataFromDDC. + @Nullable + private PowerThrottlingData mPowerThrottlingDataActive = null; + @Nullable + private PowerThrottlingConfigData mPowerThrottlingConfigData = null; + + private @Temperature.ThrottlingStatus int mCurrentThermalLevel = Temperature.THROTTLING_NONE; + private float mCurrentAvgPowerConsumed = 0; + @Nullable + private String mUniqueDisplayId = null; + @Nullable + private String mDataId = null; + + private final BiFunction<String, String, ThrottlingLevel> mDataPointMapper = (key, value) -> { + try { + int status = DeviceConfigParsingUtils.parseThermalStatus(key); + float powerQuota = Float.parseFloat(value); + return new ThrottlingLevel(status, powerQuota); + } catch (IllegalArgumentException iae) { + return null; + } + }; + + private final Function<List<ThrottlingLevel>, PowerThrottlingData> + mDataSetMapper = PowerThrottlingData::create; + + + BrightnessPowerClamper(Handler handler, ClamperChangeListener listener, + PowerData powerData) { + this(new Injector(), handler, listener, powerData); + } + + @VisibleForTesting + BrightnessPowerClamper(Injector injector, Handler handler, ClamperChangeListener listener, + PowerData powerData) { + mInjector = injector; + mConfigParameterProvider = injector.getDeviceConfigParameterProvider(); + mHandler = handler; + mChangeListener = listener; + + mHandler.post(() -> { + setDisplayData(powerData); + loadOverrideData(); + start(); + }); + + } + + @Override + @NonNull + BrightnessClamper.Type getType() { + return Type.POWER; + } + + @Override + void onDeviceConfigChanged() { + mHandler.post(() -> { + loadOverrideData(); + recalculateActiveData(); + }); + } + + @Override + void onDisplayChanged(PowerData data) { + mHandler.post(() -> { + setDisplayData(data); + recalculateActiveData(); + }); + } + + @Override + void stop() { + if (mPmicMonitor != null) { + mPmicMonitor.shutdown(); + } + } + + /** + * Dumps the state of BrightnessPowerClamper. + */ + public void dump(PrintWriter pw) { + pw.println("BrightnessPowerClamper:"); + pw.println(" mCurrentAvgPowerConsumed=" + mCurrentAvgPowerConsumed); + pw.println(" mUniqueDisplayId=" + mUniqueDisplayId); + pw.println(" mCurrentThermalLevel=" + mCurrentThermalLevel); + pw.println(" mPowerThrottlingDataFromDDC=" + (mPowerThrottlingDataFromDDC == null ? "null" + : mPowerThrottlingDataFromDDC.toString())); + super.dump(pw); + } + + private void recalculateActiveData() { + if (mUniqueDisplayId == null || mDataId == null) { + return; + } + mPowerThrottlingDataActive = mPowerThrottlingDataOverride + .getOrDefault(mUniqueDisplayId, Map.of()).getOrDefault(mDataId, + mPowerThrottlingDataFromDDC); + if (mPowerThrottlingDataActive != null) { + if (mPmicMonitor != null) { + mPmicMonitor.stop(); + mPmicMonitor.start(); + } + } else { + if (mPmicMonitor != null) { + mPmicMonitor.stop(); + } + } + recalculateBrightnessCap(); + } + + private void loadOverrideData() { + String throttlingDataOverride = mConfigParameterProvider.getPowerThrottlingData(); + mPowerThrottlingDataOverride = DeviceConfigParsingUtils.parseDeviceConfigMap( + throttlingDataOverride, mDataPointMapper, mDataSetMapper); + } + + private void setDisplayData(@NonNull PowerData data) { + mUniqueDisplayId = data.getUniqueDisplayId(); + mDataId = data.getPowerThrottlingDataId(); + mPowerThrottlingDataFromDDC = data.getPowerThrottlingData(); + if (mPowerThrottlingDataFromDDC == null && !DEFAULT_ID.equals(mDataId)) { + Slog.wtf(TAG, + "Power throttling data is missing for powerThrottlingDataId=" + mDataId); + } + + mPowerThrottlingConfigData = data.getPowerThrottlingConfigData(); + if (mPowerThrottlingConfigData == null) { + Slog.d(TAG, + "Power throttling data is missing for configuration data."); + } + } + + private void recalculateBrightnessCap() { + boolean isActive = false; + float targetBrightnessCap = PowerManager.BRIGHTNESS_MAX; + float powerQuota = getPowerQuotaForThermalStatus(mCurrentThermalLevel); + if (mPowerThrottlingDataActive == null) { + return; + } + if (powerQuota > 0 && mCurrentAvgPowerConsumed > powerQuota) { + isActive = true; + // calculate new brightness Cap. + // Brightness has a linear relation to power-consumed. + targetBrightnessCap = + (powerQuota / mCurrentAvgPowerConsumed) * PowerManager.BRIGHTNESS_MAX; + // Cap to lowest allowed brightness on device. + targetBrightnessCap = Math.max(targetBrightnessCap, + mPowerThrottlingConfigData.brightnessLowestCapAllowed); + } + + if (mBrightnessCap != targetBrightnessCap || mIsActive != isActive) { + mIsActive = isActive; + mBrightnessCap = targetBrightnessCap; + mChangeListener.onChanged(); + } + } + + private float getPowerQuotaForThermalStatus(@Temperature.ThrottlingStatus int thermalStatus) { + float powerQuota = 0f; + if (mPowerThrottlingDataActive != null) { + // Throttling levels are sorted by increasing severity + for (ThrottlingLevel level : mPowerThrottlingDataActive.throttlingLevels) { + if (level.thermalStatus <= thermalStatus) { + powerQuota = level.powerQuotaMilliWatts; + } else { + // Throttling levels that are greater than the current status are irrelevant + break; + } + } + } + return powerQuota; + } + + private void recalculatePowerQuotaChange(float avgPowerConsumed, int thermalStatus) { + mHandler.post(() -> { + mCurrentThermalLevel = thermalStatus; + mCurrentAvgPowerConsumed = avgPowerConsumed; + recalculateBrightnessCap(); + }); + } + + private void start() { + if (mPowerThrottlingConfigData == null) { + return; + } + PowerChangeListener listener = (powerConsumed, thermalStatus) -> { + recalculatePowerQuotaChange(powerConsumed, thermalStatus); + }; + mPmicMonitor = + mInjector.getPmicMonitor(listener, mPowerThrottlingConfigData.pollingWindowMillis); + mPmicMonitor.start(); + } + + public interface PowerData { + @NonNull + String getUniqueDisplayId(); + + @NonNull + String getPowerThrottlingDataId(); + + @Nullable + PowerThrottlingData getPowerThrottlingData(); + + @Nullable + PowerThrottlingConfigData getPowerThrottlingConfigData(); + } + + /** + * Power change listener + */ + @FunctionalInterface + public interface PowerChangeListener { + /** + * Notifies that power state changed from power controller. + */ + void onChanged(float avgPowerConsumed, @Temperature.ThrottlingStatus int thermalStatus); + } + + @VisibleForTesting + static class Injector { + PmicMonitor getPmicMonitor(PowerChangeListener listener, int pollingTime) { + return new PmicMonitor(listener, pollingTime); + } + + DeviceConfigParameterProvider getDeviceConfigParameterProvider() { + return new DeviceConfigParameterProvider(DeviceConfigInterface.REAL); + } + } +} diff --git a/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java new file mode 100644 index 0000000000000000000000000000000000000000..26784f2353ff892d92e0f1d07e465326383c5bbc --- /dev/null +++ b/services/core/java/com/android/server/display/brightness/clamper/PmicMonitor.java @@ -0,0 +1,192 @@ +/* + * 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.BrightnessPowerClamper.PowerChangeListener; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.Context; +import android.hardware.power.stats.EnergyConsumer; +import android.hardware.power.stats.EnergyConsumerResult; +import android.hardware.power.stats.EnergyConsumerType; +import android.os.IThermalService; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.Temperature; +import android.power.PowerStatsInternal; +import android.util.IntArray; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +/** + * Monitors the display consumed power and helps make informed decision, + * regarding overconsumption. + */ +public class PmicMonitor { + private static final String TAG = "PmicMonitor"; + + // The executor to periodically monitor the display power. + private final ScheduledExecutorService mExecutor; + @NonNull + private final PowerChangeListener mPowerChangeListener; + private final long mPowerMonitorPeriodConfigSecs; + private final PowerStatsInternal mPowerStatsInternal; + @VisibleForTesting final IThermalService mThermalService; + private ScheduledFuture<?> mPmicMonitorFuture; + private float mLastEnergyConsumed = 0; + private float mCurrentAvgPower = 0; + private Temperature mCurrentTemperature; + private long mCurrentTimestampMillis = 0; + + PmicMonitor(PowerChangeListener listener, int powerMonitorPeriodConfigSecs) { + mPowerChangeListener = listener; + mPowerStatsInternal = LocalServices.getService(PowerStatsInternal.class); + mThermalService = IThermalService.Stub.asInterface( + ServiceManager.getService(Context.THERMAL_SERVICE)); + // start a periodic worker thread. + mExecutor = Executors.newSingleThreadScheduledExecutor(); + mPowerMonitorPeriodConfigSecs = (long) powerMonitorPeriodConfigSecs; + } + + @Nullable + private Temperature getDisplayTemperature() { + Temperature retTemperature = null; + try { + Temperature[] temperatures; + // TODO b/279114539 Try DISPLAY first and then fallback to SKIN. + temperatures = mThermalService.getCurrentTemperaturesWithType( + Temperature.TYPE_SKIN); + if (temperatures.length > 1) { + Slog.w(TAG, "Multiple skin temperatures not allowed!"); + } + if (temperatures.length > 0) { + retTemperature = temperatures[0]; + } + } catch (RemoteException e) { + Slog.w(TAG, "getDisplayTemperature failed" + e); + } + return retTemperature; + } + + private void capturePeriodicDisplayPower() { + final EnergyConsumer[] energyConsumers = mPowerStatsInternal.getEnergyConsumerInfo(); + if (energyConsumers == null || energyConsumers.length == 0) { + return; + } + final IntArray energyConsumerIds = new IntArray(); + for (int i = 0; i < energyConsumers.length; i++) { + if (energyConsumers[i].type == EnergyConsumerType.DISPLAY) { + energyConsumerIds.add(energyConsumers[i].id); + } + } + + if (energyConsumerIds.size() == 0) { + Slog.w(TAG, "DISPLAY energyConsumerIds size is null"); + return; + } + CompletableFuture<EnergyConsumerResult[]> futureECRs = + mPowerStatsInternal.getEnergyConsumedAsync(energyConsumerIds.toArray()); + if (futureECRs == null) { + Slog.w(TAG, "Energy consumers results are null"); + return; + } + + EnergyConsumerResult[] displayResults; + try { + displayResults = futureECRs.get(); + } catch (InterruptedException e) { + Slog.w(TAG, "timeout or interrupt reading getEnergyConsumedAsync failed", e); + displayResults = null; + } catch (ExecutionException e) { + Slog.wtf(TAG, "exception reading getEnergyConsumedAsync: ", e); + displayResults = null; + } + + if (displayResults == null || displayResults.length == 0) { + Slog.w(TAG, "displayResults are null"); + return; + } + // Support for only 1 display rail. + float energyConsumed = (displayResults[0].energyUWs - mLastEnergyConsumed); + float timeIntervalSeconds = + (displayResults[0].timestampMs - mCurrentTimestampMillis) / 1000.f; + // energy consumed is received in microwatts-seconds. + float currentPower = energyConsumed / timeIntervalSeconds; + // convert power received in microwatts to milliwatts. + currentPower = currentPower / 1000.f; + + // capture thermal state. + Temperature displayTemperature = getDisplayTemperature(); + mCurrentAvgPower = currentPower; + mCurrentTemperature = displayTemperature; + mLastEnergyConsumed = displayResults[0].energyUWs; + mCurrentTimestampMillis = displayResults[0].timestampMs; + if (mCurrentTemperature != null) { + mPowerChangeListener.onChanged(mCurrentAvgPower, mCurrentTemperature.getStatus()); + } + } + + /** + * Start polling the power IC. + */ + public void start() { + if (mPowerStatsInternal == null) { + Slog.w(TAG, "Power stats service not found for monitoring."); + return; + } + if (mThermalService == null) { + Slog.w(TAG, "Thermal service not found."); + return; + } + if (mPmicMonitorFuture == null) { + mPmicMonitorFuture = mExecutor.scheduleAtFixedRate( + this::capturePeriodicDisplayPower, + mPowerMonitorPeriodConfigSecs, + mPowerMonitorPeriodConfigSecs, + TimeUnit.SECONDS); + } else { + Slog.e(TAG, "already scheduled, stop() called before start."); + } + } + + /** + * Stop polling to power IC. + */ + public void stop() { + if (mPmicMonitorFuture != null) { + mPmicMonitorFuture.cancel(true); + mPmicMonitorFuture = null; + } + } + + /** + * Shutdown power IC service and worker thread. + */ + public void shutdown() { + mExecutor.shutdownNow(); + } +} diff --git a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java index 23ffe5948da81f5fd830ff06ba4ff18d5a99d0e7..465584c3d90ccd241906ef31c2039a2432bf577a 100644 --- a/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java +++ b/services/core/java/com/android/server/display/feature/DeviceConfigParameterProvider.java @@ -77,6 +77,12 @@ public class DeviceConfigParameterProvider { // Test parameters // usage e.g.: adb shell device_config put display_manager refresh_rate_in_hbm_sunlight 90 + // allows to customize power throttling data + public String getPowerThrottlingData() { + return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, + DisplayManager.DeviceConfig.KEY_POWER_THROTTLING_DATA, null); + } + // allows to customize brightness throttling data public String getBrightnessThrottlingData() { return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER, diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java index 7050c5a4168f8e12375b7887991e2a36cdb3a891..fae8383bb62ef9189cd9ccd79826a8491772e7ed 100644 --- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java +++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java @@ -67,20 +67,31 @@ public class DisplayManagerFlags { Flags.FLAG_BACK_UP_SMOOTH_DISPLAY_AND_FORCE_PEAK_REFRESH_RATE, Flags::backUpSmoothDisplayAndForcePeakRefreshRate); + private final FlagState mPowerThrottlingClamperFlagState = new FlagState( + Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER, + Flags::enablePowerThrottlingClamper); + /** Returns whether connected display management is enabled or not. */ public boolean isConnectedDisplayManagementEnabled() { return mConnectedDisplayManagementFlagState.isEnabled(); } - /** Returns whether hdr clamper is enabled on not*/ + /** Returns whether NBM Controller is enabled or not. */ public boolean isNbmControllerEnabled() { return mNbmControllerFlagState.isEnabled(); } + /** Returns whether hdr clamper is enabled on not. */ public boolean isHdrClamperEnabled() { return mHdrClamperFlagState.isEnabled(); } + /** Returns whether power throttling clamper is enabled on not. */ + public boolean isPowerThrottlingClamperEnabled() { + return mPowerThrottlingClamperFlagState.isEnabled(); + } + + /** * Returns whether adaptive tone improvements are enabled */ diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig index a85e10dcfe2eba46c1e303589b04fad57e81b338..9ab9c9def61bc662eca10061ec09cc24168a055a 100644 --- a/services/core/java/com/android/server/display/feature/display_flags.aconfig +++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig @@ -26,6 +26,14 @@ flag { is_fixed_read_only: true } +flag { + name: "enable_power_throttling_clamper" + namespace: "display_manager" + description: "Feature flag for Power Throttling Clamper" + bug: "294777007" + is_fixed_read_only: true +} + flag { name: "enable_adaptive_tone_improvements_1" namespace: "display_manager" diff --git a/services/core/java/com/android/server/display/layout/Layout.java b/services/core/java/com/android/server/display/layout/Layout.java index d9ec3de46b0e4791b9fef229461d78086991fafc..40cb3303adda7fc22d95bad4baaa91f50a69f508 100644 --- a/services/core/java/com/android/server/display/layout/Layout.java +++ b/services/core/java/com/android/server/display/layout/Layout.java @@ -80,7 +80,8 @@ public class Layout { createDisplayLocked(address, /* isDefault= */ true, /* isEnabled= */ true, DEFAULT_DISPLAY_GROUP_NAME, idProducer, POSITION_UNKNOWN, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, - /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ null); } /** @@ -97,6 +98,7 @@ public class Layout { * @param refreshRateZoneId Layout limited refresh rate zone name. * @param refreshRateThermalThrottlingMapId Name of which refresh rate throttling * policy should be used. + * @param powerThrottlingMapId Name of which power throttling policy should be used. * * @exception IllegalArgumentException When a default display owns a display group other than * DEFAULT_DISPLAY_GROUP. @@ -106,7 +108,8 @@ public class Layout { String displayGroupName, DisplayIdProducer idProducer, int position, @Nullable DisplayAddress leadDisplayAddress, String brightnessThrottlingMapId, @Nullable String refreshRateZoneId, - @Nullable String refreshRateThermalThrottlingMapId) { + @Nullable String refreshRateThermalThrottlingMapId, + @Nullable String powerThrottlingMapId) { if (contains(address)) { Slog.w(TAG, "Attempting to add second definition for display-device: " + address); return; @@ -139,7 +142,7 @@ public class Layout { final Display display = new Display(address, logicalDisplayId, isEnabled, displayGroupName, brightnessThrottlingMapId, position, leadDisplayAddress, refreshRateZoneId, - refreshRateThermalThrottlingMapId); + refreshRateThermalThrottlingMapId, powerThrottlingMapId); mDisplays.add(display); } @@ -311,6 +314,9 @@ public class Layout { @Nullable private final String mThermalRefreshRateThrottlingMapId; + @Nullable + private final String mPowerThrottlingMapId; + // The ID of the lead display that this display will follow in a layout. -1 means no lead. // This is determined using {@code mLeadDisplayAddress}. private int mLeadDisplayId; @@ -318,7 +324,8 @@ public class Layout { private Display(@NonNull DisplayAddress address, int logicalDisplayId, boolean isEnabled, @NonNull String displayGroupName, String brightnessThrottlingMapId, int position, @Nullable DisplayAddress leadDisplayAddress, @Nullable String refreshRateZoneId, - @Nullable String refreshRateThermalThrottlingMapId) { + @Nullable String refreshRateThermalThrottlingMapId, + @Nullable String powerThrottlingMapId) { mAddress = address; mLogicalDisplayId = logicalDisplayId; mIsEnabled = isEnabled; @@ -328,6 +335,7 @@ public class Layout { mLeadDisplayAddress = leadDisplayAddress; mRefreshRateZoneId = refreshRateZoneId; mThermalRefreshRateThrottlingMapId = refreshRateThermalThrottlingMapId; + mPowerThrottlingMapId = powerThrottlingMapId; mLeadDisplayId = NO_LEAD_DISPLAY; } @@ -344,6 +352,7 @@ public class Layout { + ", mLeadDisplayId: " + mLeadDisplayId + ", mLeadDisplayAddress: " + mLeadDisplayAddress + ", mThermalRefreshRateThrottlingMapId: " + mThermalRefreshRateThrottlingMapId + + ", mPowerThrottlingMapId: " + mPowerThrottlingMapId + "}"; } @@ -366,7 +375,9 @@ public class Layout { && this.mLeadDisplayId == otherDisplay.mLeadDisplayId && Objects.equals(mLeadDisplayAddress, otherDisplay.mLeadDisplayAddress) && Objects.equals(mThermalRefreshRateThrottlingMapId, - otherDisplay.mThermalRefreshRateThrottlingMapId); + otherDisplay.mThermalRefreshRateThrottlingMapId) + && Objects.equals(mPowerThrottlingMapId, + otherDisplay.mPowerThrottlingMapId); } @Override @@ -382,6 +393,7 @@ public class Layout { result = 31 * result + mLeadDisplayId; result = 31 * result + Objects.hashCode(mLeadDisplayAddress); result = 31 * result + Objects.hashCode(mThermalRefreshRateThrottlingMapId); + result = 31 * result + Objects.hashCode(mPowerThrottlingMapId); return result; } @@ -441,6 +453,15 @@ public class Layout { return mThermalRefreshRateThrottlingMapId; } + /** + * Gets the id of the power throttling map that should be used. + * @return The ID of the power throttling map that this display should use, + * null if unspecified, will fall back to default. + */ + public String getPowerThrottlingMapId() { + return mPowerThrottlingMapId; + } + private void setLeadDisplayId(int id) { mLeadDisplayId = id; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java index 4b124caca7d6cb9bd0612b9a5d0d6aa15c16ab04..8cc3408ad79b285acb0386d8c9c83bfaada31479 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DeviceStateToLayoutMapTest.java @@ -99,6 +99,14 @@ public class DeviceStateToLayoutMapTest { assertEquals("concurrent2", configLayout.getAt(1).getThermalBrightnessThrottlingMapId()); } + @Test + public void testPowerThrottlingMapId() { + Layout configLayout = mDeviceStateToLayoutMap.get(5); + + assertEquals("concurrent1", configLayout.getAt(0).getPowerThrottlingMapId()); + assertEquals("concurrent2", configLayout.getAt(1).getPowerThrottlingMapId()); + } + @Test public void testRearDisplayLayout() { Layout configLayout = mDeviceStateToLayoutMap.get(2); @@ -133,13 +141,15 @@ public class DeviceStateToLayoutMapTest { mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness1", /* refreshRateZoneId= */ "zone1", - /* refreshRateThermalThrottlingMapId= */ "rr1"); + /* refreshRateThermalThrottlingMapId= */ "rr1", + /* powerThrottlingMapId= */ "power1"); testLayout.createDisplayLocked(DisplayAddress.fromPhysicalDisplayId(678L), /* isDefault= */ false, /* isEnabled= */ false, /* displayGroupName= */ "group1", mDisplayIdProducerMock, Layout.Display.POSITION_REAR, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "brightness2", /* refreshRateZoneId= */ "zone2", - /* refreshRateThermalThrottlingMapId= */ "rr2"); + /* refreshRateThermalThrottlingMapId= */ "rr2", + /* powerThrottlingMapId= */ "power2"); testLayout.postProcessLocked(); assertEquals(testLayout, configLayout); @@ -200,7 +210,8 @@ public class DeviceStateToLayoutMapTest { mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, DisplayAddress.fromPhysicalDisplayId(123L), /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */ null)); + /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ null)); } @Test @@ -215,7 +226,8 @@ public class DeviceStateToLayoutMapTest { mDisplayIdProducerMock, Layout.Display.POSITION_FRONT, DisplayAddress.fromPhysicalDisplayId(987L), /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */ null)); + /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ null)); } @Test @@ -271,7 +283,8 @@ public class DeviceStateToLayoutMapTest { enabled, group, mDisplayIdProducerMock, Layout.Display.POSITION_UNKNOWN, leadDisplayAddress, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ null); } private void setupDeviceStateToLayoutMap() throws IOException { @@ -327,7 +340,6 @@ public class DeviceStateToLayoutMapTest { + "<brightnessThrottlingMapId>concurrent2</brightnessThrottlingMapId>\n" + "</display>\n" + "</layout>\n" - + "<layout>\n" + "<state>3</state> \n" + "<display enabled=\"true\" defaultDisplay=\"true\" " @@ -338,7 +350,6 @@ public class DeviceStateToLayoutMapTest { + "<address>678</address>\n" + "</display>\n" + "</layout>\n" - + "<layout>\n" + "<state>4</state> \n" + "<display enabled=\"true\" defaultDisplay=\"true\" >\n" @@ -351,6 +362,20 @@ public class DeviceStateToLayoutMapTest { + "<address>678</address>\n" + "</display>\n" + "</layout>\n" + + "<layout>\n" + + "<state>5</state> \n" + + "<display enabled=\"true\" defaultDisplay=\"true\">\n" + + "<address>345</address>\n" + + "<position>front</position>\n" + + "<powerThrottlingMapId>concurrent1</powerThrottlingMapId>\n" + + "</display>\n" + + "<display enabled=\"true\">\n" + + "<address>678</address>\n" + + "<position>rear</position>\n" + + "<powerThrottlingMapId>concurrent2</powerThrottlingMapId>\n" + + "</display>\n" + + "</layout>\n" + + "<layout>\n" + "<state>99</state> \n" + "<display enabled=\"true\" defaultDisplay=\"true\" " @@ -361,6 +386,7 @@ public class DeviceStateToLayoutMapTest { + "<refreshRateThermalThrottlingMapId>" + "rr1" + "</refreshRateThermalThrottlingMapId>" + + "<powerThrottlingMapId>power1</powerThrottlingMapId>\n" + "</display>\n" + "<display enabled=\"false\" displayGroup=\"group1\" " + "refreshRateZoneId=\"zone2\">\n" @@ -370,6 +396,7 @@ public class DeviceStateToLayoutMapTest { + "<refreshRateThermalThrottlingMapId>" + "rr2" + "</refreshRateThermalThrottlingMapId>" + + "<powerThrottlingMapId>power2</powerThrottlingMapId>\n" + "</display>\n" + "</layout>\n" + "</layouts>\n"; diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 065dd1f1f743da751f8ffcf8f28158cc44ee8221..8b13018fc14bebbb112c6af18332d2c681f28973 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -720,13 +720,15 @@ public class LogicalDisplayMapperTest { mIdProducer, POSITION_UNKNOWN, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "concurrent", - /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ "concurrent"); layout.createDisplayLocked(device2.getDisplayDeviceInfoLocked().address, /* isDefault= */ false, /* isEnabled= */ true, /* displayGroup= */ null, mIdProducer, POSITION_UNKNOWN, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ "concurrent", - /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateZoneId= */ null, /* refreshRateThermalThrottlingMapId= */ null, + /* powerThrottlingMapId= */ "concurrent"); when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); layout = new Layout(); @@ -927,7 +929,7 @@ public class LogicalDisplayMapperTest { /* isDefault= */ false, /* isEnabled= */ true, /* displayGroupName= */ null, mIdProducer, POSITION_REAR, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */null); + /* refreshRateThermalThrottlingMapId= */null, /* powerThrottlingMapId= */null); when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(layout); when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1); @@ -986,7 +988,7 @@ public class LogicalDisplayMapperTest { layout.createDisplayLocked(address, /* isDefault= */ false, enabled, group, mIdProducer, Layout.Display.POSITION_UNKNOWN, /* leadDisplayAddress= */ null, /* brightnessThrottlingMapId= */ null, /* refreshRateZoneId= */ null, - /* refreshRateThermalThrottlingMapId= */ null); + /* refreshRateThermalThrottlingMapId= */ null, /* powerThrottlingMapId= */ null); } private void advanceTime(long timeMs) { diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java index 22d26224c83f16ad055ff35fbded874fddaca5ba..c0e0df98213f9fae49cb1ca2f67ec5fc321c1c95 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessClamperControllerTest.java @@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest; import com.android.server.display.DisplayBrightnessState; import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.testutils.TestHandler; import org.junit.Before; @@ -63,12 +64,13 @@ public class BrightnessClamperControllerTest { @Mock private BrightnessClamper<BrightnessClamperController.DisplayDeviceData> mMockClamper; @Mock + private DisplayManagerFlags mFlags; + @Mock private BrightnessModifier mMockModifier; @Mock private DisplayManagerInternal.DisplayPowerRequest mMockRequest; @Mock private DeviceConfig.Properties mMockProperties; - private BrightnessClamperController mClamperController; private TestInjector mTestInjector; @@ -219,7 +221,7 @@ public class BrightnessClamperControllerTest { private BrightnessClamperController createBrightnessClamperController() { return new BrightnessClamperController(mTestInjector, mTestHandler, mMockExternalListener, - mMockDisplayDeviceData, mMockContext); + mMockDisplayDeviceData, mMockContext, mFlags); } private class TestInjector extends BrightnessClamperController.Injector { @@ -247,7 +249,8 @@ public class BrightnessClamperControllerTest { List<BrightnessClamper<? super BrightnessClamperController.DisplayDeviceData>> getClampers( Handler handler, BrightnessClamperController.ClamperChangeListener clamperChangeListener, - BrightnessClamperController.DisplayDeviceData data) { + BrightnessClamperController.DisplayDeviceData data, + DisplayManagerFlags flags) { mCapturedChangeListener = clamperChangeListener; return mClampers; } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b3f33ad858fe14e9a59e60a513015e55440c0bd4 --- /dev/null +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/BrightnessPowerClamperTest.java @@ -0,0 +1,246 @@ +/* + * 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.BrightnessPowerClamper.PowerChangeListener; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import android.os.PowerManager; +import android.os.RemoteException; +import android.os.Temperature; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.server.display.DisplayDeviceConfig; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingConfigData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData; +import com.android.server.display.DisplayDeviceConfig.PowerThrottlingData.ThrottlingLevel; +import com.android.server.display.feature.DeviceConfigParameterProvider; +import com.android.server.testutils.FakeDeviceConfigInterface; +import com.android.server.testutils.TestHandler; + +import junitparams.JUnitParamsRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.List; + +@RunWith(JUnitParamsRunner.class) +public class BrightnessPowerClamperTest { + private static final String TAG = "BrightnessPowerClamperTest"; + private static final float FLOAT_TOLERANCE = 0.001f; + + private static final String DISPLAY_ID = "displayId"; + @Mock + private BrightnessClamperController.ClamperChangeListener mMockClamperChangeListener; + private TestPmicMonitor mPmicMonitor; + private final FakeDeviceConfigInterface mFakeDeviceConfigInterface = + new FakeDeviceConfigInterface(); + private final TestHandler mTestHandler = new TestHandler(null); + private BrightnessPowerClamper mClamper; + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mClamper = new BrightnessPowerClamper(new TestInjector(), mTestHandler, + mMockClamperChangeListener, new TestPowerData()); + mTestHandler.flush(); + } + + @Test + public void testTypeIsPower() { + assertEquals(BrightnessClamper.Type.POWER, mClamper.getType()); + } + + @Test + public void testNoThrottlingData() { + assertFalse(mClamper.isActive()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + } + + @Test + public void testPowerThrottlingNoOngoingAnimation() throws RemoteException { + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE); + mTestHandler.flush(); + assertFalse(mClamper.isActive()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f)))); + + mPmicMonitor.setAvgPowerConsumed(200f); + float expectedBrightness = 0.5f; + expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX; + + mTestHandler.flush(); + // Assume current brightness as max, as there is no throttling. + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL); + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f)))); + + mPmicMonitor.setAvgPowerConsumed(100f); + expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX; + mTestHandler.flush(); + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + } + + @Test + public void testPowerThrottlingWithOngoingAnimation() throws RemoteException { + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_SEVERE); + mTestHandler.flush(); + assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE, 100f)))); + + mPmicMonitor.setAvgPowerConsumed(200f); + float expectedBrightness = 0.5f; + expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX; + + mTestHandler.flush(); + // Assume current brightness as max, as there is no throttling. + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_CRITICAL); + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL, 50f)))); + + mPmicMonitor.setAvgPowerConsumed(100f); + expectedBrightness = 0.5f * PowerManager.BRIGHTNESS_MAX; + mTestHandler.flush(); + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + } + + @Test + public void testPowerThrottlingRemoveBrightnessCap() throws RemoteException { + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_LIGHT); + mTestHandler.flush(); + assertFalse(mClamper.isActive()); + assertEquals(PowerManager.BRIGHTNESS_MAX, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + + // update a new device config for power-throttling. + mClamper.onDisplayChanged(new TestPowerData( + List.of(new ThrottlingLevel(PowerManager.THERMAL_STATUS_LIGHT, 100f)))); + + mPmicMonitor.setAvgPowerConsumed(200f); + float expectedBrightness = 0.5f; + expectedBrightness = expectedBrightness * PowerManager.BRIGHTNESS_MAX; + + mTestHandler.flush(); + + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + mPmicMonitor.setThermalStatus(Temperature.THROTTLING_NONE); + + mPmicMonitor.setAvgPowerConsumed(100f); + // No cap applied for Temperature.THROTTLING_NONE + expectedBrightness = PowerManager.BRIGHTNESS_MAX; + mTestHandler.flush(); + + // clamper should not be active anymore. + assertFalse(mClamper.isActive()); + // Assume current brightness as max, as there is no throttling. + assertEquals(expectedBrightness, mClamper.getBrightnessCap(), FLOAT_TOLERANCE); + } + + + private static class TestPmicMonitor extends PmicMonitor { + private Temperature mCurrentTemperature; + private final PowerChangeListener mListener; + TestPmicMonitor(PowerChangeListener listener, int pollingTime) { + super(listener, pollingTime); + mListener = listener; + } + public void setAvgPowerConsumed(float power) { + int status = mCurrentTemperature.getStatus(); + mListener.onChanged(power, status); + } + public void setThermalStatus(@Temperature.ThrottlingStatus int status) { + mCurrentTemperature = new Temperature(100, Temperature.TYPE_SKIN, "test_temp", status); + } + } + + private class TestInjector extends BrightnessPowerClamper.Injector { + @Override + TestPmicMonitor getPmicMonitor(PowerChangeListener listener, + int pollingTime) { + mPmicMonitor = new TestPmicMonitor(listener, pollingTime); + return mPmicMonitor; + } + + @Override + DeviceConfigParameterProvider getDeviceConfigParameterProvider() { + return new DeviceConfigParameterProvider(mFakeDeviceConfigInterface); + } + } + + private static class TestPowerData implements BrightnessPowerClamper.PowerData { + + private final String mUniqueDisplayId; + private final String mDataId; + private final PowerThrottlingData mData; + private final PowerThrottlingConfigData mConfigData; + + private TestPowerData() { + this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, null); + } + + private TestPowerData(List<ThrottlingLevel> data) { + this(DISPLAY_ID, DisplayDeviceConfig.DEFAULT_ID, data); + } + + private TestPowerData(String uniqueDisplayId, String dataId, List<ThrottlingLevel> data) { + mUniqueDisplayId = uniqueDisplayId; + mDataId = dataId; + mData = PowerThrottlingData.create(data); + mConfigData = new PowerThrottlingConfigData(0.1f, 10); + } + + @NonNull + @Override + public String getUniqueDisplayId() { + return mUniqueDisplayId; + } + + @NonNull + @Override + public String getPowerThrottlingDataId() { + return mDataId; + } + + @Nullable + @Override + public PowerThrottlingData getPowerThrottlingData() { + return mData; + } + + @Nullable + @Override + public PowerThrottlingConfigData getPowerThrottlingConfigData() { + return mConfigData; + } + } +}