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