Merge "Add CEC AIDL tests to test mapping (presubmit)"
diff --git a/audio/aidl/TEST_MAPPING b/audio/aidl/TEST_MAPPING
index a166e61..486c13d 100644
--- a/audio/aidl/TEST_MAPPING
+++ b/audio/aidl/TEST_MAPPING
@@ -13,6 +13,9 @@
       "name": "VtsHalDownmixTargetTest"
     },
     {
+      "name": "VtsHalDynamicProcessingTargetTest"
+    },
+    {
       "name": "VtsHalEnvironmentalReverbTargetTest"
     },
     {
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/CommandId.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/CommandId.aidl
index 79299ee..86b69fa 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/CommandId.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/CommandId.aidl
@@ -37,14 +37,14 @@
   START = 0,
   STOP = 1,
   RESET = 2,
-  VENDOR_COMMAND_0 = 256,
-  VENDOR_COMMAND_1 = 257,
-  VENDOR_COMMAND_2 = 258,
-  VENDOR_COMMAND_3 = 259,
-  VENDOR_COMMAND_4 = 260,
-  VENDOR_COMMAND_5 = 261,
-  VENDOR_COMMAND_6 = 262,
-  VENDOR_COMMAND_7 = 263,
-  VENDOR_COMMAND_8 = 264,
-  VENDOR_COMMAND_9 = 265,
+  VENDOR_COMMAND_0 = 0x100,
+  VENDOR_COMMAND_1,
+  VENDOR_COMMAND_2,
+  VENDOR_COMMAND_3,
+  VENDOR_COMMAND_4,
+  VENDOR_COMMAND_5,
+  VENDOR_COMMAND_6,
+  VENDOR_COMMAND_7,
+  VENDOR_COMMAND_8,
+  VENDOR_COMMAND_9,
 }
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Downmix.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Downmix.aidl
index 76f8ce5..402441d 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Downmix.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Downmix.aidl
@@ -47,7 +47,7 @@
   }
   @VintfStability
   enum Type {
-    STRIP = 0,
-    FOLD = 1,
+    STRIP,
+    FOLD,
   }
 }
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/DynamicsProcessing.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/DynamicsProcessing.aidl
index ed4dc80..8e5b719 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/DynamicsProcessing.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/DynamicsProcessing.aidl
@@ -36,14 +36,14 @@
 union DynamicsProcessing {
   android.hardware.audio.effect.VendorExtension vendorExtension;
   android.hardware.audio.effect.DynamicsProcessing.EngineArchitecture engineArchitecture;
-  android.hardware.audio.effect.DynamicsProcessing.BandChannelConfig preEq;
-  android.hardware.audio.effect.DynamicsProcessing.BandChannelConfig postEq;
-  android.hardware.audio.effect.DynamicsProcessing.EqBandConfig preEqBand;
-  android.hardware.audio.effect.DynamicsProcessing.EqBandConfig postEqBand;
-  android.hardware.audio.effect.DynamicsProcessing.BandChannelConfig mbc;
-  android.hardware.audio.effect.DynamicsProcessing.MbcBandConfig mbcBand;
-  android.hardware.audio.effect.DynamicsProcessing.LimiterConfig limiter;
-  float inputGainDb;
+  android.hardware.audio.effect.DynamicsProcessing.ChannelConfig[] preEq;
+  android.hardware.audio.effect.DynamicsProcessing.ChannelConfig[] postEq;
+  android.hardware.audio.effect.DynamicsProcessing.EqBandConfig[] preEqBand;
+  android.hardware.audio.effect.DynamicsProcessing.EqBandConfig[] postEqBand;
+  android.hardware.audio.effect.DynamicsProcessing.ChannelConfig[] mbc;
+  android.hardware.audio.effect.DynamicsProcessing.MbcBandConfig[] mbcBand;
+  android.hardware.audio.effect.DynamicsProcessing.LimiterConfig[] limiter;
+  android.hardware.audio.effect.DynamicsProcessing.InputGain[] inputGain;
   @VintfStability
   union Id {
     int vendorExtensionTag;
@@ -52,37 +52,39 @@
   @VintfStability
   parcelable Capability {
     ParcelableHolder extension;
+    float minCutOffFreq;
+    float maxCutOffFreq;
   }
   enum ResolutionPreference {
-    FAVOR_FREQUENCY_RESOLUTION = 0,
-    FAVOR_TIME_RESOLUTION = 1,
+    FAVOR_FREQUENCY_RESOLUTION,
+    FAVOR_TIME_RESOLUTION,
   }
   @VintfStability
-  parcelable BandEnablement {
+  parcelable StageEnablement {
     boolean inUse;
     int bandCount;
   }
   @VintfStability
   parcelable EngineArchitecture {
     android.hardware.audio.effect.DynamicsProcessing.ResolutionPreference resolutionPreference = android.hardware.audio.effect.DynamicsProcessing.ResolutionPreference.FAVOR_FREQUENCY_RESOLUTION;
-    float preferredFrameDurationMs;
-    android.hardware.audio.effect.DynamicsProcessing.BandEnablement preEqBand;
-    android.hardware.audio.effect.DynamicsProcessing.BandEnablement postEqBand;
-    android.hardware.audio.effect.DynamicsProcessing.BandEnablement mbcBand;
+    float preferredProcessingDurationMs;
+    android.hardware.audio.effect.DynamicsProcessing.StageEnablement preEqStage;
+    android.hardware.audio.effect.DynamicsProcessing.StageEnablement postEqStage;
+    android.hardware.audio.effect.DynamicsProcessing.StageEnablement mbcStage;
     boolean limiterInUse;
   }
   @VintfStability
-  parcelable BandChannelConfig {
+  parcelable ChannelConfig {
     int channel;
-    android.hardware.audio.effect.DynamicsProcessing.BandEnablement enablement;
+    boolean enable;
   }
   @VintfStability
   parcelable EqBandConfig {
     int channel;
     int band;
     boolean enable;
-    float cutoffFrequency;
-    float gain;
+    float cutoffFrequencyHz;
+    float gainDb;
   }
   @VintfStability
   parcelable MbcBandConfig {
@@ -90,7 +92,6 @@
     int band;
     boolean enable;
     float cutoffFrequencyHz;
-    float gainDb;
     float attackTimeMs;
     float releaseTimeMs;
     float ratio;
@@ -105,7 +106,6 @@
   parcelable LimiterConfig {
     int channel;
     boolean enable;
-    boolean inUse;
     int linkGroup;
     float attackTimeMs;
     float releaseTimeMs;
@@ -113,4 +113,9 @@
     float thresholdDb;
     float postGainDb;
   }
+  @VintfStability
+  parcelable InputGain {
+    int channel;
+    float gainDb;
+  }
 }
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/EnvironmentalReverb.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/EnvironmentalReverb.aidl
index 0e61932..7f34f33 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/EnvironmentalReverb.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/EnvironmentalReverb.aidl
@@ -44,15 +44,15 @@
   int diffusionPm;
   int densityPm;
   boolean bypass;
-  const int MIN_ROOM_LEVEL_MB = -6000;
+  const int MIN_ROOM_LEVEL_MB = (-6000);
   const int MAX_ROOM_LEVEL_MB = 0;
-  const int MIN_ROOM_HF_LEVEL_MB = -4000;
+  const int MIN_ROOM_HF_LEVEL_MB = (-4000);
   const int MAX_ROOM_HF_LEVEL_MB = 0;
   const int MIN_DECAY_TIME_MS = 100;
   const int MAX_DECAY_TIME_MS = 20000;
   const int MIN_DECAY_HF_RATIO_PM = 100;
   const int MAX_DECAY_HF_RATIO_PM = 1000;
-  const int MIN_LEVEL_MB = -6000;
+  const int MIN_LEVEL_MB = (-6000);
   const int MAX_LEVEL_MB = 0;
   const int MIN_DELAY_MS = 0;
   const int MAX_DELAY_MS = 65;
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/HapticGenerator.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/HapticGenerator.aidl
index 959594b..20f7e02 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/HapticGenerator.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/HapticGenerator.aidl
@@ -48,9 +48,9 @@
   }
   @Backing(type="int") @VintfStability
   enum VibratorScale {
-    MUTE = -100,
-    VERY_LOW = -2,
-    LOW = -1,
+    MUTE = (-100),
+    VERY_LOW = (-2),
+    LOW = (-1),
     NONE = 0,
     HIGH = 1,
     VERY_HIGH = 2,
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/NoiseSuppression.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/NoiseSuppression.aidl
index 223d95a..397f897 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/NoiseSuppression.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/NoiseSuppression.aidl
@@ -47,8 +47,8 @@
   }
   @Backing(type="int") @VintfStability
   enum Level {
-    LOW = 0,
-    MEDIUM = 1,
-    HIGH = 2,
+    LOW,
+    MEDIUM,
+    HIGH,
   }
 }
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/PresetReverb.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/PresetReverb.aidl
index 24a9ce1..4651742 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/PresetReverb.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/PresetReverb.aidl
@@ -38,13 +38,13 @@
   android.hardware.audio.effect.PresetReverb.Presets preset;
   @Backing(type="int") @VintfStability
   enum Presets {
-    NONE = 0,
-    SMALLROOM = 1,
-    MEDIUMROOM = 2,
-    LARGEROOM = 3,
-    MEDIUMHALL = 4,
-    LARGEHALL = 5,
-    PLATE = 6,
+    NONE,
+    SMALLROOM,
+    MEDIUMROOM,
+    LARGEROOM,
+    MEDIUMHALL,
+    LARGEHALL,
+    PLATE,
   }
   @VintfStability
   union Id {
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/State.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/State.aidl
index 3176b01..17f9814 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/State.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/State.aidl
@@ -34,7 +34,7 @@
 package android.hardware.audio.effect;
 @Backing(type="byte") @VintfStability
 enum State {
-  INIT = 0,
-  IDLE = 1,
-  PROCESSING = 2,
+  INIT,
+  IDLE,
+  PROCESSING,
 }
diff --git a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Visualizer.aidl b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Visualizer.aidl
index 25f0b73..c8cb551 100644
--- a/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Visualizer.aidl
+++ b/audio/aidl/aidl_api/android.hardware.audio.effect/current/android/hardware/audio/effect/Visualizer.aidl
@@ -62,12 +62,12 @@
   @VintfStability
   enum ScalingMode {
     NORMALIZED = 0,
-    AS_PLAYED = 1,
+    AS_PLAYED,
   }
   @VintfStability
   enum MeasurementMode {
     NONE = 0,
-    PEAK_RMS = 1,
+    PEAK_RMS,
   }
   @VintfStability
   union GetOnlyParameters {
diff --git a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
index e25b15a..501dc01 100644
--- a/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
+++ b/audio/aidl/android/hardware/audio/core/stream-out-async-sm.gv
@@ -30,6 +30,7 @@
     STANDBY -> PAUSED [label="burst"];                // producer -> active
     IDLE -> STANDBY [label="standby"];                // consumer -> passive
     IDLE -> TRANSFERRING [label="burst"];             // producer -> active
+    IDLE -> ACTIVE [label="burst"];                   // full write
     ACTIVE -> PAUSED [label="pause"];                 // consumer -> passive (not consuming)
     ACTIVE -> DRAINING [label="drain"];               // producer -> passive
     ACTIVE -> TRANSFERRING [label="burst"];           // early unblocking
@@ -45,6 +46,7 @@
     PAUSED -> IDLE [label="flush"];                   // producer -> passive, buffer is cleared
     DRAINING -> IDLE [label="←IStreamCallback.onDrainReady"];
     DRAINING -> TRANSFERRING [label="burst"];         // producer -> active
+    DRAINING -> ACTIVE [label="burst"];               // full write
     DRAINING -> DRAIN_PAUSED [label="pause"];         // consumer -> passive (not consuming)
     DRAIN_PAUSED -> DRAINING [label="start"];         // consumer -> active
     DRAIN_PAUSED -> TRANSFER_PAUSED [label="burst"];  // producer -> active
diff --git a/audio/aidl/android/hardware/audio/core/stream-out-sm.gv b/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
index 6aa5c61..47e7fda 100644
--- a/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
+++ b/audio/aidl/android/hardware/audio/core/stream-out-sm.gv
@@ -31,6 +31,7 @@
     ACTIVE -> ACTIVE [label="burst"];
     ACTIVE -> PAUSED [label="pause"];          // consumer -> passive (not consuming)
     ACTIVE -> DRAINING [label="drain"];        // producer -> passive
+    ACTIVE -> IDLE [label="drain"];            // synchronous drain
     PAUSED -> PAUSED [label="burst"];
     PAUSED -> ACTIVE [label="start"];          // consumer -> active
     PAUSED -> IDLE [label="flush"];            // producer -> passive, buffer is cleared
diff --git a/audio/aidl/android/hardware/audio/effect/DynamicsProcessing.aidl b/audio/aidl/android/hardware/audio/effect/DynamicsProcessing.aidl
index ee5dcad..6db3338 100644
--- a/audio/aidl/android/hardware/audio/effect/DynamicsProcessing.aidl
+++ b/audio/aidl/android/hardware/audio/effect/DynamicsProcessing.aidl
@@ -51,6 +51,14 @@
          * capability definition not enough.
          */
         ParcelableHolder extension;
+        /**
+         * Min Cut off frequency (in Hz) for all Bands.
+         */
+        float minCutOffFreq;
+        /**
+         * Max Cut off frequency (in Hz) for all Bands.
+         */
+        float maxCutOffFreq;
     }
 
     /**
@@ -68,16 +76,16 @@
     }
 
     /**
-     * Band enablement configuration.
+     * Stage enablement configuration.
      */
     @VintfStability
-    parcelable BandEnablement {
+    parcelable StageEnablement {
         /**
-         * True if multi-band stage is in use.
+         * True if stage is in use.
          */
         boolean inUse;
         /**
-         * Number of bands configured for this stage.
+         * Number of bands configured for this stage. Must be positive when inUse is true.
          */
         int bandCount;
     }
@@ -92,21 +100,22 @@
          */
         ResolutionPreference resolutionPreference = ResolutionPreference.FAVOR_FREQUENCY_RESOLUTION;
         /**
-         * Preferred frame duration in milliseconds (ms).
+         * Preferred processing duration in milliseconds (ms). Must not be negative, 0 means no
+         * preference.
          */
-        float preferredFrameDurationMs;
+        float preferredProcessingDurationMs;
         /**
          * PreEq stage (Multi-band Equalizer) configuration.
          */
-        BandEnablement preEqBand;
+        StageEnablement preEqStage;
         /**
          * PostEq stage (Multi-band Equalizer) configuration.
          */
-        BandEnablement postEqBand;
+        StageEnablement postEqStage;
         /**
          * MBC stage (Multi-band Compressor) configuration.
          */
-        BandEnablement mbcBand;
+        StageEnablement mbcStage;
         /**
          * True if Limiter stage is in use.
          */
@@ -114,18 +123,19 @@
     }
 
     /**
-     * Band enablement configuration for a specific channel.
+     * Enablement configuration for a specific channel.
      */
     @VintfStability
-    parcelable BandChannelConfig {
+    parcelable ChannelConfig {
         /**
-         * Channel index.
+         * Channel index. Must not be negative, and not exceed the channel count calculated from
+         * Parameter.common.input.base.channelMask.
          */
         int channel;
         /**
-         * Channel index.
+         * Channel enablement configuration. Can not be true if corresponding stage is not in use.
          */
-        BandEnablement enablement;
+        boolean enable;
     }
 
     /**
@@ -134,7 +144,8 @@
     @VintfStability
     parcelable EqBandConfig {
         /**
-         * Channel index.
+         * Channel index. Must not be negative, and not exceed the channel count calculated from
+         * Parameter.common.input.base.channelMask.
          */
         int channel;
         /**
@@ -142,17 +153,20 @@
          */
         int band;
         /**
-         * True if EQ stage is enabled.
+         * True if EQ band is enabled.
+         * If EngineArchitecture EQ stage inUse was set to false, then enable can not be set to
+         * true.
          */
         boolean enable;
         /**
-         * Topmost frequency number (in Hz) this band will process.
+         * Topmost frequency number (in Hz) this band will process. Must be in the range of
+         * [minCutOffFreq, maxCutOffFreq] defined in Capability.
          */
-        float cutoffFrequency;
+        float cutoffFrequencyHz;
         /**
          * Gain factor in decibels (dB).
          */
-        float gain;
+        float gainDb;
     }
 
     /**
@@ -161,51 +175,53 @@
     @VintfStability
     parcelable MbcBandConfig {
         /**
-         * Channel index.
+         * Channel index. Must not be negative, and not exceed the channel count calculated from
+         * Parameter.common.input.base.channelMask.
          */
         int channel;
         /**
-         * Band index, must in the range of [0, bandCount-1].
+         * Band index. Must be in the range of [0, bandCount-1].
          */
         int band;
         /**
-         * True if MBC stage is enabled.
+         * True if MBC band is enabled.
+         * If EngineArchitecture MBC inUse was set to false, then enable here can not be set to
+         * true.
          */
         boolean enable;
         /**
-         * Topmost frequency number (in Hz) this band will process.
+         * Topmost frequency number (in Hz) this band will process. Must be in the range of
+         * [minCutOffFreq, maxCutOffFreq] defined in Capability.
          */
         float cutoffFrequencyHz;
         /**
-         * Gain factor in decibels (dB).
-         */
-        float gainDb;
-        /**
-         * Attack Time for compressor in milliseconds (ms).
+         * Attack Time for compressor in milliseconds (ms). Must not be negative.
          */
         float attackTimeMs;
         /**
-         * Release Time for compressor in milliseconds (ms).
+         * Release Time for compressor in milliseconds (ms). Must not be negative.
          */
         float releaseTimeMs;
         /**
-         * Compressor ratio (N:1) (input:output).
+         * Compressor ratio (N:1) (input:output). Must not be negative.
          */
         float ratio;
         /**
-         * Compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS).
+         * Compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS).  Must not be
+         * positive.
          */
         float thresholdDb;
         /**
-         * Width in decibels (dB) around compressor threshold point.
+         * Width in decibels (dB) around compressor threshold point. Must not be negative.
          */
         float kneeWidthDb;
         /**
-         * Noise gate threshold in decibels (dB) from 0 dB Full Scale (dBFS).
+         * Noise gate threshold in decibels (dB) from 0 dB Full Scale (dBFS). Must not be positive.
          */
         float noiseGateThresholdDb;
         /**
-         * Expander ratio (1:N) (input:output) for signals below the Noise Gate Threshold.
+         * Expander ratio (1:N) (input:output) for signals below the Noise Gate Threshold. Must not
+         * be negative.
          */
         float expanderRatio;
         /**
@@ -224,36 +240,35 @@
     @VintfStability
     parcelable LimiterConfig {
         /**
-         * Channel index.
+         * Channel index. Must not be negative, and not exceed the channel count calculated from
+         * Parameter.common.input.base.channelMask.
          */
         int channel;
         /**
-         * True if Limiter stage is enabled.
+         * True if Limiter band is enabled.
+         * If EngineArchitecture limiterInUse was set to false, then enable can not be set to true.
          */
         boolean enable;
         /**
-         * True if Limiter stage is in use.
-         */
-        boolean inUse;
-        /**
          * Index of group assigned to this Limiter. Only limiters that share the same linkGroup
          * index will react together.
          */
         int linkGroup;
         /**
-         * Attack Time for compressor in milliseconds (ms).
+         * Attack Time for compressor in milliseconds (ms). Must not be negative.
          */
         float attackTimeMs;
         /**
-         * Release Time for compressor in milliseconds (ms).
+         * Release Time for compressor in milliseconds (ms). Must not be negative.
          */
         float releaseTimeMs;
         /**
-         * Compressor ratio (N:1) (input:output).
+         * Compressor ratio (N:1) (input:output). Must not be negative.
          */
         float ratio;
         /**
-         * Compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS).
+         * Compressor threshold measured in decibels (dB) from 0 dB Full Scale (dBFS). Must not be
+         * positive.
          */
         float thresholdDb;
         /**
@@ -263,39 +278,55 @@
     }
 
     /**
+     * Input gain for a channel (specified by the channel index).
+     */
+    @VintfStability
+    parcelable InputGain {
+        /**
+         * Channel index. Must not be negative, and not exceed the channel count calculated from
+         * Parameter.common.input.base.channelMask.
+         */
+        int channel;
+        /**
+         * Gain applied to the input signal in decibels (dB). 0 dB means no change in level.
+         */
+        float gainDb;
+    }
+
+    /**
      * Effect engine architecture.
      */
     EngineArchitecture engineArchitecture;
     /**
-     * PreEq stage per channel configuration.
+     * PreEq stage per channel configuration. Only valid when pre EQ stage inUse is true.
      */
-    BandChannelConfig preEq;
+    ChannelConfig[] preEq;
     /**
-     * PostEq stage per channel configuration.
+     * PostEq stage per channel configuration. Only valid when post EQ stage inUse is true.
      */
-    BandChannelConfig postEq;
+    ChannelConfig[] postEq;
     /**
-     * PreEq stage per band configuration.
+     * PreEq stage per band configuration. Only valid when pre EQ stage inUse is true.
      */
-    EqBandConfig preEqBand;
+    EqBandConfig[] preEqBand;
     /**
-     * PostEq stage per band configuration.
+     * PostEq stage per band configuration. Only valid when post EQ stage inUse is true.
      */
-    EqBandConfig postEqBand;
+    EqBandConfig[] postEqBand;
     /**
-     * MBC stage per channel configuration.
+     * MBC stage per channel configuration. Only valid when MBC stage inUse is true.
      */
-    BandChannelConfig mbc;
+    ChannelConfig[] mbc;
     /**
-     * PostEq stage per band configuration.
+     * PostEq stage per band configuration. Only valid when MBC stage inUse is true.
      */
-    MbcBandConfig mbcBand;
+    MbcBandConfig[] mbcBand;
     /**
-     * Limiter stage configuration.
+     * Limiter stage configuration. Only valid when limiter stage inUse is true.
      */
-    LimiterConfig limiter;
+    LimiterConfig[] limiter;
     /**
-     * Input gain factor in decibels (dB). 0 dB means no change in level.
+     * Input gain factor.
      */
-    float inputGainDb;
+    InputGain[] inputGain;
 }
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 6f23636..1e6785f 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -156,7 +156,7 @@
         "libbassboostsw",
         "libbundleaidl",
         "libdownmixaidl",
-        "libdynamicsprocessingsw",
+        "libdynamicsprocessingaidl",
         "libenvreverbsw",
         "libequalizersw",
         "libhapticgeneratoraidl",
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index e8b5bfc..13b04cd 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -46,6 +46,7 @@
 using aidl::android::media::audio::common::AudioPortConfig;
 using aidl::android::media::audio::common::AudioPortExt;
 using aidl::android::media::audio::common::AudioProfile;
+using aidl::android::media::audio::common::Boolean;
 using aidl::android::media::audio::common::Int;
 using aidl::android::media::audio::common::PcmType;
 using android::hardware::audio::common::getFrameSizeInBytes;
@@ -138,12 +139,15 @@
         (flags.getTag() == AudioIoFlags::Tag::output &&
          !isBitPositionFlagSet(flags.get<AudioIoFlags::Tag::output>(),
                                AudioOutputFlags::MMAP_NOIRQ))) {
+        StreamContext::DebugParameters params{mDebug.streamTransientStateDelayMs,
+                                              mVendorDebug.forceTransientBurst,
+                                              mVendorDebug.forceSynchronousDrain};
         StreamContext temp(
                 std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
                 std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
                 portConfigIt->format.value(), portConfigIt->channelMask.value(),
                 std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames),
-                asyncCallback, mDebug.streamTransientStateDelayMs);
+                asyncCallback, params);
         if (temp.isValid()) {
             *out_context = std::move(temp);
         } else {
@@ -976,18 +980,69 @@
     return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
 }
 
+const std::string Module::VendorDebug::kForceTransientBurstName = "aosp.forceTransientBurst";
+const std::string Module::VendorDebug::kForceSynchronousDrainName = "aosp.forceSynchronousDrain";
+
 ndk::ScopedAStatus Module::getVendorParameters(const std::vector<std::string>& in_ids,
                                                std::vector<VendorParameter>* _aidl_return) {
     LOG(DEBUG) << __func__ << ": id count: " << in_ids.size();
-    (void)_aidl_return;
-    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    bool allParametersKnown = true;
+    for (const auto& id : in_ids) {
+        if (id == VendorDebug::kForceTransientBurstName) {
+            VendorParameter forceTransientBurst{.id = id};
+            forceTransientBurst.ext.setParcelable(Boolean{mVendorDebug.forceTransientBurst});
+            _aidl_return->push_back(std::move(forceTransientBurst));
+        } else if (id == VendorDebug::kForceSynchronousDrainName) {
+            VendorParameter forceSynchronousDrain{.id = id};
+            forceSynchronousDrain.ext.setParcelable(Boolean{mVendorDebug.forceSynchronousDrain});
+            _aidl_return->push_back(std::move(forceSynchronousDrain));
+        } else {
+            allParametersKnown = false;
+            LOG(ERROR) << __func__ << ": unrecognized parameter \"" << id << "\"";
+        }
+    }
+    if (allParametersKnown) return ndk::ScopedAStatus::ok();
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
 }
 
+namespace {
+
+template <typename W>
+bool extractParameter(const VendorParameter& p, decltype(W::value)* v) {
+    std::optional<W> value;
+    binder_status_t result = p.ext.getParcelable(&value);
+    if (result == STATUS_OK && value.has_value()) {
+        *v = value.value().value;
+        return true;
+    }
+    LOG(ERROR) << __func__ << ": failed to read the value of the parameter \"" << p.id
+               << "\": " << result;
+    return false;
+}
+
+}  // namespace
+
 ndk::ScopedAStatus Module::setVendorParameters(const std::vector<VendorParameter>& in_parameters,
                                                bool in_async) {
     LOG(DEBUG) << __func__ << ": parameter count " << in_parameters.size()
                << ", async: " << in_async;
-    return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    bool allParametersKnown = true;
+    for (const auto& p : in_parameters) {
+        if (p.id == VendorDebug::kForceTransientBurstName) {
+            if (!extractParameter<Boolean>(p, &mVendorDebug.forceTransientBurst)) {
+                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            }
+        } else if (p.id == VendorDebug::kForceSynchronousDrainName) {
+            if (!extractParameter<Boolean>(p, &mVendorDebug.forceSynchronousDrain)) {
+                return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            }
+        } else {
+            allParametersKnown = false;
+            LOG(ERROR) << __func__ << ": unrecognized parameter \"" << p.id << "\"";
+        }
+    }
+    if (allParametersKnown) return ndk::ScopedAStatus::ok();
+    return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
 }
 
 ndk::ScopedAStatus Module::addDeviceEffect(
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index a490a2a..0520cba 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -402,7 +402,11 @@
                     usleep(1000);  // Simulate a blocking call into the driver.
                     populateReply(&reply, mIsConnected);
                     // Can switch the state to ERROR if a driver error occurs.
-                    switchToTransientState(StreamDescriptor::State::DRAINING);
+                    if (mState == StreamDescriptor::State::ACTIVE && mForceSynchronousDrain) {
+                        mState = StreamDescriptor::State::IDLE;
+                    } else {
+                        switchToTransientState(StreamDescriptor::State::DRAINING);
+                    }
                 } else if (mState == StreamDescriptor::State::TRANSFER_PAUSED) {
                     mState = StreamDescriptor::State::DRAIN_PAUSED;
                     populateReply(&reply, mIsConnected);
@@ -467,14 +471,19 @@
 
 bool StreamOutWorkerLogic::write(size_t clientSize, StreamDescriptor::Reply* reply) {
     const size_t readByteCount = mDataMQ->availableToRead();
-    // Amount of data that the HAL module is going to actually use.
-    const size_t byteCount = std::min({clientSize, readByteCount, mDataBufferSize});
     bool fatal = false;
     if (bool success = readByteCount > 0 ? mDataMQ->read(&mDataBuffer[0], readByteCount) : true) {
         const bool isConnected = mIsConnected;
         LOG(DEBUG) << __func__ << ": reading of " << readByteCount << " bytes from data MQ"
                    << " succeeded; connected? " << isConnected;
-        // Frames are consumed and counted regardless of connection status.
+        // Amount of data that the HAL module is going to actually use.
+        size_t byteCount = std::min({clientSize, readByteCount, mDataBufferSize});
+        if (byteCount >= mFrameSize && mForceTransientBurst) {
+            // In order to prevent the state machine from going to ACTIVE state,
+            // simulate partial write.
+            byteCount -= mFrameSize;
+        }
+        // Frames are consumed and counted regardless of the connection status.
         reply->fmqByteCount += byteCount;
         mFrameCount += byteCount / mFrameSize;
         populateReply(reply, isConnected);
diff --git a/audio/aidl/default/acousticEchoCanceler/AcousticEchoCancelerSw.cpp b/audio/aidl/default/acousticEchoCanceler/AcousticEchoCancelerSw.cpp
index 7f06013..40b46e0 100644
--- a/audio/aidl/default/acousticEchoCanceler/AcousticEchoCancelerSw.cpp
+++ b/audio/aidl/default/acousticEchoCanceler/AcousticEchoCancelerSw.cpp
@@ -54,7 +54,6 @@
         return EX_ILLEGAL_ARGUMENT;
     }
     *_aidl_return = AcousticEchoCancelerSw::kDescriptor;
-    LOG(ERROR) << __func__ << "xxx " << _aidl_return->toString();
     return EX_NONE;
 }
 
diff --git a/audio/aidl/default/audio_effects_config.xml b/audio/aidl/default/audio_effects_config.xml
index 9670e9c..e460f89 100644
--- a/audio/aidl/default/audio_effects_config.xml
+++ b/audio/aidl/default/audio_effects_config.xml
@@ -33,7 +33,7 @@
         <library name="bassboostsw" path="libbassboostsw.so"/>
         <library name="bundle" path="libbundleaidl.so"/>
         <library name="downmix" path="libdownmixaidl.so"/>
-        <library name="dynamics_processingsw" path="libdynamicsprocessingsw.so"/>
+        <library name="dynamics_processing" path="libdynamicsprocessingaidl.so"/>
         <library name="equalizersw" path="libequalizersw.so"/>
         <library name="haptic_generator" path="libhapticgeneratoraidl.so"/>
         <library name="loudness_enhancer" path="libloudnessenhanceraidl.so"/>
@@ -72,7 +72,7 @@
             <libsw library="bundle" uuid="8631f300-72e2-11df-b57e-0002a5d5c51b"/>
         </effectProxy>
         <effect name="downmix" library="downmix" uuid="93f04452-e4fe-41cc-91f9-e475b6d1d69f"/>
-        <effect name="dynamics_processing" library="dynamics_processingsw" uuid="fa818d78-588b-11ed-9b6a-0242ac120002"/>
+        <effect name="dynamics_processing" library="dynamics_processing" uuid="e0e6539b-1781-7261-676f-6d7573696340"/>
         <effect name="haptic_generator" library="haptic_generator" uuid="97c4acd1-8b82-4f2f-832e-c2fe5d7a9931"/>
         <effect name="loudness_enhancer" library="loudness_enhancer" uuid="fa415329-2034-4bea-b5dc-5b381c8d1e2c"/>
         <effect name="env_reverb" library="env_reverbsw" uuid="fa819886-588b-11ed-9b6a-0242ac120002"/>
diff --git a/audio/aidl/default/dynamicProcessing/DynamicsProcessingSw.cpp b/audio/aidl/default/dynamicProcessing/DynamicsProcessingSw.cpp
index 39345a9..0ffbaa1 100644
--- a/audio/aidl/default/dynamicProcessing/DynamicsProcessingSw.cpp
+++ b/audio/aidl/default/dynamicProcessing/DynamicsProcessingSw.cpp
@@ -18,6 +18,7 @@
 #define LOG_TAG "AHAL_DynamicsProcessingSw"
 #include <Utils.h>
 #include <algorithm>
+#include <set>
 #include <unordered_set>
 
 #include <android-base/logging.h>
@@ -60,7 +61,8 @@
 namespace aidl::android::hardware::audio::effect {
 
 const std::string DynamicsProcessingSw::kEffectName = "DynamicsProcessingSw";
-const DynamicsProcessing::Capability DynamicsProcessingSw::kCapability;
+const DynamicsProcessing::Capability DynamicsProcessingSw::kCapability = {.minCutOffFreq = 220,
+                                                                          .maxCutOffFreq = 20000};
 const Descriptor DynamicsProcessingSw::kDescriptor = {
         .common = {.id = {.type = kDynamicsProcessingTypeUUID,
                           .uuid = kDynamicsProcessingSwImplUUID,
@@ -83,16 +85,143 @@
     RETURN_IF(Parameter::Specific::dynamicsProcessing != specific.getTag(), EX_ILLEGAL_ARGUMENT,
               "EffectNotSupported");
 
-    mSpecificParam = specific.get<Parameter::Specific::dynamicsProcessing>();
-    LOG(DEBUG) << __func__ << " success with: " << specific.toString();
-    return ndk::ScopedAStatus::ok();
+    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
+
+    LOG(INFO) << __func__ << specific.toString();
+    auto& dpParam = specific.get<Parameter::Specific::dynamicsProcessing>();
+    auto tag = dpParam.getTag();
+    switch (tag) {
+        case DynamicsProcessing::engineArchitecture: {
+            RETURN_IF(mContext->setEngineArchitecture(
+                              dpParam.get<DynamicsProcessing::engineArchitecture>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setEngineArchitectureFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        case DynamicsProcessing::preEq: {
+            RETURN_IF(mContext->setPreEqChannelCfgs(dpParam.get<DynamicsProcessing::preEq>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setPreEqChannelCfgsFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        case DynamicsProcessing::postEq: {
+            RETURN_IF(mContext->setPostEqChannelCfgs(dpParam.get<DynamicsProcessing::postEq>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setPostEqChannelCfgsFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        case DynamicsProcessing::mbc: {
+            RETURN_IF(mContext->setMbcChannelCfgs(dpParam.get<DynamicsProcessing::mbc>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setMbcChannelCfgsFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        case DynamicsProcessing::preEqBand: {
+            RETURN_IF(mContext->setPreEqBandCfgs(dpParam.get<DynamicsProcessing::preEqBand>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setPreEqBandCfgsFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        case DynamicsProcessing::postEqBand: {
+            RETURN_IF(mContext->setPostEqBandCfgs(dpParam.get<DynamicsProcessing::postEqBand>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setPostEqBandCfgsFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        case DynamicsProcessing::mbcBand: {
+            RETURN_IF(mContext->setMbcBandCfgs(dpParam.get<DynamicsProcessing::mbcBand>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "setMbcBandCfgsFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        case DynamicsProcessing::limiter: {
+            RETURN_IF(mContext->setLimiterCfgs(dpParam.get<DynamicsProcessing::limiter>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "limiterCfgsFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        case DynamicsProcessing::inputGain: {
+            RETURN_IF(mContext->setInputGainCfgs(dpParam.get<DynamicsProcessing::inputGain>()) !=
+                              RetCode::SUCCESS,
+                      EX_ILLEGAL_ARGUMENT, "inputGainCfgFailed");
+            return ndk::ScopedAStatus::ok();
+        }
+        case DynamicsProcessing::vendorExtension: {
+            LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
+                    EX_ILLEGAL_ARGUMENT, "DynamicsProcessingTagNotSupported");
+        }
+    }
 }
 
 ndk::ScopedAStatus DynamicsProcessingSw::getParameterSpecific(const Parameter::Id& id,
                                                               Parameter::Specific* specific) {
     auto tag = id.getTag();
     RETURN_IF(Parameter::Id::dynamicsProcessingTag != tag, EX_ILLEGAL_ARGUMENT, "wrongIdTag");
-    specific->set<Parameter::Specific::dynamicsProcessing>(mSpecificParam);
+    auto dpId = id.get<Parameter::Id::dynamicsProcessingTag>();
+    auto dpIdTag = dpId.getTag();
+    switch (dpIdTag) {
+        case DynamicsProcessing::Id::commonTag:
+            return getParameterDynamicsProcessing(dpId.get<DynamicsProcessing::Id::commonTag>(),
+                                                  specific);
+        case DynamicsProcessing::Id::vendorExtensionTag:
+            LOG(ERROR) << __func__ << " unsupported tag: " << toString(dpIdTag);
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
+                    EX_ILLEGAL_ARGUMENT, "DynamicsProcessingTagNotSupported");
+    }
+}
+
+ndk::ScopedAStatus DynamicsProcessingSw::getParameterDynamicsProcessing(
+        const DynamicsProcessing::Tag& tag, Parameter::Specific* specific) {
+    RETURN_IF(!mContext, EX_NULL_POINTER, "nullContext");
+
+    DynamicsProcessing dpParam;
+    switch (tag) {
+        case DynamicsProcessing::Tag::engineArchitecture: {
+            dpParam.set<DynamicsProcessing::engineArchitecture>(mContext->getEngineArchitecture());
+            break;
+        }
+        case DynamicsProcessing::Tag::preEq: {
+            dpParam.set<DynamicsProcessing::preEq>(mContext->getPreEqChannelCfgs());
+            break;
+        }
+        case DynamicsProcessing::Tag::postEq: {
+            dpParam.set<DynamicsProcessing::postEq>(mContext->getPostEqChannelCfgs());
+            break;
+        }
+        case DynamicsProcessing::Tag::mbc: {
+            dpParam.set<DynamicsProcessing::mbc>(mContext->getMbcChannelCfgs());
+            break;
+        }
+        case DynamicsProcessing::Tag::preEqBand: {
+            dpParam.set<DynamicsProcessing::preEqBand>(mContext->getPreEqBandCfgs());
+            break;
+        }
+        case DynamicsProcessing::Tag::postEqBand: {
+            dpParam.set<DynamicsProcessing::postEqBand>(mContext->getPostEqBandCfgs());
+            break;
+        }
+        case DynamicsProcessing::Tag::mbcBand: {
+            dpParam.set<DynamicsProcessing::mbcBand>(mContext->getMbcBandCfgs());
+            break;
+        }
+        case DynamicsProcessing::Tag::limiter: {
+            dpParam.set<DynamicsProcessing::limiter>(mContext->getLimiterCfgs());
+            break;
+        }
+        case DynamicsProcessing::Tag::inputGain: {
+            dpParam.set<DynamicsProcessing::inputGain>(mContext->getInputGainCfgs());
+            break;
+        }
+        case DynamicsProcessing::vendorExtension: {
+            LOG(ERROR) << __func__ << " unsupported tag: " << toString(tag);
+            return ndk::ScopedAStatus::fromExceptionCodeWithMessage(
+                    EX_ILLEGAL_ARGUMENT, "DynamicsProcessingTagNotSupported");
+        }
+    }
+
+    specific->set<Parameter::Specific::dynamicsProcessing>(dpParam);
+    LOG(INFO) << __func__ << specific->toString();
     return ndk::ScopedAStatus::ok();
 }
 
@@ -127,4 +256,252 @@
     return {STATUS_OK, samples, samples};
 }
 
+RetCode DynamicsProcessingSwContext::setCommon(const Parameter::Common& common) {
+    mCommon = common;
+    mChannelCount =
+            ::android::hardware::audio::common::getChannelCount(common.input.base.channelMask);
+    resizeChannels();
+    resizeBands();
+    LOG(INFO) << __func__ << mCommon.toString();
+    return RetCode::SUCCESS;
+}
+
+RetCode DynamicsProcessingSwContext::setEngineArchitecture(
+        const DynamicsProcessing::EngineArchitecture& cfg) {
+    RETURN_VALUE_IF(!validateEngineConfig(cfg), RetCode::ERROR_ILLEGAL_PARAMETER,
+                    "illegalEngineConfig");
+
+    if (mEngineSettings == cfg) {
+        LOG(INFO) << __func__ << " not change in engine, do nothing";
+        return RetCode::SUCCESS;
+    }
+    mEngineSettings = cfg;
+    resizeBands();
+    return RetCode::SUCCESS;
+}
+
+RetCode DynamicsProcessingSwContext::setChannelCfgs(
+        const std::vector<DynamicsProcessing::ChannelConfig>& cfgs,
+        std::vector<DynamicsProcessing::ChannelConfig>& targetCfgs,
+        const DynamicsProcessing::StageEnablement& stage) {
+    RETURN_VALUE_IF(!stage.inUse, RetCode::ERROR_ILLEGAL_PARAMETER, "stageNotInUse");
+
+    RetCode ret = RetCode::SUCCESS;
+    std::unordered_set<int> channelSet;
+    for (auto& cfg : cfgs) {
+        if (cfg.channel < 0 || (size_t)cfg.channel >= mChannelCount) {
+            LOG(ERROR) << __func__ << " skip illegal channel config " << cfg.toString();
+            ret = RetCode::ERROR_ILLEGAL_PARAMETER;
+            continue;
+        }
+        if (0 != channelSet.count(cfg.channel)) {
+            LOG(WARNING) << __func__ << " duplicated channel " << cfg.channel;
+            ret = RetCode::ERROR_ILLEGAL_PARAMETER;
+        } else {
+            channelSet.insert(cfg.channel);
+        }
+        targetCfgs[cfg.channel] = cfg;
+    }
+    return ret;
+}
+
+RetCode DynamicsProcessingSwContext::setPreEqChannelCfgs(
+        const std::vector<DynamicsProcessing::ChannelConfig>& cfgs) {
+    return setChannelCfgs(cfgs, mPreEqChCfgs, mEngineSettings.preEqStage);
+}
+
+RetCode DynamicsProcessingSwContext::setPostEqChannelCfgs(
+        const std::vector<DynamicsProcessing::ChannelConfig>& cfgs) {
+    return setChannelCfgs(cfgs, mPostEqChCfgs, mEngineSettings.postEqStage);
+}
+
+RetCode DynamicsProcessingSwContext::setMbcChannelCfgs(
+        const std::vector<DynamicsProcessing::ChannelConfig>& cfgs) {
+    return setChannelCfgs(cfgs, mMbcChCfgs, mEngineSettings.mbcStage);
+}
+
+RetCode DynamicsProcessingSwContext::setEqBandCfgs(
+        const std::vector<DynamicsProcessing::EqBandConfig>& cfgs,
+        std::vector<DynamicsProcessing::EqBandConfig>& targetCfgs,
+        const DynamicsProcessing::StageEnablement& stage,
+        const std::vector<DynamicsProcessing::ChannelConfig>& channelConfig) {
+    RETURN_VALUE_IF(!stage.inUse, RetCode::ERROR_ILLEGAL_PARAMETER, "eqStageNotInUse");
+
+    RetCode ret = RetCode::SUCCESS;
+    std::set<std::pair<int /* channel */, int /* band */>> bandSet;
+
+    for (auto& cfg : cfgs) {
+        if (0 != bandSet.count({cfg.channel, cfg.band})) {
+            LOG(WARNING) << __func__ << " duplicated band " << cfg.toString();
+            ret = RetCode::ERROR_ILLEGAL_PARAMETER;
+        } else {
+            bandSet.insert({cfg.channel, cfg.band});
+        }
+        if (!validateEqBandConfig(cfg, mChannelCount, stage.bandCount, channelConfig)) {
+            LOG(WARNING) << __func__ << " skip invalid band " << cfg.toString();
+            ret = RetCode::ERROR_ILLEGAL_PARAMETER;
+            continue;
+            ;
+        }
+        targetCfgs[cfg.channel * stage.bandCount + cfg.band] = cfg;
+    }
+    return ret;
+}
+
+RetCode DynamicsProcessingSwContext::setPreEqBandCfgs(
+        const std::vector<DynamicsProcessing::EqBandConfig>& cfgs) {
+    return setEqBandCfgs(cfgs, mPreEqChBands, mEngineSettings.preEqStage, mPreEqChCfgs);
+}
+
+RetCode DynamicsProcessingSwContext::setPostEqBandCfgs(
+        const std::vector<DynamicsProcessing::EqBandConfig>& cfgs) {
+    return setEqBandCfgs(cfgs, mPostEqChBands, mEngineSettings.postEqStage, mPostEqChCfgs);
+}
+
+RetCode DynamicsProcessingSwContext::setMbcBandCfgs(
+        const std::vector<DynamicsProcessing::MbcBandConfig>& cfgs) {
+    RETURN_VALUE_IF(!mEngineSettings.mbcStage.inUse, RetCode::ERROR_ILLEGAL_PARAMETER,
+                    "mbcNotInUse");
+
+    RetCode ret = RetCode::SUCCESS;
+    std::set<std::pair<int /* channel */, int /* band */>> bandSet;
+
+    int bandCount = mEngineSettings.mbcStage.bandCount;
+    std::vector<bool> filled(mChannelCount * bandCount, false);
+    for (auto& it : cfgs) {
+        if (0 != bandSet.count({it.channel, it.band})) {
+            LOG(WARNING) << __func__ << " duplicated band " << it.toString();
+            ret = RetCode::ERROR_ILLEGAL_PARAMETER;
+        } else {
+            bandSet.insert({it.channel, it.band});
+        }
+        if (!validateMbcBandConfig(it, mChannelCount, mEngineSettings.mbcStage.bandCount,
+                                   mMbcChCfgs)) {
+            LOG(WARNING) << __func__ << " skip invalid band " << it.toString();
+            ret = RetCode::ERROR_ILLEGAL_PARAMETER;
+            continue;
+            ;
+        }
+        mMbcChBands[it.channel * bandCount + it.band] = it;
+    }
+    return ret;
+}
+
+RetCode DynamicsProcessingSwContext::setLimiterCfgs(
+        const std::vector<DynamicsProcessing::LimiterConfig>& cfgs) {
+    RETURN_VALUE_IF(!mEngineSettings.limiterInUse, RetCode::ERROR_ILLEGAL_PARAMETER,
+                    "limiterNotInUse");
+
+    RetCode ret = RetCode::SUCCESS;
+    std::unordered_set<int> channelSet;
+
+    for (auto& it : cfgs) {
+        if (0 != channelSet.count(it.channel)) {
+            LOG(WARNING) << __func__ << " duplicated channel " << it.channel;
+            ret = RetCode::ERROR_ILLEGAL_PARAMETER;
+        } else {
+            channelSet.insert(it.channel);
+        }
+        if (!validateLimiterConfig(it, mChannelCount)) {
+            LOG(WARNING) << __func__ << " skip invalid limiter " << it.toString();
+            ret = RetCode::ERROR_ILLEGAL_PARAMETER;
+            continue;
+        }
+        mLimiterCfgs[it.channel] = it;
+    }
+    return ret;
+}
+
+void DynamicsProcessingSwContext::resizeChannels() {
+    if (mPreEqChCfgs.size() != mChannelCount) {
+        mPreEqChCfgs.resize(mChannelCount, {.channel = kInvalidChannelId});
+    }
+    if (mPostEqChCfgs.size() != mChannelCount) {
+        mPostEqChCfgs.resize(mChannelCount, {.channel = kInvalidChannelId});
+    }
+    if (mMbcChCfgs.size() != mChannelCount) {
+        mMbcChCfgs.resize(mChannelCount, {.channel = kInvalidChannelId});
+    }
+    if (mLimiterCfgs.size() != mChannelCount) {
+        mLimiterCfgs.resize(mChannelCount, {.channel = kInvalidChannelId});
+    }
+    if (mInputGainCfgs.size() != mChannelCount) {
+        mInputGainCfgs.resize(mChannelCount, {.channel = kInvalidChannelId});
+    }
+}
+
+void DynamicsProcessingSwContext::resizeBands() {
+    if (mPreEqChBands.size() != (size_t)(mChannelCount * mEngineSettings.preEqStage.bandCount)) {
+        mPreEqChBands.resize(mChannelCount * mEngineSettings.preEqStage.bandCount,
+                             {.channel = kInvalidChannelId});
+    }
+    if (mPostEqChBands.size() != (size_t)(mChannelCount * mEngineSettings.postEqStage.bandCount)) {
+        mPostEqChBands.resize(mChannelCount * mEngineSettings.postEqStage.bandCount,
+                              {.channel = kInvalidChannelId});
+    }
+    if (mMbcChBands.size() != (size_t)(mChannelCount * mEngineSettings.mbcStage.bandCount)) {
+        mMbcChBands.resize(mChannelCount * mEngineSettings.mbcStage.bandCount,
+                           {.channel = kInvalidChannelId});
+    }
+}
+
+RetCode DynamicsProcessingSwContext::setInputGainCfgs(
+        const std::vector<DynamicsProcessing::InputGain>& cfgs) {
+    for (const auto& cfg : cfgs) {
+        RETURN_VALUE_IF(cfg.channel < 0 || (size_t)cfg.channel >= mChannelCount,
+                        RetCode::ERROR_ILLEGAL_PARAMETER, "invalidChannel");
+        mInputGainCfgs[cfg.channel] = cfg;
+    }
+    return RetCode::SUCCESS;
+}
+
+std::vector<DynamicsProcessing::InputGain> DynamicsProcessingSwContext::getInputGainCfgs() {
+    std::vector<DynamicsProcessing::InputGain> ret;
+    std::copy_if(mInputGainCfgs.begin(), mInputGainCfgs.end(), std::back_inserter(ret),
+                 [&](const auto& gain) { return gain.channel != kInvalidChannelId; });
+    return ret;
+}
+
+bool DynamicsProcessingSwContext::validateCutoffFrequency(float freq) {
+    return freq >= DynamicsProcessingSw::kCapability.minCutOffFreq &&
+           freq <= DynamicsProcessingSw::kCapability.maxCutOffFreq;
+}
+
+bool DynamicsProcessingSwContext::validateStageEnablement(
+        const DynamicsProcessing::StageEnablement& enablement) {
+    return !enablement.inUse || (enablement.inUse && enablement.bandCount > 0);
+}
+
+bool DynamicsProcessingSwContext::validateEngineConfig(
+        const DynamicsProcessing::EngineArchitecture& engine) {
+    return engine.preferredProcessingDurationMs >= 0 &&
+           validateStageEnablement(engine.preEqStage) &&
+           validateStageEnablement(engine.postEqStage) && validateStageEnablement(engine.mbcStage);
+}
+
+bool DynamicsProcessingSwContext::validateEqBandConfig(
+        const DynamicsProcessing::EqBandConfig& band, int maxChannel, int maxBand,
+        const std::vector<DynamicsProcessing::ChannelConfig>& channelConfig) {
+    return band.channel >= 0 && band.channel < maxChannel &&
+           (size_t)band.channel < channelConfig.size() && channelConfig[band.channel].enable &&
+           band.band >= 0 && band.band < maxBand && validateCutoffFrequency(band.cutoffFrequencyHz);
+}
+
+bool DynamicsProcessingSwContext::validateMbcBandConfig(
+        const DynamicsProcessing::MbcBandConfig& band, int maxChannel, int maxBand,
+        const std::vector<DynamicsProcessing::ChannelConfig>& channelConfig) {
+    return band.channel >= 0 && band.channel < maxChannel &&
+           (size_t)band.channel < channelConfig.size() && channelConfig[band.channel].enable &&
+           band.band >= 0 && band.band < maxBand &&
+           validateCutoffFrequency(band.cutoffFrequencyHz) && band.attackTimeMs >= 0 &&
+           band.releaseTimeMs >= 0 && band.ratio >= 0 && band.thresholdDb <= 0 &&
+           band.kneeWidthDb <= 0 && band.noiseGateThresholdDb <= 0 && band.expanderRatio >= 0;
+}
+
+bool DynamicsProcessingSwContext::validateLimiterConfig(
+        const DynamicsProcessing::LimiterConfig& limiter, int maxChannel) {
+    return limiter.channel >= 0 && limiter.channel < maxChannel && limiter.attackTimeMs >= 0 &&
+           limiter.releaseTimeMs >= 0 && limiter.ratio >= 0 && limiter.thresholdDb <= 0;
+}
+
 }  // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/dynamicProcessing/DynamicsProcessingSw.h b/audio/aidl/default/dynamicProcessing/DynamicsProcessingSw.h
index 2ae46c5..e336df7 100644
--- a/audio/aidl/default/dynamicProcessing/DynamicsProcessingSw.h
+++ b/audio/aidl/default/dynamicProcessing/DynamicsProcessingSw.h
@@ -29,11 +29,77 @@
 class DynamicsProcessingSwContext final : public EffectContext {
   public:
     DynamicsProcessingSwContext(int statusDepth, const Parameter::Common& common)
-        : EffectContext(statusDepth, common) {
+        : EffectContext(statusDepth, common),
+          mChannelCount(::android::hardware::audio::common::getChannelCount(
+                  common.input.base.channelMask)),
+          mPreEqChCfgs(mChannelCount, {.channel = kInvalidChannelId}),
+          mPostEqChCfgs(mChannelCount, {.channel = kInvalidChannelId}),
+          mMbcChCfgs(mChannelCount, {.channel = kInvalidChannelId}),
+          mLimiterCfgs(mChannelCount, {.channel = kInvalidChannelId}) {
         LOG(DEBUG) << __func__;
     }
-    // TODO: add specific context here
-};
+
+    // utils
+    RetCode setChannelCfgs(const std::vector<DynamicsProcessing::ChannelConfig>& cfgs,
+                           std::vector<DynamicsProcessing::ChannelConfig>& targetCfgs,
+                           const DynamicsProcessing::StageEnablement& engineSetting);
+
+    RetCode setEqBandCfgs(const std::vector<DynamicsProcessing::EqBandConfig>& cfgs,
+                          std::vector<DynamicsProcessing::EqBandConfig>& targetCfgs,
+                          const DynamicsProcessing::StageEnablement& stage,
+                          const std::vector<DynamicsProcessing::ChannelConfig>& channelConfig);
+
+    // set params
+    RetCode setCommon(const Parameter::Common& common) override;
+    RetCode setEngineArchitecture(const DynamicsProcessing::EngineArchitecture& cfg);
+    RetCode setPreEqChannelCfgs(const std::vector<DynamicsProcessing::ChannelConfig>& cfgs);
+    RetCode setPostEqChannelCfgs(const std::vector<DynamicsProcessing::ChannelConfig>& cfgs);
+    RetCode setMbcChannelCfgs(const std::vector<DynamicsProcessing::ChannelConfig>& cfgs);
+    RetCode setPreEqBandCfgs(const std::vector<DynamicsProcessing::EqBandConfig>& cfgs);
+    RetCode setPostEqBandCfgs(const std::vector<DynamicsProcessing::EqBandConfig>& cfgs);
+    RetCode setMbcBandCfgs(const std::vector<DynamicsProcessing::MbcBandConfig>& cfgs);
+    RetCode setLimiterCfgs(const std::vector<DynamicsProcessing::LimiterConfig>& cfgs);
+    RetCode setInputGainCfgs(const std::vector<DynamicsProcessing::InputGain>& cfgs);
+
+    // get params
+    DynamicsProcessing::EngineArchitecture getEngineArchitecture() { return mEngineSettings; }
+    std::vector<DynamicsProcessing::ChannelConfig> getPreEqChannelCfgs() { return mPreEqChCfgs; }
+    std::vector<DynamicsProcessing::ChannelConfig> getPostEqChannelCfgs() { return mPostEqChCfgs; }
+    std::vector<DynamicsProcessing::ChannelConfig> getMbcChannelCfgs() { return mMbcChCfgs; }
+    std::vector<DynamicsProcessing::EqBandConfig> getPreEqBandCfgs() { return mPreEqChBands; }
+    std::vector<DynamicsProcessing::EqBandConfig> getPostEqBandCfgs() { return mPostEqChBands; }
+    std::vector<DynamicsProcessing::MbcBandConfig> getMbcBandCfgs() { return mMbcChBands; }
+    std::vector<DynamicsProcessing::LimiterConfig> getLimiterCfgs() { return mLimiterCfgs; }
+    std::vector<DynamicsProcessing::InputGain> getInputGainCfgs();
+
+  private:
+    static constexpr int32_t kInvalidChannelId = -1;
+    size_t mChannelCount = 0;
+    DynamicsProcessing::EngineArchitecture mEngineSettings;
+    // Channel config vector with size of mChannelCount
+    std::vector<DynamicsProcessing::ChannelConfig> mPreEqChCfgs;
+    std::vector<DynamicsProcessing::ChannelConfig> mPostEqChCfgs;
+    std::vector<DynamicsProcessing::ChannelConfig> mMbcChCfgs;
+    std::vector<DynamicsProcessing::LimiterConfig> mLimiterCfgs;
+    std::vector<DynamicsProcessing::InputGain> mInputGainCfgs;
+    // Band config vector with size of mChannelCount * bandCount
+    std::vector<DynamicsProcessing::EqBandConfig> mPreEqChBands;
+    std::vector<DynamicsProcessing::EqBandConfig> mPostEqChBands;
+    std::vector<DynamicsProcessing::MbcBandConfig> mMbcChBands;
+
+    bool validateCutoffFrequency(float freq);
+    bool validateStageEnablement(const DynamicsProcessing::StageEnablement& enablement);
+    bool validateEngineConfig(const DynamicsProcessing::EngineArchitecture& engine);
+    bool validateEqBandConfig(const DynamicsProcessing::EqBandConfig& band, int maxChannel,
+                              int maxBand,
+                              const std::vector<DynamicsProcessing::ChannelConfig>& channelConfig);
+    bool validateMbcBandConfig(const DynamicsProcessing::MbcBandConfig& band, int maxChannel,
+                               int maxBand,
+                               const std::vector<DynamicsProcessing::ChannelConfig>& channelConfig);
+    bool validateLimiterConfig(const DynamicsProcessing::LimiterConfig& limiter, int maxChannel);
+    void resizeChannels();
+    void resizeBands();
+};  // DynamicsProcessingSwContext
 
 class DynamicsProcessingSw final : public EffectImpl {
   public:
@@ -60,7 +126,9 @@
 
   private:
     std::shared_ptr<DynamicsProcessingSwContext> mContext;
-    /* parameters */
-    DynamicsProcessing mSpecificParam;
-};
+    ndk::ScopedAStatus getParameterDynamicsProcessing(const DynamicsProcessing::Tag& tag,
+                                                      Parameter::Specific* specific);
+
+};  // DynamicsProcessingSw
+
 }  // namespace aidl::android::hardware::audio::effect
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index e9f43d8..000a704 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -36,6 +36,13 @@
     explicit Module(Type type) : mType(type) {}
 
   private:
+    struct VendorDebug {
+        static const std::string kForceTransientBurstName;
+        static const std::string kForceSynchronousDrainName;
+        bool forceTransientBurst = false;
+        bool forceSynchronousDrain = false;
+    };
+
     ndk::ScopedAStatus setModuleDebug(
             const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) override;
     ndk::ScopedAStatus getTelephony(std::shared_ptr<ITelephony>* _aidl_return) override;
@@ -128,6 +135,7 @@
     const Type mType;
     std::unique_ptr<internal::Configuration> mConfig;
     ModuleDebug mDebug;
+    VendorDebug mVendorDebug;
     // For the interfaces requiring to return the same instance, we need to hold them
     // via a strong pointer. The binder token is retained for a call to 'setMinSchedulerPolicy'.
     std::shared_ptr<ITelephony> mTelephony;
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index 5abd4de..2cf5951 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -62,12 +62,21 @@
     // Ensure that this value is not used by any of StreamDescriptor.State enums
     static constexpr int32_t STATE_CLOSED = -1;
 
+    struct DebugParameters {
+        // An extra delay for transient states, in ms.
+        int transientStateDelayMs = 0;
+        // Force the "burst" command to move the SM to the TRANSFERRING state.
+        bool forceTransientBurst = false;
+        // Force the "drain" command to be synchronous, going directly to the IDLE state.
+        bool forceSynchronousDrain = false;
+    };
+
     StreamContext() = default;
     StreamContext(std::unique_ptr<CommandMQ> commandMQ, std::unique_ptr<ReplyMQ> replyMQ,
                   const ::aidl::android::media::audio::common::AudioFormatDescription& format,
                   const ::aidl::android::media::audio::common::AudioChannelLayout& channelLayout,
                   std::unique_ptr<DataMQ> dataMQ, std::shared_ptr<IStreamCallback> asyncCallback,
-                  int transientStateDelayMs)
+                  DebugParameters debugParameters)
         : mCommandMQ(std::move(commandMQ)),
           mInternalCommandCookie(std::rand()),
           mReplyMQ(std::move(replyMQ)),
@@ -75,7 +84,7 @@
           mChannelLayout(channelLayout),
           mDataMQ(std::move(dataMQ)),
           mAsyncCallback(asyncCallback),
-          mTransientStateDelayMs(transientStateDelayMs) {}
+          mDebugParameters(debugParameters) {}
     StreamContext(StreamContext&& other)
         : mCommandMQ(std::move(other.mCommandMQ)),
           mInternalCommandCookie(other.mInternalCommandCookie),
@@ -83,8 +92,8 @@
           mFormat(other.mFormat),
           mChannelLayout(other.mChannelLayout),
           mDataMQ(std::move(other.mDataMQ)),
-          mAsyncCallback(other.mAsyncCallback),
-          mTransientStateDelayMs(other.mTransientStateDelayMs) {}
+          mAsyncCallback(std::move(other.mAsyncCallback)),
+          mDebugParameters(std::move(other.mDebugParameters)) {}
     StreamContext& operator=(StreamContext&& other) {
         mCommandMQ = std::move(other.mCommandMQ);
         mInternalCommandCookie = other.mInternalCommandCookie;
@@ -92,8 +101,8 @@
         mFormat = std::move(other.mFormat);
         mChannelLayout = std::move(other.mChannelLayout);
         mDataMQ = std::move(other.mDataMQ);
-        mAsyncCallback = other.mAsyncCallback;
-        mTransientStateDelayMs = other.mTransientStateDelayMs;
+        mAsyncCallback = std::move(other.mAsyncCallback);
+        mDebugParameters = std::move(other.mDebugParameters);
         return *this;
     }
 
@@ -107,10 +116,12 @@
     ::aidl::android::media::audio::common::AudioFormatDescription getFormat() const {
         return mFormat;
     }
+    bool getForceTransientBurst() const { return mDebugParameters.forceTransientBurst; }
+    bool getForceSynchronousDrain() const { return mDebugParameters.forceSynchronousDrain; }
     size_t getFrameSize() const;
     int getInternalCommandCookie() const { return mInternalCommandCookie; }
     ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
-    int getTransientStateDelayMs() const { return mTransientStateDelayMs; }
+    int getTransientStateDelayMs() const { return mDebugParameters.transientStateDelayMs; }
     bool isValid() const;
     void reset();
 
@@ -122,7 +133,7 @@
     ::aidl::android::media::audio::common::AudioChannelLayout mChannelLayout;
     std::unique_ptr<DataMQ> mDataMQ;
     std::shared_ptr<IStreamCallback> mAsyncCallback;
-    int mTransientStateDelayMs;
+    DebugParameters mDebugParameters;
 };
 
 class StreamWorkerCommonLogic : public ::android::hardware::audio::common::StreamLogic {
@@ -141,7 +152,9 @@
           mReplyMQ(context.getReplyMQ()),
           mDataMQ(context.getDataMQ()),
           mAsyncCallback(context.getAsyncCallback()),
-          mTransientStateDelayMs(context.getTransientStateDelayMs()) {}
+          mTransientStateDelayMs(context.getTransientStateDelayMs()),
+          mForceTransientBurst(context.getForceTransientBurst()),
+          mForceSynchronousDrain(context.getForceSynchronousDrain()) {}
     std::string init() override;
     void populateReply(StreamDescriptor::Reply* reply, bool isConnected) const;
     void populateReplyWrongState(StreamDescriptor::Reply* reply,
@@ -164,6 +177,8 @@
     std::shared_ptr<IStreamCallback> mAsyncCallback;
     const std::chrono::duration<int, std::milli> mTransientStateDelayMs;
     std::chrono::time_point<std::chrono::steady_clock> mTransientStateStart;
+    const bool mForceTransientBurst;
+    const bool mForceSynchronousDrain;
     // We use an array and the "size" field instead of a vector to be able to detect
     // memory allocation issues.
     std::unique_ptr<int8_t[]> mDataBuffer;
diff --git a/audio/aidl/default/include/effect-impl/EffectContext.h b/audio/aidl/default/include/effect-impl/EffectContext.h
index 1f39db0..a3e7ff2 100644
--- a/audio/aidl/default/include/effect-impl/EffectContext.h
+++ b/audio/aidl/default/include/effect-impl/EffectContext.h
@@ -119,11 +119,11 @@
 
     virtual RetCode setCommon(const Parameter::Common& common) {
         mCommon = common;
-        LOG(ERROR) << __func__ << mCommon.toString();
+        LOG(INFO) << __func__ << mCommon.toString();
         return RetCode::SUCCESS;
     }
     virtual Parameter::Common getCommon() {
-        LOG(ERROR) << __func__ << mCommon.toString();
+        LOG(INFO) << __func__ << mCommon.toString();
         return mCommon;
     }
 
diff --git a/audio/aidl/default/include/effect-impl/EffectTypes.h b/audio/aidl/default/include/effect-impl/EffectTypes.h
index 58c8672..b100a2e 100644
--- a/audio/aidl/default/include/effect-impl/EffectTypes.h
+++ b/audio/aidl/default/include/effect-impl/EffectTypes.h
@@ -98,12 +98,13 @@
         }                                                                              \
     } while (0)
 
-#define RETURN_VALUE_IF(expr, ret, log)                                                          \
-    do {                                                                                         \
-        if (expr) {                                                                              \
-            LOG(ERROR) << __func__ << ":" << __LINE__ << " return with expr " << #expr << (log); \
-            return ret;                                                                          \
-        }                                                                                        \
+#define RETURN_VALUE_IF(expr, ret, log)                                                  \
+    do {                                                                                 \
+        if (expr) {                                                                      \
+            LOG(ERROR) << __func__ << ":" << __LINE__ << " return with expr \"" << #expr \
+                       << "\":" << (log);                                                \
+            return ret;                                                                  \
+        }                                                                                \
     } while (0)
 
 #define RETURN_IF_BINDER_EXCEPTION(functor)                                 \
diff --git a/audio/aidl/default/include/effect-impl/EffectUUID.h b/audio/aidl/default/include/effect-impl/EffectUUID.h
index 6eec29e..1a60829 100644
--- a/audio/aidl/default/include/effect-impl/EffectUUID.h
+++ b/audio/aidl/default/include/effect-impl/EffectUUID.h
@@ -135,6 +135,12 @@
                                                         0x11ed,
                                                         0x9b6a,
                                                         {0x02, 0x42, 0xac, 0x12, 0x00, 0x02}};
+// e0e6539b-1781-7261-676f-6d7573696340
+static const AudioUuid kDynamicsProcessingImplUUID = {static_cast<int32_t>(0xe0e6539b),
+                                                      0x1781,
+                                                      0x7261,
+                                                      0x676f,
+                                                      {0x6d, 0x75, 0x73, 0x69, 0x63, 0x40}};
 // 1411e6d6-aecd-4021-a1cf-a6aceb0d71e5
 static const AudioUuid kHapticGeneratorTypeUUID = {static_cast<int32_t>(0x1411e6d6),
                                                    0xaecd,
diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp
index becdf1b..f9d12dd 100644
--- a/audio/aidl/vts/Android.bp
+++ b/audio/aidl/vts/Android.bp
@@ -81,6 +81,12 @@
 }
 
 cc_test {
+    name: "VtsHalDynamicsProcessingTargetTest",
+    defaults: ["VtsHalAudioTargetTestDefaults"],
+    srcs: ["VtsHalDynamicsProcessingTest.cpp"],
+}
+
+cc_test {
     name: "VtsHalEnvironmentalReverbTargetTest",
     defaults: ["VtsHalAudioTargetTestDefaults"],
     srcs: ["VtsHalEnvironmentalReverbTargetTest.cpp"],
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 8da475e..d4f2811 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -18,6 +18,7 @@
 #include <chrono>
 #include <cmath>
 #include <condition_variable>
+#include <forward_list>
 #include <limits>
 #include <memory>
 #include <mutex>
@@ -85,6 +86,7 @@
 using aidl::android::media::audio::common::AudioPortExt;
 using aidl::android::media::audio::common::AudioSource;
 using aidl::android::media::audio::common::AudioUsage;
+using aidl::android::media::audio::common::Boolean;
 using aidl::android::media::audio::common::Float;
 using aidl::android::media::audio::common::Int;
 using aidl::android::media::audio::common::Void;
@@ -126,7 +128,7 @@
         return WithDebugFlags(parent.mFlags);
     }
 
-    WithDebugFlags() {}
+    WithDebugFlags() = default;
     explicit WithDebugFlags(const ModuleDebug& initial) : mInitial(initial), mFlags(initial) {}
     WithDebugFlags(const WithDebugFlags&) = delete;
     WithDebugFlags& operator=(const WithDebugFlags&) = delete;
@@ -135,7 +137,10 @@
             EXPECT_IS_OK(mModule->setModuleDebug(mInitial));
         }
     }
-    void SetUp(IModule* module) { ASSERT_IS_OK(module->setModuleDebug(mFlags)); }
+    void SetUp(IModule* module) {
+        ASSERT_IS_OK(module->setModuleDebug(mFlags));
+        mModule = module;
+    }
     ModuleDebug& flags() { return mFlags; }
 
   private:
@@ -144,13 +149,65 @@
     IModule* mModule = nullptr;
 };
 
+template <typename T>
+class WithModuleParameter {
+  public:
+    WithModuleParameter(const std::string parameterId, const T& value)
+        : mParameterId(parameterId), mValue(value) {}
+    WithModuleParameter(const WithModuleParameter&) = delete;
+    WithModuleParameter& operator=(const WithModuleParameter&) = delete;
+    ~WithModuleParameter() {
+        if (mModule != nullptr) {
+            VendorParameter parameter{.id = mParameterId};
+            parameter.ext.setParcelable(mInitial);
+            EXPECT_IS_OK(mModule->setVendorParameters({parameter}, false));
+        }
+    }
+    ScopedAStatus SetUpNoChecks(IModule* module, bool failureExpected) {
+        std::vector<VendorParameter> parameters;
+        ScopedAStatus result = module->getVendorParameters({mParameterId}, &parameters);
+        if (result.isOk() && parameters.size() == 1) {
+            std::optional<T> maybeInitial;
+            binder_status_t status = parameters[0].ext.getParcelable(&maybeInitial);
+            if (status == STATUS_OK && maybeInitial.has_value()) {
+                mInitial = maybeInitial.value();
+                VendorParameter parameter{.id = mParameterId};
+                parameter.ext.setParcelable(mValue);
+                result = module->setVendorParameters({parameter}, false);
+                if (result.isOk()) {
+                    LOG(INFO) << __func__ << ": overriding parameter \"" << mParameterId
+                              << "\" with " << mValue.toString()
+                              << ", old value: " << mInitial.toString();
+                    mModule = module;
+                }
+            } else {
+                LOG(ERROR) << __func__ << ": error while retrieving the value of \"" << mParameterId
+                           << "\"";
+                return ScopedAStatus::fromStatus(status);
+            }
+        }
+        if (!result.isOk()) {
+            LOG(failureExpected ? INFO : ERROR)
+                    << __func__ << ": can not override vendor parameter \"" << mParameterId << "\""
+                    << result;
+        }
+        return result;
+    }
+
+  private:
+    const std::string mParameterId;
+    const T mValue;
+    IModule* mModule = nullptr;
+    T mInitial;
+};
+
 // For consistency, WithAudioPortConfig can start both with a non-existent
 // port config, and with an existing one. Existence is determined by the
 // id of the provided config. If it's not 0, then WithAudioPortConfig is
 // essentially a no-op wrapper.
 class WithAudioPortConfig {
   public:
-    WithAudioPortConfig() {}
+    WithAudioPortConfig() = default;
     explicit WithAudioPortConfig(const AudioPortConfig& config) : mInitialConfig(config) {}
     WithAudioPortConfig(const WithAudioPortConfig&) = delete;
     WithAudioPortConfig& operator=(const WithAudioPortConfig&) = delete;
@@ -302,26 +359,31 @@
 
     void SetUpImpl(const std::string& moduleName) {
         ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName));
-        debug.flags().simulateDeviceConnections = true;
-        ASSERT_NO_FATAL_FAILURE(debug.SetUp(module.get()));
     }
 
-    void TearDownImpl() {
-        if (module != nullptr) {
-            EXPECT_IS_OK(module->setModuleDebug(ModuleDebug{}));
-        }
-    }
+    void TearDownImpl() { debug.reset(); }
 
     void ConnectToService(const std::string& moduleName) {
+        ASSERT_EQ(module, nullptr);
+        ASSERT_EQ(debug, nullptr);
         module = IModule::fromBinder(binderUtil.connectToService(moduleName));
         ASSERT_NE(module, nullptr);
+        ASSERT_NO_FATAL_FAILURE(SetUpDebug());
     }
 
     void RestartService() {
         ASSERT_NE(module, nullptr);
         moduleConfig.reset();
+        debug.reset();
         module = IModule::fromBinder(binderUtil.restartService());
         ASSERT_NE(module, nullptr);
+        ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+    }
+
+    void SetUpDebug() {
+        debug.reset(new WithDebugFlags());
+        debug->flags().simulateDeviceConnections = true;
+        ASSERT_NO_FATAL_FAILURE(debug->SetUp(module.get()));
     }
 
     void ApplyEveryConfig(const std::vector<AudioPortConfig>& configs) {
@@ -390,7 +452,7 @@
     std::shared_ptr<IModule> module;
     std::unique_ptr<ModuleConfig> moduleConfig;
     AudioHalBinderServiceUtil binderUtil;
-    WithDebugFlags debug;
+    std::unique_ptr<WithDebugFlags> debug;
 };
 
 class AudioCoreModule : public AudioCoreModuleBase, public testing::TestWithParam<std::string> {
@@ -465,6 +527,7 @@
     size_t getBufferSizeFrames() const { return mBufferSizeFrames; }
     CommandMQ* getCommandMQ() const { return mCommandMQ.get(); }
     DataMQ* getDataMQ() const { return mDataMQ.get(); }
+    size_t getFrameSizeBytes() const { return mFrameSizeBytes; }
     ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
 
   private:
@@ -504,10 +567,48 @@
     return std::to_string(static_cast<int32_t>(event));
 }
 
+// Note: we use a reference wrapper, not a pointer, because methods of std::*list
+// return references to inserted elements. This way, we can put a returned reference
+// into the children vector without any type conversions, and this makes DAG creation
+// code more clear.
+template <typename T>
+struct DagNode : public std::pair<T, std::vector<std::reference_wrapper<DagNode<T>>>> {
+    using Children = std::vector<std::reference_wrapper<DagNode>>;
+    DagNode(const T& t, const Children& c) : std::pair<T, Children>(t, c) {}
+    DagNode(T&& t, Children&& c) : std::pair<T, Children>(std::move(t), std::move(c)) {}
+    const T& datum() const { return this->first; }
+    Children& children() { return this->second; }
+    const Children& children() const { return this->second; }
+};
+// Since DagNodes do contain references to next nodes, node links provided
+// by the list are not used. Thus, the order of the nodes in the list is not
+// important, except that the starting node must be at the front of the list,
+// which means, it must always be added last.
+template <typename T>
+struct Dag : public std::forward_list<DagNode<T>> {
+    Dag() = default;
+    // We prohibit copying and moving Dag instances because implementing that
+    // is not trivial due to references between nodes.
+    Dag(const Dag&) = delete;
+    Dag(Dag&&) = delete;
+    Dag& operator=(const Dag&) = delete;
+    Dag& operator=(Dag&&) = delete;
+};
+
 // Transition to the next state happens either due to a command from the client,
 // or after an event received from the server.
 using TransitionTrigger = std::variant<StreamDescriptor::Command, StreamEventReceiver::Event>;
-using StateTransition = std::pair<TransitionTrigger, StreamDescriptor::State>;
+std::string toString(const TransitionTrigger& trigger) {
+    if (std::holds_alternative<StreamDescriptor::Command>(trigger)) {
+        return std::string("'")
+                .append(toString(std::get<StreamDescriptor::Command>(trigger).getTag()))
+                .append("' command");
+    }
+    return std::string("'")
+            .append(toString(std::get<StreamEventReceiver::Event>(trigger)))
+            .append("' event");
+}
+
 struct StateSequence {
     virtual ~StateSequence() = default;
     virtual void rewind() = 0;
@@ -517,6 +618,10 @@
     virtual void advance(StreamDescriptor::State state) = 0;
 };
 
+// Defines the current state and the trigger to transfer to the next one,
+// thus "state" is the "from" state.
+using StateTransitionFrom = std::pair<StreamDescriptor::State, TransitionTrigger>;
+
 static const StreamDescriptor::Command kGetStatusCommand =
         StreamDescriptor::Command::make<StreamDescriptor::Command::Tag::getStatus>(Void{});
 static const StreamDescriptor::Command kStartCommand =
@@ -542,66 +647,65 @@
         StreamEventReceiver::Event::TransferReady;
 static const StreamEventReceiver::Event kDrainReadyEvent = StreamEventReceiver::Event::DrainReady;
 
-// Handle possible bifurcations:
-//   - on burst and on start: 'TRANSFERRING' -> {'ACTIVE', 'TRANSFERRING'}
-//   - on pause: 'TRANSFER_PAUSED' -> {'PAUSED', 'TRANSFER_PAUSED'}
-// It is assumed that the 'steps' provided on the construction contain the sequence
-// for the async case, which gets corrected in the case when the HAL decided to do
-// a synchronous transfer.
-class SmartStateSequence : public StateSequence {
+struct StateDag : public Dag<StateTransitionFrom> {
+    using Node = StateDag::reference;
+    using NextStates = StateDag::value_type::Children;
+
+    template <typename... Next>
+    Node makeNode(StreamDescriptor::State s, TransitionTrigger t, Next&&... next) {
+        return emplace_front(std::make_pair(s, t), NextStates{std::forward<Next>(next)...});
+    }
+    Node makeNodes(const std::vector<StateTransitionFrom>& v, Node last) {
+        auto helper = [&](auto i, auto&& h) -> Node {
+            if (i == v.end()) return last;
+            return makeNode(i->first, i->second, h(++i, h));
+        };
+        return helper(v.begin(), helper);
+    }
+    Node makeNodes(const std::vector<StateTransitionFrom>& v, StreamDescriptor::State f) {
+        return makeNodes(v, makeFinalNode(f));
+    }
+    Node makeFinalNode(StreamDescriptor::State s) {
+        // The actual command used here is irrelevant. Since it's the final node
+        // in the test sequence, no commands sent after reaching it.
+        return emplace_front(std::make_pair(s, kGetStatusCommand), NextStates{});
+    }
+};
+
+class StateSequenceFollower : public StateSequence {
   public:
-    explicit SmartStateSequence(const std::vector<StateTransition>& steps) : mSteps(steps) {}
-    explicit SmartStateSequence(std::vector<StateTransition>&& steps) : mSteps(std::move(steps)) {}
-    void rewind() override { mCurrentStep = 0; }
-    bool done() const override { return mCurrentStep >= mSteps.size(); }
-    TransitionTrigger getTrigger() override { return mSteps[mCurrentStep].first; }
+    explicit StateSequenceFollower(std::unique_ptr<StateDag> steps)
+        : mSteps(std::move(steps)), mCurrent(mSteps->front()) {}
+    void rewind() override { mCurrent = mSteps->front(); }
+    bool done() const override { return current().children().empty(); }
+    TransitionTrigger getTrigger() override { return current().datum().second; }
     std::set<StreamDescriptor::State> getExpectedStates() override {
-        std::set<StreamDescriptor::State> result = {getState()};
-        if (isBurstBifurcation() || isStartBifurcation()) {
-            result.insert(StreamDescriptor::State::ACTIVE);
-        } else if (isPauseBifurcation()) {
-            result.insert(StreamDescriptor::State::PAUSED);
-        }
+        std::set<StreamDescriptor::State> result;
+        std::transform(current().children().cbegin(), current().children().cend(),
+                       std::inserter(result, result.begin()),
+                       [](const auto& node) { return node.get().datum().first; });
+        LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(result);
         return result;
     }
     void advance(StreamDescriptor::State state) override {
-        if (isBurstBifurcation() && state == StreamDescriptor::State::ACTIVE &&
-            mCurrentStep + 1 < mSteps.size() &&
-            mSteps[mCurrentStep + 1].first == TransitionTrigger{kTransferReadyEvent}) {
-            mCurrentStep++;
+        if (auto it = std::find_if(
+                    current().children().cbegin(), current().children().cend(),
+                    [&](const auto& node) { return node.get().datum().first == state; });
+            it != current().children().cend()) {
+            LOG(DEBUG) << __func__ << ": " << toString(mCurrent.get().datum().first) << " -> "
+                       << toString(it->get().datum().first);
+            mCurrent = *it;
+        } else {
+            LOG(FATAL) << __func__ << ": state " << toString(state) << " is unexpected";
         }
-        mCurrentStep++;
     }
 
   private:
-    StreamDescriptor::State getState() const { return mSteps[mCurrentStep].second; }
-    bool isBurstBifurcation() {
-        return getTrigger() == TransitionTrigger{kBurstCommand} &&
-               getState() == StreamDescriptor::State::TRANSFERRING;
-    }
-    bool isPauseBifurcation() {
-        return getTrigger() == TransitionTrigger{kPauseCommand} &&
-               getState() == StreamDescriptor::State::TRANSFER_PAUSED;
-    }
-    bool isStartBifurcation() {
-        return getTrigger() == TransitionTrigger{kStartCommand} &&
-               getState() == StreamDescriptor::State::TRANSFERRING;
-    }
-    const std::vector<StateTransition> mSteps;
-    size_t mCurrentStep = 0;
+    StateDag::const_reference current() const { return mCurrent.get(); }
+    std::unique_ptr<StateDag> mSteps;
+    std::reference_wrapper<StateDag::value_type> mCurrent;
 };
 
-std::string toString(const TransitionTrigger& trigger) {
-    if (std::holds_alternative<StreamDescriptor::Command>(trigger)) {
-        return std::string("'")
-                .append(toString(std::get<StreamDescriptor::Command>(trigger).getTag()))
-                .append("' command");
-    }
-    return std::string("'")
-            .append(toString(std::get<StreamEventReceiver::Event>(trigger)))
-            .append("' event");
-}
-
 struct StreamLogicDriver {
     virtual ~StreamLogicDriver() = default;
     // Return 'true' to stop the worker.
@@ -927,7 +1031,7 @@
         return common->close();
     }
 
-    WithStream() {}
+    WithStream() = default;
     explicit WithStream(const AudioPortConfig& portConfig) : mPortConfig(portConfig) {}
     WithStream(const WithStream&) = delete;
     WithStream& operator=(const WithStream&) = delete;
@@ -1032,7 +1136,7 @@
 
 class WithAudioPatch {
   public:
-    WithAudioPatch() {}
+    WithAudioPatch() = default;
     WithAudioPatch(const AudioPortConfig& srcPortConfig, const AudioPortConfig& sinkPortConfig)
         : mSrcPortConfig(srcPortConfig), mSinkPortConfig(sinkPortConfig) {}
     WithAudioPatch(bool sinkIsCfg1, const AudioPortConfig& portConfig1,
@@ -1473,7 +1577,7 @@
         GTEST_SKIP() << "No external devices in the module.";
     }
     AudioPort ignored;
-    WithDebugFlags doNotSimulateConnections = WithDebugFlags::createNested(debug);
+    WithDebugFlags doNotSimulateConnections = WithDebugFlags::createNested(*debug);
     doNotSimulateConnections.flags().simulateDeviceConnections = false;
     ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
     for (const auto& port : ports) {
@@ -1493,7 +1597,7 @@
     }
     WithDevicePortConnectedState portConnected(*ports.begin(), GenerateUniqueDeviceAddress());
     ASSERT_NO_FATAL_FAILURE(portConnected.SetUp(module.get()));
-    ModuleDebug midwayDebugChange = debug.flags();
+    ModuleDebug midwayDebugChange = debug->flags();
     midwayDebugChange.simulateDeviceConnections = false;
     EXPECT_STATUS(EX_ILLEGAL_STATE, module->setModuleDebug(midwayDebugChange))
             << "when trying to disable connections simulation while having a connected device";
@@ -2716,8 +2820,8 @@
 
 class StreamLogicDefaultDriver : public StreamLogicDriver {
   public:
-    explicit StreamLogicDefaultDriver(std::shared_ptr<StateSequence> commands)
-        : mCommands(commands) {
+    StreamLogicDefaultDriver(std::shared_ptr<StateSequence> commands, size_t frameSizeBytes)
+        : mCommands(commands), mFrameSizeBytes(frameSizeBytes) {
         mCommands->rewind();
     }
 
@@ -2736,7 +2840,10 @@
                 if (actualSize != nullptr) {
                     // In the output scenario, reduce slightly the fmqByteCount to verify
                     // that the HAL module always consumes all data from the MQ.
-                    if (maxDataSize > 1) maxDataSize--;
+                    if (maxDataSize > static_cast<int>(mFrameSizeBytes)) {
+                        LOG(DEBUG) << __func__ << ": reducing data size by " << mFrameSizeBytes;
+                        maxDataSize -= mFrameSizeBytes;
+                    }
                     *actualSize = maxDataSize;
                 }
                 command->set<StreamDescriptor::Command::Tag::burst>(maxDataSize);
@@ -2782,6 +2889,7 @@
 
   protected:
     std::shared_ptr<StateSequence> mCommands;
+    const size_t mFrameSizeBytes;
     std::optional<StreamDescriptor::State> mPreviousState;
     std::optional<int64_t> mPreviousFrames;
     bool mObservablePositionIncrease = false;
@@ -2830,7 +2938,7 @@
                 (!isNonBlocking && streamType == StreamTypeFilter::ASYNC)) {
                 continue;
             }
-            WithDebugFlags delayTransientStates = WithDebugFlags::createNested(debug);
+            WithDebugFlags delayTransientStates = WithDebugFlags::createNested(*debug);
             delayTransientStates.flags().streamTransientStateDelayMs =
                     std::get<NAMED_CMD_DELAY_MS>(std::get<PARAM_CMD_SEQ>(GetParam()));
             ASSERT_NO_FATAL_FAILURE(delayTransientStates.SetUp(module.get()));
@@ -2841,6 +2949,40 @@
             } else {
                 ASSERT_NO_FATAL_FAILURE(RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
             }
+            if (isNonBlocking) {
+                // Also try running the same sequence with "aosp.forceTransientBurst" set.
+                // This will only work with the default implementation. When it works, the stream
+                // tries always to move to the 'TRANSFERRING' state after a burst.
+                // This helps to check more paths for our test scenarios.
+                WithModuleParameter forceTransientBurst("aosp.forceTransientBurst", Boolean{true});
+                if (forceTransientBurst.SetUpNoChecks(module.get(), true /*failureExpected*/)
+                            .isOk()) {
+                    if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
+                        ASSERT_NO_FATAL_FAILURE(
+                                RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates));
+                    } else {
+                        ASSERT_NO_FATAL_FAILURE(
+                                RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
+                    }
+                }
+            } else if (!IOTraits<Stream>::is_input) {
+                // Also try running the same sequence with "aosp.forceSynchronousDrain" set.
+                // This will only work with the default implementation. When it works, the stream
+                // tries always to move to the 'IDLE' state after a drain.
+                // This helps to check more paths for our test scenarios.
+                WithModuleParameter forceSynchronousDrain("aosp.forceSynchronousDrain",
+                                                          Boolean{true});
+                if (forceSynchronousDrain.SetUpNoChecks(module.get(), true /*failureExpected*/)
+                            .isOk()) {
+                    if (!std::get<PARAM_SETUP_SEQ>(GetParam())) {
+                        ASSERT_NO_FATAL_FAILURE(
+                                RunStreamIoCommandsImplSeq1(portConfig, commandsAndStates));
+                    } else {
+                        ASSERT_NO_FATAL_FAILURE(
+                                RunStreamIoCommandsImplSeq2(portConfig, commandsAndStates));
+                    }
+                }
+            }
         }
     }
 
@@ -2861,7 +3003,8 @@
 
         WithStream<Stream> stream(patch.getPortConfig(IOTraits<Stream>::is_input));
         ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
-        StreamLogicDefaultDriver driver(commandsAndStates);
+        StreamLogicDefaultDriver driver(commandsAndStates,
+                                        stream.getContext()->getFrameSizeBytes());
         typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
                                                  stream.getEventReceiver());
 
@@ -2882,7 +3025,8 @@
                                      std::shared_ptr<StateSequence> commandsAndStates) {
         WithStream<Stream> stream(portConfig);
         ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
-        StreamLogicDefaultDriver driver(commandsAndStates);
+        StreamLogicDefaultDriver driver(commandsAndStates,
+                                        stream.getContext()->getFrameSizeBytes());
         typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
                                                  stream.getEventReceiver());
 
@@ -3219,38 +3363,52 @@
 
 // TODO: Add async test cases for input once it is implemented.
 
-std::shared_ptr<StateSequence> makeBurstCommands(bool isSync, size_t burstCount) {
-    const auto burst =
-            isSync ? std::vector<StateTransition>{std::make_pair(kBurstCommand,
-                                                                 StreamDescriptor::State::ACTIVE)}
-                   : std::vector<StateTransition>{
-                             std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
-                             std::make_pair(kTransferReadyEvent, StreamDescriptor::State::ACTIVE)};
-    std::vector<StateTransition> result{
-            std::make_pair(kStartCommand, StreamDescriptor::State::IDLE)};
-    for (size_t i = 0; i < burstCount; ++i) {
-        result.insert(result.end(), burst.begin(), burst.end());
+std::shared_ptr<StateSequence> makeBurstCommands(bool isSync) {
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    StateDag::Node last = d->makeFinalNode(State::ACTIVE);
+    StateDag::Node active = d->makeNode(State::ACTIVE, kBurstCommand, last);
+    StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+    if (!isSync) {
+        // Allow optional routing via the TRANSFERRING state on bursts.
+        active.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, last));
+        idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
     }
-    return std::make_shared<SmartStateSequence>(result);
+    d->makeNode(State::STANDBY, kStartCommand, idle);
+    return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kReadSeq =
-        std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true, 3));
-static const NamedCommandSequence kWriteSyncSeq = std::make_tuple(
-        std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true, 3));
-static const NamedCommandSequence kWriteAsyncSeq = std::make_tuple(
-        std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false, 3));
+        std::make_tuple(std::string("Read"), 0, StreamTypeFilter::ANY, makeBurstCommands(true));
+static const NamedCommandSequence kWriteSyncSeq =
+        std::make_tuple(std::string("Write"), 0, StreamTypeFilter::SYNC, makeBurstCommands(true));
+static const NamedCommandSequence kWriteAsyncSeq =
+        std::make_tuple(std::string("Write"), 0, StreamTypeFilter::ASYNC, makeBurstCommands(false));
 
 std::shared_ptr<StateSequence> makeAsyncDrainCommands(bool isInput) {
-    return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
-            std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-            std::make_pair(kBurstCommand, isInput ? StreamDescriptor::State::ACTIVE
-                                                  : StreamDescriptor::State::TRANSFERRING),
-            std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand,
-                           StreamDescriptor::State::DRAINING),
-            isInput ? std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE)
-                    : std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
-            std::make_pair(isInput ? kDrainInCommand : kDrainOutAllCommand,
-                           StreamDescriptor::State::DRAINING)});
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    if (isInput) {
+        d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+                      std::make_pair(State::IDLE, kBurstCommand),
+                      std::make_pair(State::ACTIVE, kDrainInCommand),
+                      std::make_pair(State::DRAINING, kStartCommand),
+                      std::make_pair(State::ACTIVE, kDrainInCommand)},
+                     State::DRAINING);
+    } else {
+        StateDag::Node draining =
+                d->makeNodes({std::make_pair(State::DRAINING, kBurstCommand),
+                              std::make_pair(State::TRANSFERRING, kDrainOutAllCommand)},
+                             State::DRAINING);
+        StateDag::Node idle =
+                d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
+                              std::make_pair(State::TRANSFERRING, kDrainOutAllCommand)},
+                             draining);
+        // If we get straight into ACTIVE on burst, no further testing is possible.
+        draining.children().push_back(d->makeFinalNode(State::ACTIVE));
+        idle.children().push_back(d->makeFinalNode(State::ACTIVE));
+        d->makeNode(State::STANDBY, kStartCommand, idle);
+    }
+    return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kWriteDrainAsyncSeq =
         std::make_tuple(std::string("WriteDrain"), kStreamTransientStateTransitionDelayMs,
@@ -3259,58 +3417,92 @@
         std::string("Drain"), 0, StreamTypeFilter::ANY, makeAsyncDrainCommands(true));
 
 std::shared_ptr<StateSequence> makeDrainOutCommands(bool isSync) {
-    return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
-            std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-            std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-            std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
-            std::make_pair(isSync ? TransitionTrigger(kGetStatusCommand)
-                                  : TransitionTrigger(kDrainReadyEvent),
-                           StreamDescriptor::State::IDLE)});
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    StateDag::Node last = d->makeFinalNode(State::IDLE);
+    StateDag::Node active = d->makeNodes(
+            {std::make_pair(State::ACTIVE, kDrainOutAllCommand),
+             std::make_pair(State::DRAINING, isSync ? TransitionTrigger(kGetStatusCommand)
+                                                    : TransitionTrigger(kDrainReadyEvent))},
+            last);
+    StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+    if (!isSync) {
+        idle.children().push_back(d->makeNode(State::TRANSFERRING, kTransferReadyEvent, active));
+    } else {
+        active.children().push_back(last);
+    }
+    d->makeNode(State::STANDBY, kStartCommand, idle);
+    return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kDrainOutSyncSeq = std::make_tuple(
         std::string("Drain"), 0, StreamTypeFilter::SYNC, makeDrainOutCommands(true));
 static const NamedCommandSequence kDrainOutAsyncSeq = std::make_tuple(
         std::string("Drain"), 0, StreamTypeFilter::ASYNC, makeDrainOutCommands(false));
 
-std::shared_ptr<StateSequence> makeDrainOutPauseCommands(bool isSync) {
-    return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
-            std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-            std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE
-                                                 : StreamDescriptor::State::TRANSFERRING),
-            std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
-            std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
-            std::make_pair(kStartCommand, StreamDescriptor::State::DRAINING),
-            std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
-            std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::PAUSED
-                                                 : StreamDescriptor::State::TRANSFER_PAUSED)});
+std::shared_ptr<StateSequence> makeDrainPauseOutCommands(bool isSync) {
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand),
+                                            std::make_pair(State::DRAIN_PAUSED, kStartCommand),
+                                            std::make_pair(State::DRAINING, kPauseCommand),
+                                            std::make_pair(State::DRAIN_PAUSED, kBurstCommand)},
+                                           isSync ? State::PAUSED : State::TRANSFER_PAUSED);
+    StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutAllCommand, draining);
+    StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+    if (!isSync) {
+        idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining));
+    } else {
+        // If we get straight into IDLE on drain, no further testing is possible.
+        active.children().push_back(d->makeFinalNode(State::IDLE));
+    }
+    d->makeNode(State::STANDBY, kStartCommand, idle);
+    return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kDrainPauseOutSyncSeq =
         std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs,
-                        StreamTypeFilter::SYNC, makeDrainOutPauseCommands(true));
+                        StreamTypeFilter::SYNC, makeDrainPauseOutCommands(true));
 static const NamedCommandSequence kDrainPauseOutAsyncSeq =
         std::make_tuple(std::string("DrainPause"), kStreamTransientStateTransitionDelayMs,
-                        StreamTypeFilter::ASYNC, makeDrainOutPauseCommands(false));
+                        StreamTypeFilter::ASYNC, makeDrainPauseOutCommands(false));
 
 // This sequence also verifies that the capture / presentation position is not reset on standby.
 std::shared_ptr<StateSequence> makeStandbyCommands(bool isInput, bool isSync) {
-    return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
-            std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-            std::make_pair(kStandbyCommand, StreamDescriptor::State::STANDBY),
-            std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-            std::make_pair(kBurstCommand, isInput || isSync
-                                                  ? StreamDescriptor::State::ACTIVE
-                                                  : StreamDescriptor::State::TRANSFERRING),
-            std::make_pair(kPauseCommand, isInput || isSync
-                                                  ? StreamDescriptor::State::PAUSED
-                                                  : StreamDescriptor::State::TRANSFER_PAUSED),
-            std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY
-                                                  : StreamDescriptor::State::IDLE),
-            std::make_pair(isInput ? kGetStatusCommand : kStandbyCommand,  // no-op for input
-                           StreamDescriptor::State::STANDBY),
-            std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-            std::make_pair(kBurstCommand, isInput || isSync
-                                                  ? StreamDescriptor::State::ACTIVE
-                                                  : StreamDescriptor::State::TRANSFERRING)});
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    if (isInput) {
+        d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+                      std::make_pair(State::IDLE, kStandbyCommand),
+                      std::make_pair(State::STANDBY, kStartCommand),
+                      std::make_pair(State::IDLE, kBurstCommand),
+                      std::make_pair(State::ACTIVE, kPauseCommand),
+                      std::make_pair(State::PAUSED, kFlushCommand),
+                      std::make_pair(State::STANDBY, kStartCommand),
+                      std::make_pair(State::IDLE, kBurstCommand)},
+                     State::ACTIVE);
+    } else {
+        StateDag::Node idle3 =
+                d->makeNode(State::IDLE, kBurstCommand, d->makeFinalNode(State::ACTIVE));
+        StateDag::Node idle2 = d->makeNodes({std::make_pair(State::IDLE, kStandbyCommand),
+                                             std::make_pair(State::STANDBY, kStartCommand)},
+                                            idle3);
+        StateDag::Node active = d->makeNodes({std::make_pair(State::ACTIVE, kPauseCommand),
+                                              std::make_pair(State::PAUSED, kFlushCommand)},
+                                             idle2);
+        StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+        if (!isSync) {
+            idle3.children().push_back(d->makeFinalNode(State::TRANSFERRING));
+            StateDag::Node transferring =
+                    d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand),
+                                  std::make_pair(State::TRANSFER_PAUSED, kFlushCommand)},
+                                 idle2);
+            idle.children().push_back(transferring);
+        }
+        d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+                      std::make_pair(State::IDLE, kStandbyCommand),
+                      std::make_pair(State::STANDBY, kStartCommand)},
+                     idle);
+    }
+    return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kStandbyInSeq = std::make_tuple(
         std::string("Standby"), 0, StreamTypeFilter::ANY, makeStandbyCommands(true, false));
@@ -3320,50 +3512,71 @@
         std::make_tuple(std::string("Standby"), kStreamTransientStateTransitionDelayMs,
                         StreamTypeFilter::ASYNC, makeStandbyCommands(false, false));
 
-static const NamedCommandSequence kPauseInSeq =
-        std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::ANY,
-                        std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
-                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
-                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
-                                std::make_pair(kFlushCommand, StreamDescriptor::State::STANDBY)}));
-static const NamedCommandSequence kPauseOutSyncSeq =
-        std::make_tuple(std::string("Pause"), 0, StreamTypeFilter::SYNC,
-                        std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
-                                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                                std::make_pair(kBurstCommand, StreamDescriptor::State::ACTIVE),
-                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
-                                std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
-                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED),
-                                std::make_pair(kBurstCommand, StreamDescriptor::State::PAUSED),
-                                std::make_pair(kStartCommand, StreamDescriptor::State::ACTIVE),
-                                std::make_pair(kPauseCommand, StreamDescriptor::State::PAUSED)}));
-/* TODO: Figure out a better way for testing sync/async bursts
-static const NamedCommandSequence kPauseOutAsyncSeq = std::make_tuple(
-        std::string("Pause"), kStreamTransientStateTransitionDelayMs, StreamTypeFilter::ASYNC,
-        std::make_shared<StaticStateSequence>(std::vector<StateTransition>{
-                std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-                std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFERRING),
-                std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED),
-                std::make_pair(kStartCommand, StreamDescriptor::State::TRANSFERRING),
-                std::make_pair(kPauseCommand, StreamDescriptor::State::TRANSFER_PAUSED),
-                std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAIN_PAUSED),
-                std::make_pair(kBurstCommand, StreamDescriptor::State::TRANSFER_PAUSED)}));
-*/
+std::shared_ptr<StateSequence> makePauseCommands(bool isInput, bool isSync) {
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    if (isInput) {
+        d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+                      std::make_pair(State::IDLE, kBurstCommand),
+                      std::make_pair(State::ACTIVE, kPauseCommand),
+                      std::make_pair(State::PAUSED, kBurstCommand),
+                      std::make_pair(State::ACTIVE, kPauseCommand),
+                      std::make_pair(State::PAUSED, kFlushCommand)},
+                     State::STANDBY);
+    } else {
+        StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
+                                            std::make_pair(State::ACTIVE, kPauseCommand),
+                                            std::make_pair(State::PAUSED, kStartCommand),
+                                            std::make_pair(State::ACTIVE, kPauseCommand),
+                                            std::make_pair(State::PAUSED, kBurstCommand),
+                                            std::make_pair(State::PAUSED, kStartCommand),
+                                            std::make_pair(State::ACTIVE, kPauseCommand)},
+                                           State::PAUSED);
+        if (!isSync) {
+            idle.children().push_back(
+                    d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand),
+                                  std::make_pair(State::TRANSFER_PAUSED, kStartCommand),
+                                  std::make_pair(State::TRANSFERRING, kPauseCommand),
+                                  std::make_pair(State::TRANSFER_PAUSED, kDrainOutAllCommand),
+                                  std::make_pair(State::DRAIN_PAUSED, kBurstCommand)},
+                                 State::TRANSFER_PAUSED));
+        }
+        d->makeNode(State::STANDBY, kStartCommand, idle);
+    }
+    return std::make_shared<StateSequenceFollower>(std::move(d));
+}
+static const NamedCommandSequence kPauseInSeq = std::make_tuple(
+        std::string("Pause"), 0, StreamTypeFilter::ANY, makePauseCommands(true, false));
+static const NamedCommandSequence kPauseOutSyncSeq = std::make_tuple(
+        std::string("Pause"), 0, StreamTypeFilter::SYNC, makePauseCommands(false, true));
+static const NamedCommandSequence kPauseOutAsyncSeq =
+        std::make_tuple(std::string("Pause"), kStreamTransientStateTransitionDelayMs,
+                        StreamTypeFilter::ASYNC, makePauseCommands(false, false));
 
 std::shared_ptr<StateSequence> makeFlushCommands(bool isInput, bool isSync) {
-    return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
-            std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-            std::make_pair(kBurstCommand, isInput || isSync
-                                                  ? StreamDescriptor::State::ACTIVE
-                                                  : StreamDescriptor::State::TRANSFERRING),
-            std::make_pair(kPauseCommand, isInput || isSync
-                                                  ? StreamDescriptor::State::PAUSED
-                                                  : StreamDescriptor::State::TRANSFER_PAUSED),
-            std::make_pair(kFlushCommand, isInput ? StreamDescriptor::State::STANDBY
-                                                  : StreamDescriptor::State::IDLE)});
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    if (isInput) {
+        d->makeNodes({std::make_pair(State::STANDBY, kStartCommand),
+                      std::make_pair(State::IDLE, kBurstCommand),
+                      std::make_pair(State::ACTIVE, kPauseCommand),
+                      std::make_pair(State::PAUSED, kFlushCommand)},
+                     State::STANDBY);
+    } else {
+        StateDag::Node last = d->makeFinalNode(State::IDLE);
+        StateDag::Node idle = d->makeNodes({std::make_pair(State::IDLE, kBurstCommand),
+                                            std::make_pair(State::ACTIVE, kPauseCommand),
+                                            std::make_pair(State::PAUSED, kFlushCommand)},
+                                           last);
+        if (!isSync) {
+            idle.children().push_back(
+                    d->makeNodes({std::make_pair(State::TRANSFERRING, kPauseCommand),
+                                  std::make_pair(State::TRANSFER_PAUSED, kFlushCommand)},
+                                 last));
+        }
+        d->makeNode(State::STANDBY, kStartCommand, idle);
+    }
+    return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kFlushInSeq = std::make_tuple(
         std::string("Flush"), 0, StreamTypeFilter::ANY, makeFlushCommands(true, false));
@@ -3374,13 +3587,21 @@
                         StreamTypeFilter::ASYNC, makeFlushCommands(false, false));
 
 std::shared_ptr<StateSequence> makeDrainPauseFlushOutCommands(bool isSync) {
-    return std::make_shared<SmartStateSequence>(std::vector<StateTransition>{
-            std::make_pair(kStartCommand, StreamDescriptor::State::IDLE),
-            std::make_pair(kBurstCommand, isSync ? StreamDescriptor::State::ACTIVE
-                                                 : StreamDescriptor::State::TRANSFERRING),
-            std::make_pair(kDrainOutAllCommand, StreamDescriptor::State::DRAINING),
-            std::make_pair(kPauseCommand, StreamDescriptor::State::DRAIN_PAUSED),
-            std::make_pair(kFlushCommand, StreamDescriptor::State::IDLE)});
+    using State = StreamDescriptor::State;
+    auto d = std::make_unique<StateDag>();
+    StateDag::Node draining = d->makeNodes({std::make_pair(State::DRAINING, kPauseCommand),
+                                            std::make_pair(State::DRAIN_PAUSED, kFlushCommand)},
+                                           State::IDLE);
+    StateDag::Node active = d->makeNode(State::ACTIVE, kDrainOutAllCommand, draining);
+    StateDag::Node idle = d->makeNode(State::IDLE, kBurstCommand, active);
+    if (!isSync) {
+        idle.children().push_back(d->makeNode(State::TRANSFERRING, kDrainOutAllCommand, draining));
+    } else {
+        // If we get straight into IDLE on drain, no further testing is possible.
+        active.children().push_back(d->makeFinalNode(State::IDLE));
+    }
+    d->makeNode(State::STANDBY, kStartCommand, idle);
+    return std::make_shared<StateSequenceFollower>(std::move(d));
 }
 static const NamedCommandSequence kDrainPauseFlushOutSyncSeq =
         std::make_tuple(std::string("DrainPauseFlush"), kStreamTransientStateTransitionDelayMs,
diff --git a/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp b/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
new file mode 100644
index 0000000..9feff91
--- /dev/null
+++ b/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
@@ -0,0 +1,1115 @@
+/*
+ * 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.
+ */
+
+#include <aidl/Vintf.h>
+
+#define LOG_TAG "VtsHalDynamicsProcessingTest"
+
+#include <set>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <Utils.h>
+#include "EffectHelper.h"
+
+using namespace android;
+
+using aidl::android::hardware::audio::effect::Capability;
+using aidl::android::hardware::audio::effect::Descriptor;
+using aidl::android::hardware::audio::effect::DynamicsProcessing;
+using aidl::android::hardware::audio::effect::IEffect;
+using aidl::android::hardware::audio::effect::IFactory;
+using aidl::android::hardware::audio::effect::kDynamicsProcessingTypeUUID;
+using aidl::android::hardware::audio::effect::Parameter;
+
+/**
+ * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
+ * VtsAudioEffectTargetTest.
+ */
+class DynamicsProcessingTestHelper : public EffectHelper {
+  public:
+    DynamicsProcessingTestHelper(std::pair<std::shared_ptr<IFactory>, Descriptor> pair,
+                                 int32_t channelLayOut = AudioChannelLayout::LAYOUT_STEREO) {
+        std::tie(mFactory, mDescriptor) = pair;
+        mChannelLayout = channelLayOut;
+        mChannelCount = ::android::hardware::audio::common::getChannelCount(
+                AudioChannelLayout::make<AudioChannelLayout::layoutMask>(mChannelLayout));
+    }
+
+    // setup
+    void SetUpDynamicsProcessingEffect() {
+        ASSERT_NE(nullptr, mFactory);
+        ASSERT_NO_FATAL_FAILURE(create(mFactory, mEffect, mDescriptor));
+
+        Parameter::Specific specific = getDefaultParamSpecific();
+        Parameter::Common common = EffectHelper::createParamCommon(
+                0 /* session */, 1 /* ioHandle */, 44100 /* iSampleRate */, 44100 /* oSampleRate */,
+                0x100 /* iFrameCount */, 0x100 /* oFrameCount */,
+                AudioChannelLayout::make<AudioChannelLayout::layoutMask>(mChannelLayout),
+                AudioChannelLayout::make<AudioChannelLayout::layoutMask>(mChannelLayout));
+        IEffect::OpenEffectReturn ret;
+        ASSERT_NO_FATAL_FAILURE(open(mEffect, common, specific, &ret, EX_NONE));
+        ASSERT_NE(nullptr, mEffect);
+        mEngineConfigApplied = mEngineConfigPreset;
+    }
+
+    Parameter::Specific getDefaultParamSpecific() {
+        DynamicsProcessing dp = DynamicsProcessing::make<DynamicsProcessing::engineArchitecture>(
+                mEngineConfigPreset);
+        Parameter::Specific specific =
+                Parameter::Specific::make<Parameter::Specific::dynamicsProcessing>(dp);
+        return specific;
+    }
+
+    // teardown
+    void TearDownDynamicsProcessingEffect() {
+        ASSERT_NO_FATAL_FAILURE(close(mEffect));
+        ASSERT_NO_FATAL_FAILURE(destroy(mFactory, mEffect));
+    }
+
+    // utils functions for parameter checking
+    bool isParamValid(const DynamicsProcessing::Tag& tag, const DynamicsProcessing& dp,
+                      const Descriptor& desc);
+    bool isParamEqual(const DynamicsProcessing::Tag& tag, const DynamicsProcessing& dpRef,
+                      const DynamicsProcessing& dpTest);
+
+    bool isEnablementValid(const DynamicsProcessing::StageEnablement& enablement);
+    bool isEngineConfigValid(const DynamicsProcessing::EngineArchitecture& cfg);
+
+    bool isCutoffFrequencyValid(float freq, const DynamicsProcessing::Capability& cap);
+    bool isChannelConfigValid(const std::vector<DynamicsProcessing::ChannelConfig>& cfgs,
+                              bool stageInUse);
+
+    bool isPreEqBandConfigValid(const DynamicsProcessing::Capability& cap,
+                                const std::vector<DynamicsProcessing::EqBandConfig>& cfgs,
+                                bool stageInUse, int bandCount);
+    bool isPostEqBandConfigValid(const DynamicsProcessing::Capability& cap,
+                                 const std::vector<DynamicsProcessing::EqBandConfig>& cfgs,
+                                 bool stageInUse, int bandCount);
+    bool isMbcBandConfigValid(const DynamicsProcessing::Capability& cap,
+                              const std::vector<DynamicsProcessing::MbcBandConfig>& cfgs,
+                              bool stageInUse, int bandCount);
+    bool isLimiterConfigValid(const std::vector<DynamicsProcessing::LimiterConfig>& cfgs,
+                              bool stageInUse);
+    bool isInputGainValid(const std::vector<DynamicsProcessing::InputGain>& cfgs);
+
+    bool isEngineConfigEqual(const DynamicsProcessing::EngineArchitecture& refCfg,
+                             const DynamicsProcessing::EngineArchitecture& testCfg);
+
+    template <typename T>
+    std::vector<T> filterEnabledVector(const std::vector<T>& vec);
+
+    template <typename T>
+    bool isAidlVectorEqualAfterFilter(const std::vector<T>& source, const std::vector<T>& target);
+
+    template <typename T>
+    bool isAidlVectorEqual(const std::vector<T>& source, const std::vector<T>& target);
+
+    // get set params and validate
+    void SetAndGetDynamicsProcessingParameters();
+
+    // enqueue test parameters
+    void addEngineConfig(const DynamicsProcessing::EngineArchitecture& cfg);
+    void addPreEqChannelConfig(const std::vector<DynamicsProcessing::ChannelConfig>& cfg);
+    void addPostEqChannelConfig(const std::vector<DynamicsProcessing::ChannelConfig>& cfg);
+    void addMbcChannelConfig(const std::vector<DynamicsProcessing::ChannelConfig>& cfg);
+    void addPreEqBandConfigs(const std::vector<DynamicsProcessing::EqBandConfig>& cfgs);
+    void addPostEqBandConfigs(const std::vector<DynamicsProcessing::EqBandConfig>& cfgs);
+    void addMbcBandConfigs(const std::vector<DynamicsProcessing::MbcBandConfig>& cfgs);
+    void addLimiterConfig(const std::vector<DynamicsProcessing::LimiterConfig>& cfg);
+    void addInputGain(const std::vector<DynamicsProcessing::InputGain>& inputGain);
+
+    static constexpr float kPreferredProcessingDurationMs = 10.0f;
+    static constexpr int kBandCount = 5;
+    std::shared_ptr<IFactory> mFactory;
+    std::shared_ptr<IEffect> mEffect;
+    Descriptor mDescriptor;
+    DynamicsProcessing::EngineArchitecture mEngineConfigApplied;
+    DynamicsProcessing::EngineArchitecture mEngineConfigPreset{
+            .resolutionPreference =
+                    DynamicsProcessing::ResolutionPreference::FAVOR_FREQUENCY_RESOLUTION,
+            .preferredProcessingDurationMs = kPreferredProcessingDurationMs,
+            .preEqStage = {.inUse = true, .bandCount = kBandCount},
+            .postEqStage = {.inUse = true, .bandCount = kBandCount},
+            .mbcStage = {.inUse = true, .bandCount = kBandCount},
+            .limiterInUse = true,
+    };
+
+    std::unordered_set<int /* channelId */> mPreEqChannelEnable;
+    std::unordered_set<int /* channelId */> mPostEqChannelEnable;
+    std::unordered_set<int /* channelId */> mMbcChannelEnable;
+    std::unordered_set<int /* channelId */> mLimiterChannelEnable;
+    static const std::set<std::vector<DynamicsProcessing::ChannelConfig>> kChannelConfigTestSet;
+    static const std::set<DynamicsProcessing::StageEnablement> kStageEnablementTestSet;
+    static const std::set<std::vector<DynamicsProcessing::InputGain>> kInputGainTestSet;
+
+  private:
+    int32_t mChannelLayout;
+    int mChannelCount;
+    std::vector<std::pair<DynamicsProcessing::Tag, DynamicsProcessing>> mTags;
+    void CleanUp() {
+        mTags.clear();
+        mPreEqChannelEnable.clear();
+        mPostEqChannelEnable.clear();
+        mMbcChannelEnable.clear();
+        mLimiterChannelEnable.clear();
+    }
+};
+
+// test value set for DynamicsProcessing::StageEnablement
+const std::set<DynamicsProcessing::StageEnablement>
+        DynamicsProcessingTestHelper::kStageEnablementTestSet = {
+                {.inUse = true, .bandCount = DynamicsProcessingTestHelper::kBandCount},
+                {.inUse = true, .bandCount = 0},
+                {.inUse = true, .bandCount = -1},
+                {.inUse = false, .bandCount = DynamicsProcessingTestHelper::kBandCount}};
+
+// test value set for DynamicsProcessing::ChannelConfig
+const std::set<std::vector<DynamicsProcessing::ChannelConfig>>
+        DynamicsProcessingTestHelper::kChannelConfigTestSet = {
+                {{.channel = -1, .enable = false},
+                 {.channel = 0, .enable = true},
+                 {.channel = 1, .enable = false},
+                 {.channel = 2, .enable = true}},
+
+                {{.channel = -1, .enable = false}, {.channel = 2, .enable = true}},
+
+                {{.channel = 0, .enable = true}, {.channel = 1, .enable = true}}};
+
+// test value set for DynamicsProcessing::InputGain
+const std::set<std::vector<DynamicsProcessing::InputGain>>
+        DynamicsProcessingTestHelper::kInputGainTestSet = {
+                {{.channel = 0, .gainDb = 10.f},
+                 {.channel = 1, .gainDb = 0.f},
+                 {.channel = 2, .gainDb = -10.f}},
+
+                {{.channel = -1, .gainDb = -10.f}, {.channel = -2, .gainDb = 10.f}},
+
+                {{.channel = -1, .gainDb = 10.f}, {.channel = 0, .gainDb = -10.f}}};
+
+bool DynamicsProcessingTestHelper::isParamValid(const DynamicsProcessing::Tag& tag,
+                                                const DynamicsProcessing& dp,
+                                                const Descriptor& desc) {
+    const DynamicsProcessing::Capability& dpCap =
+            desc.capability.get<Capability::dynamicsProcessing>();
+    switch (tag) {
+        case DynamicsProcessing::engineArchitecture: {
+            return isEngineConfigValid(dp.get<DynamicsProcessing::engineArchitecture>());
+        }
+        case DynamicsProcessing::preEq: {
+            return isChannelConfigValid(dp.get<DynamicsProcessing::preEq>(),
+                                        mEngineConfigApplied.preEqStage.inUse);
+        }
+        case DynamicsProcessing::postEq: {
+            return isChannelConfigValid(dp.get<DynamicsProcessing::postEq>(),
+                                        mEngineConfigApplied.postEqStage.inUse);
+        }
+        case DynamicsProcessing::mbc: {
+            return isChannelConfigValid(dp.get<DynamicsProcessing::mbc>(),
+                                        mEngineConfigApplied.mbcStage.inUse);
+        }
+        case DynamicsProcessing::preEqBand: {
+            return isPreEqBandConfigValid(dpCap, dp.get<DynamicsProcessing::preEqBand>(),
+                                          mEngineConfigApplied.preEqStage.inUse,
+                                          mEngineConfigApplied.preEqStage.bandCount);
+        }
+        case DynamicsProcessing::postEqBand: {
+            return isPostEqBandConfigValid(dpCap, dp.get<DynamicsProcessing::postEqBand>(),
+                                           mEngineConfigApplied.postEqStage.inUse,
+                                           mEngineConfigApplied.postEqStage.bandCount);
+        }
+        case DynamicsProcessing::mbcBand: {
+            return isMbcBandConfigValid(dpCap, dp.get<DynamicsProcessing::mbcBand>(),
+                                        mEngineConfigApplied.mbcStage.inUse,
+                                        mEngineConfigApplied.mbcStage.bandCount);
+        }
+        case DynamicsProcessing::limiter: {
+            return isLimiterConfigValid(dp.get<DynamicsProcessing::limiter>(),
+                                        mEngineConfigApplied.limiterInUse);
+        }
+        case DynamicsProcessing::inputGain: {
+            return isInputGainValid(dp.get<DynamicsProcessing::inputGain>());
+        }
+        case DynamicsProcessing::vendorExtension: {
+            return true;
+        }
+    }
+    return true;
+}
+
+bool DynamicsProcessingTestHelper::isParamEqual(const DynamicsProcessing::Tag& tag,
+                                                const DynamicsProcessing& dpRef,
+                                                const DynamicsProcessing& dpTest) {
+    switch (tag) {
+        case DynamicsProcessing::engineArchitecture: {
+            return isEngineConfigEqual(dpRef.get<DynamicsProcessing::engineArchitecture>(),
+                                       dpTest.get<DynamicsProcessing::engineArchitecture>());
+        }
+        case DynamicsProcessing::preEq: {
+            const auto& source = dpRef.get<DynamicsProcessing::preEq>();
+            const auto& target = dpTest.get<DynamicsProcessing::preEq>();
+            return isAidlVectorEqualAfterFilter<DynamicsProcessing::ChannelConfig>(source, target);
+        }
+        case DynamicsProcessing::postEq: {
+            return isAidlVectorEqualAfterFilter<DynamicsProcessing::ChannelConfig>(
+                    dpRef.get<DynamicsProcessing::postEq>(),
+                    dpTest.get<DynamicsProcessing::postEq>());
+        }
+        case DynamicsProcessing::mbc: {
+            return isAidlVectorEqualAfterFilter<DynamicsProcessing::ChannelConfig>(
+                    dpRef.get<DynamicsProcessing::mbc>(), dpTest.get<DynamicsProcessing::mbc>());
+        }
+        case DynamicsProcessing::preEqBand: {
+            return isAidlVectorEqualAfterFilter<DynamicsProcessing::EqBandConfig>(
+                    dpRef.get<DynamicsProcessing::preEqBand>(),
+                    dpTest.get<DynamicsProcessing::preEqBand>());
+        }
+        case DynamicsProcessing::postEqBand: {
+            return isAidlVectorEqualAfterFilter<DynamicsProcessing::EqBandConfig>(
+                    dpRef.get<DynamicsProcessing::postEqBand>(),
+                    dpTest.get<DynamicsProcessing::postEqBand>());
+        }
+        case DynamicsProcessing::mbcBand: {
+            return isAidlVectorEqualAfterFilter<DynamicsProcessing::MbcBandConfig>(
+                    dpRef.get<DynamicsProcessing::mbcBand>(),
+                    dpTest.get<DynamicsProcessing::mbcBand>());
+        }
+        case DynamicsProcessing::limiter: {
+            return isAidlVectorEqualAfterFilter<DynamicsProcessing::LimiterConfig>(
+                    dpRef.get<DynamicsProcessing::limiter>(),
+                    dpTest.get<DynamicsProcessing::limiter>());
+        }
+        case DynamicsProcessing::inputGain: {
+            return isAidlVectorEqual<DynamicsProcessing::InputGain>(
+                    dpRef.get<DynamicsProcessing::inputGain>(),
+                    dpTest.get<DynamicsProcessing::inputGain>());
+        }
+        case DynamicsProcessing::vendorExtension: {
+            return false;
+        }
+    }
+}
+
+bool DynamicsProcessingTestHelper::isEnablementValid(
+        const DynamicsProcessing::StageEnablement& enablement) {
+    return !enablement.inUse || (enablement.inUse && enablement.bandCount > 0);
+}
+
+bool DynamicsProcessingTestHelper::isEngineConfigValid(
+        const DynamicsProcessing::EngineArchitecture& cfg) {
+    return cfg.preferredProcessingDurationMs >= 0 && isEnablementValid(cfg.preEqStage) &&
+           isEnablementValid(cfg.postEqStage) && isEnablementValid(cfg.mbcStage);
+}
+
+bool DynamicsProcessingTestHelper::isChannelConfigValid(
+        const std::vector<DynamicsProcessing::ChannelConfig>& cfgs, bool stageInUse) {
+    std::unordered_set<int> channelSet;
+    if (!stageInUse) return false;
+    for (auto cfg : cfgs) {
+        if (cfg.channel < 0 || cfg.channel >= mChannelCount || 0 != channelSet.count(cfg.channel)) {
+            return false;
+        }
+        channelSet.insert(cfg.channel);
+    }
+    return true;
+}
+
+bool DynamicsProcessingTestHelper::isCutoffFrequencyValid(
+        float freq, const DynamicsProcessing::Capability& cap) {
+    return freq >= cap.minCutOffFreq && freq <= cap.maxCutOffFreq;
+}
+
+bool DynamicsProcessingTestHelper::isPreEqBandConfigValid(
+        const DynamicsProcessing::Capability& cap,
+        const std::vector<DynamicsProcessing::EqBandConfig>& cfgs, bool stageInUse, int bandCount) {
+    std::set<std::pair<int /* channelID */, int /* bandID */>> bandSet;
+    if (!stageInUse) return false;
+    for (auto cfg : cfgs) {
+        if (0 == mPreEqChannelEnable.count(cfg.channel) || cfg.channel < 0 ||
+            cfg.channel >= mChannelCount || cfg.band < 0 || cfg.band >= bandCount ||
+            !isCutoffFrequencyValid(cfg.cutoffFrequencyHz, cap) ||
+            0 != bandSet.count({cfg.channel, cfg.band})) {
+            return false;
+        }
+        bandSet.insert({cfg.channel, cfg.band});
+    }
+    return true;
+}
+
+bool DynamicsProcessingTestHelper::isPostEqBandConfigValid(
+        const DynamicsProcessing::Capability& cap,
+        const std::vector<DynamicsProcessing::EqBandConfig>& cfgs, bool stageInUse, int bandCount) {
+    std::set<std::pair<int /* channelID */, int /* bandID */>> bandSet;
+    // not able to set/get parameter when stage not in use.
+    if (!stageInUse) return false;
+    for (auto cfg : cfgs) {
+        if (0 == mPostEqChannelEnable.count(cfg.channel) || cfg.channel < 0 ||
+            cfg.channel >= mChannelCount || cfg.band < 0 || cfg.band >= bandCount ||
+            !isCutoffFrequencyValid(cfg.cutoffFrequencyHz, cap) ||
+            0 != bandSet.count({cfg.channel, cfg.band})) {
+            return false;
+        }
+        bandSet.insert({cfg.channel, cfg.band});
+    }
+    return true;
+}
+
+bool DynamicsProcessingTestHelper::isMbcBandConfigValid(
+        const DynamicsProcessing::Capability& cap,
+        const std::vector<DynamicsProcessing::MbcBandConfig>& cfgs, bool stageInUse,
+        int bandCount) {
+    std::set<std::pair<int /* channelID */, int /* bandID */>> bandSet;
+    if (!stageInUse) return false;
+    for (auto cfg : cfgs) {
+        if (0 == mMbcChannelEnable.count(cfg.channel) || cfg.channel < 0 ||
+            cfg.channel >= mChannelCount || cfg.band < 0 || cfg.band >= bandCount ||
+            (cfg.attackTimeMs < 0) || cfg.releaseTimeMs < 0 || cfg.ratio < 0 ||
+            cfg.thresholdDb > 0 || cfg.kneeWidthDb < 0 || cfg.noiseGateThresholdDb > 0 ||
+            cfg.expanderRatio < 0 || !isCutoffFrequencyValid(cfg.cutoffFrequencyHz, cap) ||
+            0 != bandSet.count({cfg.channel, cfg.band})) {
+            return false;
+        }
+        bandSet.insert({cfg.channel, cfg.band});
+    }
+    return true;
+}
+
+bool DynamicsProcessingTestHelper::isLimiterConfigValid(
+        const std::vector<DynamicsProcessing::LimiterConfig>& cfgs, bool stageInUse) {
+    std::set<int> channelSet;
+    if (!stageInUse) return false;
+    for (auto cfg : cfgs) {
+        if (0 == mLimiterChannelEnable.count(cfg.channel) || cfg.channel < 0 ||
+            cfg.channel >= mChannelCount || cfg.attackTimeMs < 0 || cfg.releaseTimeMs < 0 ||
+            cfg.ratio < 0 || cfg.thresholdDb > 0 || 0 != channelSet.count(cfg.channel)) {
+            return false;
+        }
+        channelSet.insert(cfg.channel);
+    }
+    return true;
+}
+
+bool DynamicsProcessingTestHelper::isInputGainValid(
+        const std::vector<DynamicsProcessing::InputGain>& cfgs) {
+    std::set<int> channelSet;
+    for (auto cfg : cfgs) {
+        if (cfg.channel < 0 || cfg.channel >= mChannelCount || 0 != channelSet.count(cfg.channel)) {
+            return false;
+        }
+        channelSet.insert(cfg.channel);
+    }
+    return true;
+}
+
+bool DynamicsProcessingTestHelper::isEngineConfigEqual(
+        const DynamicsProcessing::EngineArchitecture& ref,
+        const DynamicsProcessing::EngineArchitecture& test) {
+    return ref == test;
+}
+
+template <typename T>
+std::vector<T> DynamicsProcessingTestHelper::filterEnabledVector(const std::vector<T>& vec) {
+    std::vector<T> ret;
+    std::copy_if(vec.begin(), vec.end(), std::back_inserter(ret),
+                 [](const auto& v) { return v.enable; });
+    return ret;
+}
+
+template <typename T>
+bool DynamicsProcessingTestHelper::isAidlVectorEqual(const std::vector<T>& source,
+                                                     const std::vector<T>& target) {
+    if (source.size() != target.size()) return false;
+
+    auto tempS = source;
+    auto tempT = target;
+    std::sort(tempS.begin(), tempS.end());
+    std::sort(tempT.begin(), tempT.end());
+    return tempS == tempT;
+}
+
+template <typename T>
+bool DynamicsProcessingTestHelper::isAidlVectorEqualAfterFilter(const std::vector<T>& source,
+                                                                const std::vector<T>& target) {
+    return isAidlVectorEqual<T>(filterEnabledVector<T>(source), filterEnabledVector<T>(target));
+}
+
+void DynamicsProcessingTestHelper::SetAndGetDynamicsProcessingParameters() {
+    for (auto& it : mTags) {
+        auto& tag = it.first;
+        auto& dp = it.second;
+
+        // validate parameter
+        Descriptor desc;
+        ASSERT_STATUS(EX_NONE, mEffect->getDescriptor(&desc));
+        const bool valid = isParamValid(tag, dp, desc);
+        const binder_exception_t expected = valid ? EX_NONE : EX_ILLEGAL_ARGUMENT;
+
+        // set parameter
+        Parameter expectParam;
+        Parameter::Specific specific;
+        specific.set<Parameter::Specific::dynamicsProcessing>(dp);
+        expectParam.set<Parameter::specific>(specific);
+        ASSERT_STATUS(expected, mEffect->setParameter(expectParam)) << expectParam.toString();
+
+        // only get if parameter in range and set success
+        if (expected == EX_NONE) {
+            Parameter getParam;
+            Parameter::Id id;
+            DynamicsProcessing::Id dpId;
+            dpId.set<DynamicsProcessing::Id::commonTag>(tag);
+            id.set<Parameter::Id::dynamicsProcessingTag>(dpId);
+            // if set success, then get should match
+            EXPECT_STATUS(expected, mEffect->getParameter(id, &getParam));
+            Parameter::Specific specificTest = getParam.get<Parameter::specific>();
+            const auto& target = specificTest.get<Parameter::Specific::dynamicsProcessing>();
+            EXPECT_TRUE(isParamEqual(tag, dp, target)) << dp.toString() << "\n"
+                                                       << target.toString();
+            // update mEngineConfigApplied after setting successfully
+            if (tag == DynamicsProcessing::engineArchitecture) {
+                mEngineConfigApplied = target.get<DynamicsProcessing::engineArchitecture>();
+            }
+        }
+    }
+}
+
+void DynamicsProcessingTestHelper::addEngineConfig(
+        const DynamicsProcessing::EngineArchitecture& cfg) {
+    DynamicsProcessing dp;
+    dp.set<DynamicsProcessing::engineArchitecture>(cfg);
+    mTags.push_back({DynamicsProcessing::engineArchitecture, dp});
+}
+
+void DynamicsProcessingTestHelper::addPreEqChannelConfig(
+        const std::vector<DynamicsProcessing::ChannelConfig>& cfgs) {
+    DynamicsProcessing dp;
+    dp.set<DynamicsProcessing::preEq>(cfgs);
+    mTags.push_back({DynamicsProcessing::preEq, dp});
+    for (auto& cfg : cfgs) {
+        if (cfg.enable) mPreEqChannelEnable.insert(cfg.channel);
+    }
+}
+
+void DynamicsProcessingTestHelper::addPostEqChannelConfig(
+        const std::vector<DynamicsProcessing::ChannelConfig>& cfgs) {
+    DynamicsProcessing dp;
+    dp.set<DynamicsProcessing::postEq>(cfgs);
+    mTags.push_back({DynamicsProcessing::postEq, dp});
+    for (auto& cfg : cfgs) {
+        if (cfg.enable) mPostEqChannelEnable.insert(cfg.channel);
+    }
+}
+
+void DynamicsProcessingTestHelper::addMbcChannelConfig(
+        const std::vector<DynamicsProcessing::ChannelConfig>& cfgs) {
+    DynamicsProcessing dp;
+    dp.set<DynamicsProcessing::mbc>(cfgs);
+    mTags.push_back({DynamicsProcessing::mbc, dp});
+    for (auto& cfg : cfgs) {
+        if (cfg.enable) mMbcChannelEnable.insert(cfg.channel);
+    }
+}
+
+void DynamicsProcessingTestHelper::addPreEqBandConfigs(
+        const std::vector<DynamicsProcessing::EqBandConfig>& cfgs) {
+    DynamicsProcessing dp;
+    dp.set<DynamicsProcessing::preEqBand>(cfgs);
+    mTags.push_back({DynamicsProcessing::preEqBand, dp});
+}
+
+void DynamicsProcessingTestHelper::addPostEqBandConfigs(
+        const std::vector<DynamicsProcessing::EqBandConfig>& cfgs) {
+    DynamicsProcessing dp;
+    dp.set<DynamicsProcessing::postEqBand>(cfgs);
+    mTags.push_back({DynamicsProcessing::postEqBand, dp});
+}
+
+void DynamicsProcessingTestHelper::addMbcBandConfigs(
+        const std::vector<DynamicsProcessing::MbcBandConfig>& cfgs) {
+    DynamicsProcessing dp;
+    dp.set<DynamicsProcessing::mbcBand>(cfgs);
+    mTags.push_back({DynamicsProcessing::mbcBand, dp});
+}
+
+void DynamicsProcessingTestHelper::addLimiterConfig(
+        const std::vector<DynamicsProcessing::LimiterConfig>& cfgs) {
+    DynamicsProcessing dp;
+    dp.set<DynamicsProcessing::limiter>(cfgs);
+    mTags.push_back({DynamicsProcessing::limiter, dp});
+    for (auto& cfg : cfgs) {
+        if (cfg.enable) mLimiterChannelEnable.insert(cfg.channel);
+    }
+}
+
+void DynamicsProcessingTestHelper::addInputGain(
+        const std::vector<DynamicsProcessing::InputGain>& inputGains) {
+    DynamicsProcessing dp;
+    dp.set<DynamicsProcessing::inputGain>(inputGains);
+    mTags.push_back({DynamicsProcessing::inputGain, dp});
+}
+
+/**
+ * Test DynamicsProcessing Engine Configuration
+ */
+enum EngineArchitectureTestParamName {
+    ENGINE_TEST_INSTANCE_NAME,
+    ENGINE_TEST_RESOLUTION_PREFERENCE,
+    ENGINE_TEST_PREFERRED_DURATION,
+    ENGINE_TEST_STAGE_ENABLEMENT,
+    ENGINE_TEST_LIMITER_IN_USE
+};
+using EngineArchitectureTestParams = std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>,
+                                                DynamicsProcessing::ResolutionPreference, float,
+                                                DynamicsProcessing::StageEnablement, bool>;
+
+void fillEngineArchConfig(DynamicsProcessing::EngineArchitecture& cfg,
+                          const EngineArchitectureTestParams& params) {
+    cfg.resolutionPreference = std::get<ENGINE_TEST_RESOLUTION_PREFERENCE>(params);
+    cfg.preferredProcessingDurationMs = std::get<ENGINE_TEST_PREFERRED_DURATION>(params);
+    cfg.preEqStage = cfg.postEqStage = cfg.mbcStage =
+            std::get<ENGINE_TEST_STAGE_ENABLEMENT>(params);
+    cfg.limiterInUse = std::get<ENGINE_TEST_LIMITER_IN_USE>(params);
+}
+
+class DynamicsProcessingTestEngineArchitecture
+    : public ::testing::TestWithParam<EngineArchitectureTestParams>,
+      public DynamicsProcessingTestHelper {
+  public:
+    DynamicsProcessingTestEngineArchitecture()
+        : DynamicsProcessingTestHelper(std::get<ENGINE_TEST_INSTANCE_NAME>(GetParam())) {
+        fillEngineArchConfig(mCfg, GetParam());
+    };
+
+    void SetUp() override { SetUpDynamicsProcessingEffect(); }
+
+    void TearDown() override { TearDownDynamicsProcessingEffect(); }
+
+    DynamicsProcessing::EngineArchitecture mCfg;
+};
+
+TEST_P(DynamicsProcessingTestEngineArchitecture, SetAndGetEngineArch) {
+    EXPECT_NO_FATAL_FAILURE(addEngineConfig(mCfg));
+    SetAndGetDynamicsProcessingParameters();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        DynamicsProcessingTest, DynamicsProcessingTestEngineArchitecture,
+        ::testing::Combine(
+                testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
+                        IFactory::descriptor, kDynamicsProcessingTypeUUID)),
+                testing::Values(DynamicsProcessing::ResolutionPreference::FAVOR_TIME_RESOLUTION,
+                                DynamicsProcessing::ResolutionPreference::
+                                        FAVOR_FREQUENCY_RESOLUTION),  // variant
+                testing::Values(-10.f, 0.f, 10.f),                    // processing duration
+                testing::ValuesIn(
+                        DynamicsProcessingTestHelper::kStageEnablementTestSet),  // preEQ/postEQ/mbc
+                testing::Bool()),                                                // limiter enable
+        [](const auto& info) {
+            auto descriptor = std::get<ENGINE_TEST_INSTANCE_NAME>(info.param).second;
+            DynamicsProcessing::EngineArchitecture cfg;
+            fillEngineArchConfig(cfg, info.param);
+            std::string name = "Implementor_" + descriptor.common.implementor + "_name_" +
+                               descriptor.common.name + "_UUID_" +
+                               descriptor.common.id.uuid.toString() + "_Cfg_" + cfg.toString();
+            std::replace_if(
+                    name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
+            return name;
+        });
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DynamicsProcessingTestEngineArchitecture);
+
+/**
+ * Test DynamicsProcessing Input Gain
+ */
+enum InputGainTestParamName {
+    INPUT_GAIN_INSTANCE_NAME,
+    INPUT_GAIN_PARAM,
+};
+class DynamicsProcessingTestInputGain
+    : public ::testing::TestWithParam<std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>,
+                                                 std::vector<DynamicsProcessing::InputGain>>>,
+      public DynamicsProcessingTestHelper {
+  public:
+    DynamicsProcessingTestInputGain()
+        : DynamicsProcessingTestHelper(std::get<INPUT_GAIN_INSTANCE_NAME>(GetParam())),
+          mInputGain(std::get<INPUT_GAIN_PARAM>(GetParam())){};
+
+    void SetUp() override { SetUpDynamicsProcessingEffect(); }
+
+    void TearDown() override { TearDownDynamicsProcessingEffect(); }
+
+    const std::vector<DynamicsProcessing::InputGain> mInputGain;
+};
+
+TEST_P(DynamicsProcessingTestInputGain, SetAndGetInputGain) {
+    EXPECT_NO_FATAL_FAILURE(addInputGain(mInputGain));
+    SetAndGetDynamicsProcessingParameters();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        DynamicsProcessingTest, DynamicsProcessingTestInputGain,
+        ::testing::Combine(testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
+                                   IFactory::descriptor, kDynamicsProcessingTypeUUID)),
+                           testing::ValuesIn(DynamicsProcessingTestInputGain::kInputGainTestSet)),
+        [](const auto& info) {
+            auto descriptor = std::get<INPUT_GAIN_INSTANCE_NAME>(info.param).second;
+            std::string gains =
+                    ::android::internal::ToString(std::get<INPUT_GAIN_PARAM>(info.param));
+            std::string name = "Implementor_" + descriptor.common.implementor + "_name_" +
+                               descriptor.common.name + "_UUID_" +
+                               descriptor.common.id.uuid.toString() + "_inputGains_" + gains;
+            std::replace_if(
+                    name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
+            return name;
+        });
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DynamicsProcessingTestInputGain);
+
+/**
+ * Test DynamicsProcessing Limiter Config
+ */
+enum LimiterConfigTestParamName {
+    LIMITER_INSTANCE_NAME,
+    LIMITER_CHANNEL,
+    LIMITER_ENABLE,
+    LIMITER_LINK_GROUP,
+    LIMITER_ENGINE_IN_USE,
+    LIMITER_ADDITIONAL,
+};
+enum LimiterConfigTestAdditionalParam {
+    LIMITER_ATTACK_TIME,
+    LIMITER_RELEASE_TIME,
+    LIMITER_RATIO,
+    LIMITER_THRESHOLD,
+    LIMITER_POST_GAIN,
+    LIMITER_MAX_NUM,
+};
+using LimiterConfigTestAdditional = std::array<float, LIMITER_MAX_NUM>;
+// attachTime, releaseTime, ratio, thresh, postGain
+static constexpr std::array<LimiterConfigTestAdditional, 4> kLimiterConfigTestAdditionalParam = {
+        {{-1, -60, -2.5, -2, -3.14},
+         {-1, 60, -2.5, 2, -3.14},
+         {1, -60, 2.5, -2, 3.14},
+         {1, 60, 2.5, 2, 3.14}}};
+
+using LimiterConfigTestParams =
+        std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>, int32_t, bool, int32_t, bool,
+                   LimiterConfigTestAdditional>;
+
+void fillLimiterConfig(DynamicsProcessing::LimiterConfig& cfg,
+                       const LimiterConfigTestParams& params) {
+    const std::array<float, LIMITER_MAX_NUM> additional = std::get<LIMITER_ADDITIONAL>(params);
+    cfg.channel = std::get<LIMITER_CHANNEL>(params);
+    cfg.enable = std::get<LIMITER_ENABLE>(params);
+    cfg.linkGroup = std::get<LIMITER_LINK_GROUP>(params);
+    cfg.attackTimeMs = additional[LIMITER_ATTACK_TIME];
+    cfg.releaseTimeMs = additional[LIMITER_RELEASE_TIME];
+    cfg.ratio = additional[LIMITER_RATIO];
+    cfg.thresholdDb = additional[LIMITER_THRESHOLD];
+    cfg.postGainDb = additional[LIMITER_POST_GAIN];
+}
+
+class DynamicsProcessingTestLimiterConfig
+    : public ::testing::TestWithParam<LimiterConfigTestParams>,
+      public DynamicsProcessingTestHelper {
+  public:
+    DynamicsProcessingTestLimiterConfig()
+        : DynamicsProcessingTestHelper(std::get<LIMITER_INSTANCE_NAME>(GetParam())),
+          mLimiterInUseEngine(std::get<LIMITER_ENGINE_IN_USE>(GetParam())) {
+        fillLimiterConfig(mCfg, GetParam());
+    }
+
+    void SetUp() override { SetUpDynamicsProcessingEffect(); }
+
+    void TearDown() override { TearDownDynamicsProcessingEffect(); }
+
+    DynamicsProcessing::LimiterConfig mCfg;
+    bool mLimiterInUseEngine;
+};
+
+TEST_P(DynamicsProcessingTestLimiterConfig, SetAndGetLimiterConfig) {
+    mEngineConfigPreset.limiterInUse = mLimiterInUseEngine;
+    EXPECT_NO_FATAL_FAILURE(addEngineConfig(mEngineConfigPreset));
+    EXPECT_NO_FATAL_FAILURE(addLimiterConfig({mCfg}));
+    SetAndGetDynamicsProcessingParameters();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        DynamicsProcessingTest, DynamicsProcessingTestLimiterConfig,
+        ::testing::Combine(testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
+                                   IFactory::descriptor, kDynamicsProcessingTypeUUID)),
+                           testing::Values(-1, 0, 1, 2),  // channel count
+                           testing::Bool(),               // enable
+                           testing::Values(3),            // link group
+                           testing::Bool(),               // engine limiter enable
+                           testing::ValuesIn(kLimiterConfigTestAdditionalParam)),  // Additional
+        [](const auto& info) {
+            auto descriptor = std::get<LIMITER_INSTANCE_NAME>(info.param).second;
+            DynamicsProcessing::LimiterConfig cfg;
+            fillLimiterConfig(cfg, info.param);
+            std::string engineLimiterInUse =
+                    std::to_string(std::get<LIMITER_ENGINE_IN_USE>(info.param));
+            std::string name = "Implementor_" + descriptor.common.implementor + "_name_" +
+                               descriptor.common.name + "_UUID_" +
+                               descriptor.common.id.uuid.toString() + "_limiterConfig_" +
+                               cfg.toString() + "_engineSetting_" + engineLimiterInUse;
+            std::replace_if(
+                    name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
+            return name;
+        });
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DynamicsProcessingTestLimiterConfig);
+
+/**
+ * Test DynamicsProcessing ChannelConfig
+ */
+enum ChannelConfigTestParamName {
+    BAND_CHANNEL_TEST_INSTANCE_NAME,
+    BAND_CHANNEL_TEST_CHANNEL_CONFIG,
+    BAND_CHANNEL_TEST_ENGINE_IN_USE
+};
+using ChannelConfigTestParams = std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>,
+                                           std::vector<DynamicsProcessing::ChannelConfig>, bool>;
+
+class DynamicsProcessingTestChannelConfig
+    : public ::testing::TestWithParam<ChannelConfigTestParams>,
+      public DynamicsProcessingTestHelper {
+  public:
+    DynamicsProcessingTestChannelConfig()
+        : DynamicsProcessingTestHelper(std::get<BAND_CHANNEL_TEST_INSTANCE_NAME>(GetParam())),
+          mCfg(std::get<BAND_CHANNEL_TEST_CHANNEL_CONFIG>(GetParam())),
+          mInUseEngine(std::get<BAND_CHANNEL_TEST_ENGINE_IN_USE>(GetParam())) {}
+
+    void SetUp() override { SetUpDynamicsProcessingEffect(); }
+
+    void TearDown() override { TearDownDynamicsProcessingEffect(); }
+
+    std::vector<DynamicsProcessing::ChannelConfig> mCfg;
+    const bool mInUseEngine;
+};
+
+TEST_P(DynamicsProcessingTestChannelConfig, SetAndGetPreEqChannelConfig) {
+    mEngineConfigPreset.preEqStage.inUse = mInUseEngine;
+    EXPECT_NO_FATAL_FAILURE(addEngineConfig(mEngineConfigPreset));
+    EXPECT_NO_FATAL_FAILURE(addPreEqChannelConfig(mCfg));
+    SetAndGetDynamicsProcessingParameters();
+}
+
+TEST_P(DynamicsProcessingTestChannelConfig, SetAndGetPostEqChannelConfig) {
+    mEngineConfigPreset.postEqStage.inUse = mInUseEngine;
+    EXPECT_NO_FATAL_FAILURE(addEngineConfig(mEngineConfigPreset));
+    EXPECT_NO_FATAL_FAILURE(addPostEqChannelConfig(mCfg));
+    SetAndGetDynamicsProcessingParameters();
+}
+
+TEST_P(DynamicsProcessingTestChannelConfig, SetAndGetMbcChannelConfig) {
+    mEngineConfigPreset.mbcStage.inUse = mInUseEngine;
+    EXPECT_NO_FATAL_FAILURE(addEngineConfig(mEngineConfigPreset));
+    EXPECT_NO_FATAL_FAILURE(addMbcChannelConfig(mCfg));
+    SetAndGetDynamicsProcessingParameters();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        DynamicsProcessingTest, DynamicsProcessingTestChannelConfig,
+        ::testing::Combine(
+                testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
+                        IFactory::descriptor, kDynamicsProcessingTypeUUID)),
+                testing::ValuesIn(
+                        DynamicsProcessingTestHelper::kChannelConfigTestSet),  // channel config
+                testing::Bool()),                                              // Engine inUse
+        [](const auto& info) {
+            auto descriptor = std::get<BAND_CHANNEL_TEST_INSTANCE_NAME>(info.param).second;
+            std::string engineInUse =
+                    std::to_string(std::get<BAND_CHANNEL_TEST_ENGINE_IN_USE>(info.param));
+            std::string channelConfig = ::android::internal::ToString(
+                    std::get<BAND_CHANNEL_TEST_CHANNEL_CONFIG>(info.param));
+
+            std::string name = "Implementor_" + descriptor.common.implementor + "_name_" +
+                               descriptor.common.name + "_UUID_" +
+                               descriptor.common.id.uuid.toString() + "_" + channelConfig +
+                               "_engineInUse_" + engineInUse;
+            std::replace_if(
+                    name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
+            return name;
+        });
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DynamicsProcessingTestChannelConfig);
+
+/**
+ * Test DynamicsProcessing EqBandConfig
+ */
+enum EqBandConfigTestParamName {
+    EQ_BAND_INSTANCE_NAME,
+    EQ_BAND_CHANNEL,
+    EQ_BAND_CHANNEL_ENABLE,
+    EQ_BAND_ENABLE,
+    EQ_BAND_CUT_OFF_FREQ,
+    EQ_BAND_GAIN,
+    EQ_BAND_STAGE_IN_USE
+};
+using EqBandConfigTestParams = std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>, int32_t,
+                                          std::vector<DynamicsProcessing::ChannelConfig>, bool,
+                                          std::vector<std::pair<int, float>>, float, bool>;
+
+void fillEqBandConfig(std::vector<DynamicsProcessing::EqBandConfig>& cfgs,
+                      const EqBandConfigTestParams& params) {
+    const std::vector<std::pair<int, float>> cutOffFreqs = std::get<EQ_BAND_CUT_OFF_FREQ>(params);
+    int bandCount = cutOffFreqs.size();
+    cfgs.resize(bandCount);
+    for (int i = 0; i < bandCount; i++) {
+        cfgs[i].channel = std::get<EQ_BAND_CHANNEL>(params);
+        cfgs[i].band = cutOffFreqs[i].first;
+        cfgs[i].enable = std::get<EQ_BAND_ENABLE>(params);
+        cfgs[i].cutoffFrequencyHz = cutOffFreqs[i].second;
+        cfgs[i].gainDb = std::get<EQ_BAND_GAIN>(params);
+    }
+}
+
+class DynamicsProcessingTestEqBandConfig : public ::testing::TestWithParam<EqBandConfigTestParams>,
+                                           public DynamicsProcessingTestHelper {
+  public:
+    DynamicsProcessingTestEqBandConfig()
+        : DynamicsProcessingTestHelper(std::get<EQ_BAND_INSTANCE_NAME>(GetParam())),
+          mStageInUse(std::get<EQ_BAND_STAGE_IN_USE>(GetParam())),
+          mChannelConfig(std::get<EQ_BAND_CHANNEL_ENABLE>(GetParam())) {
+        fillEqBandConfig(mCfgs, GetParam());
+    }
+
+    void SetUp() override { SetUpDynamicsProcessingEffect(); }
+
+    void TearDown() override { TearDownDynamicsProcessingEffect(); }
+
+    std::vector<DynamicsProcessing::EqBandConfig> mCfgs;
+    const bool mStageInUse;
+    const std::vector<DynamicsProcessing::ChannelConfig> mChannelConfig;
+};
+
+TEST_P(DynamicsProcessingTestEqBandConfig, SetAndGetPreEqBandConfig) {
+    mEngineConfigPreset.preEqStage.inUse = mStageInUse;
+    mEngineConfigPreset.preEqStage.bandCount = mCfgs.size();
+    EXPECT_NO_FATAL_FAILURE(addEngineConfig(mEngineConfigPreset));
+    EXPECT_NO_FATAL_FAILURE(addPreEqChannelConfig(mChannelConfig));
+    EXPECT_NO_FATAL_FAILURE(addPreEqBandConfigs(mCfgs));
+    SetAndGetDynamicsProcessingParameters();
+}
+
+TEST_P(DynamicsProcessingTestEqBandConfig, SetAndGetPostEqBandConfig) {
+    mEngineConfigPreset.postEqStage.inUse = mStageInUse;
+    mEngineConfigPreset.postEqStage.bandCount = mCfgs.size();
+    EXPECT_NO_FATAL_FAILURE(addEngineConfig(mEngineConfigPreset));
+    EXPECT_NO_FATAL_FAILURE(addPostEqChannelConfig(mChannelConfig));
+    EXPECT_NO_FATAL_FAILURE(addPostEqBandConfigs(mCfgs));
+    SetAndGetDynamicsProcessingParameters();
+}
+
+std::vector<std::vector<std::pair<int, float>>> kBands{
+        {
+                {0, 600},
+                {1, 2000},
+                {2, 6000},
+                {3, 10000},
+                {4, 16000},
+        },  // 5 bands
+        {
+                {0, 800},
+                {3, 15000},
+                {2, 6000},
+                {1, 2000},
+        },  // 4 bands, unsorted
+        {
+                {0, 650},
+                {1, 2000},
+                {2, 6000},
+                {3, 10000},
+                {3, 16000},
+        },  // 5 bands, missing band
+        {
+                {0, 900},
+                {1, 8000},
+                {2, 4000},
+                {3, 12000},
+        },  // 4 bands, cutoff freq not increasing
+        {
+                {0, 450},
+                {1, 2000},
+                {7, 6000},
+                {3, 10000},
+                {4, 16000},
+        },  // bad band index
+        {
+                {0, 1},
+                {1, 8000},
+        },  // too low cutoff freq
+        {
+                {0, 1200},
+                {1, 80000},
+        },  // too high cutoff freq
+};
+
+INSTANTIATE_TEST_SUITE_P(
+        DynamicsProcessingTest, DynamicsProcessingTestEqBandConfig,
+        ::testing::Combine(
+                testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
+                        IFactory::descriptor, kDynamicsProcessingTypeUUID)),
+                testing::Values(-1, 0, 10),  // channel ID
+                testing::ValuesIn(
+                        DynamicsProcessingTestHelper::kChannelConfigTestSet),  // channel enable
+                testing::Bool(),                                               // band enable
+                testing::ValuesIn(kBands),       // cut off frequencies
+                testing::Values(-3.14f, 3.14f),  // gain
+                testing::Bool()),                // stage in use
+        [](const auto& info) {
+            auto descriptor = std::get<EQ_BAND_INSTANCE_NAME>(info.param).second;
+            std::vector<DynamicsProcessing::EqBandConfig> cfgs;
+            fillEqBandConfig(cfgs, info.param);
+            std::string enable =
+                    ::android::internal::ToString(std::get<EQ_BAND_CHANNEL_ENABLE>(info.param));
+            std::string bands = ::android::internal::ToString(cfgs);
+            std::string stageInUse = std::to_string(std::get<EQ_BAND_STAGE_IN_USE>(info.param));
+            std::string name = "Implementor_" + descriptor.common.implementor + "_name_" +
+                               descriptor.common.name + "_UUID_" +
+                               descriptor.common.id.uuid.toString() + "_" + enable + "_bands_" +
+                               bands + "_stageInUse_" + stageInUse;
+            std::replace_if(
+                    name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
+            return name;
+        });
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DynamicsProcessingTestEqBandConfig);
+
+/**
+ * Test DynamicsProcessing MbcBandConfig
+ */
+
+enum MbcBandConfigParamName {
+    MBC_BAND_INSTANCE_NAME,
+    MBC_BAND_CHANNEL,
+    MBC_BAND_CHANNEL_CONFIG,
+    MBC_BAND_ENABLE,
+    MBC_BAND_CUTOFF_FREQ,
+    MBC_BAND_STAGE_IN_USE,
+    MBC_BAND_ADDITIONAL
+};
+enum MbcBandConfigAdditional {
+    MBC_ADD_ATTACK_TIME,
+    MBC_ADD_RELEASE_TIME,
+    MBC_ADD_RATIO,
+    MBC_ADD_THRESHOLD,
+    MBC_ADD_KNEE_WIDTH,
+    MBC_ADD_NOISE_GATE_THRESHOLD,
+    MBC_ADD_EXPENDER_RATIO,
+    MBC_ADD_PRE_GAIN,
+    MBC_ADD_POST_GAIN,
+    MBC_ADD_MAX_NUM
+};
+using TestParamsMbcBandConfigAdditional = std::array<float, MBC_ADD_MAX_NUM>;
+
+// attachTime, releaseTime, ratio, thresh, kneeWidth, noise, expander, preGain, postGain
+static constexpr std::array<TestParamsMbcBandConfigAdditional, 4> kMbcBandConfigAdditionalParam = {
+        {{-3, -10, -2, -2, -5, -90, -2.5, -2, -2},
+         {0, 0, 0, 0, 0, 0, 0, 0, 0},
+         {-3, 10, -2, 2, -5, 90, -2.5, 2, -2},
+         {3, 10, 2, 2, 5, 90, 2.5, 2, 2}}};
+
+using TestParamsMbcBandConfig =
+        std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>, int32_t,
+                   std::vector<DynamicsProcessing::ChannelConfig>, bool,
+                   std::vector<std::pair<int, float>>, bool, TestParamsMbcBandConfigAdditional>;
+
+void fillMbcBandConfig(std::vector<DynamicsProcessing::MbcBandConfig>& cfgs,
+                       const TestParamsMbcBandConfig& params) {
+    const std::vector<std::pair<int, float>> cutOffFreqs = std::get<MBC_BAND_CUTOFF_FREQ>(params);
+    const std::array<float, MBC_ADD_MAX_NUM> additional = std::get<MBC_BAND_ADDITIONAL>(params);
+    int bandCount = cutOffFreqs.size();
+    cfgs.resize(bandCount);
+    for (int i = 0; i < bandCount; i++) {
+        cfgs[i] = DynamicsProcessing::MbcBandConfig{
+                .channel = std::get<MBC_BAND_CHANNEL>(params),
+                .band = cutOffFreqs[i].first,
+                .enable = std::get<MBC_BAND_ENABLE>(params),
+                .cutoffFrequencyHz = cutOffFreqs[i].second,
+                .attackTimeMs = additional[MBC_ADD_ATTACK_TIME],
+                .releaseTimeMs = additional[MBC_ADD_RELEASE_TIME],
+                .ratio = additional[MBC_ADD_RATIO],
+                .thresholdDb = additional[MBC_ADD_THRESHOLD],
+                .kneeWidthDb = additional[MBC_ADD_KNEE_WIDTH],
+                .noiseGateThresholdDb = additional[MBC_ADD_NOISE_GATE_THRESHOLD],
+                .expanderRatio = additional[MBC_ADD_EXPENDER_RATIO],
+                .preGainDb = additional[MBC_ADD_PRE_GAIN],
+                .postGainDb = additional[MBC_ADD_POST_GAIN]};
+    }
+}
+
+class DynamicsProcessingTestMbcBandConfig
+    : public ::testing::TestWithParam<TestParamsMbcBandConfig>,
+      public DynamicsProcessingTestHelper {
+  public:
+    DynamicsProcessingTestMbcBandConfig()
+        : DynamicsProcessingTestHelper(std::get<MBC_BAND_INSTANCE_NAME>(GetParam())),
+          mStageInUse(std::get<MBC_BAND_STAGE_IN_USE>(GetParam())),
+          mChannelConfig(std::get<MBC_BAND_CHANNEL_CONFIG>(GetParam())) {
+        fillMbcBandConfig(mCfgs, GetParam());
+    }
+
+    void SetUp() override { SetUpDynamicsProcessingEffect(); }
+
+    void TearDown() override { TearDownDynamicsProcessingEffect(); }
+
+    std::vector<DynamicsProcessing::MbcBandConfig> mCfgs;
+    const bool mStageInUse;
+    const std::vector<DynamicsProcessing::ChannelConfig> mChannelConfig;
+};
+
+TEST_P(DynamicsProcessingTestMbcBandConfig, SetAndGetMbcBandConfig) {
+    mEngineConfigPreset.mbcStage.inUse = mStageInUse;
+    mEngineConfigPreset.mbcStage.bandCount = mCfgs.size();
+    EXPECT_NO_FATAL_FAILURE(addEngineConfig(mEngineConfigPreset));
+    EXPECT_NO_FATAL_FAILURE(addMbcChannelConfig(mChannelConfig));
+    EXPECT_NO_FATAL_FAILURE(addMbcBandConfigs(mCfgs));
+    SetAndGetDynamicsProcessingParameters();
+}
+
+INSTANTIATE_TEST_SUITE_P(
+        DynamicsProcessingTest, DynamicsProcessingTestMbcBandConfig,
+        ::testing::Combine(
+                testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
+                        IFactory::descriptor, kDynamicsProcessingTypeUUID)),
+                testing::Values(-1, 0, 10),  // channel count
+                testing::ValuesIn(
+                        DynamicsProcessingTestHelper::kChannelConfigTestSet),  // channel config
+                testing::Bool(),                                               // enable
+                testing::ValuesIn(kBands),                          // cut off frequencies
+                testing::Bool(),                                    // stage in use
+                testing::ValuesIn(kMbcBandConfigAdditionalParam)),  // Additional
+        [](const auto& info) {
+            auto descriptor = std::get<MBC_BAND_INSTANCE_NAME>(info.param).second;
+            std::vector<DynamicsProcessing::MbcBandConfig> cfgs;
+            fillMbcBandConfig(cfgs, info.param);
+            std::string enable =
+                    ::android::internal::ToString(std::get<MBC_BAND_CHANNEL_CONFIG>(info.param));
+            std::string mbcBands = ::android::internal::ToString(cfgs);
+            std::string stageInUse = std::to_string(std::get<MBC_BAND_STAGE_IN_USE>(info.param));
+            std::string name = "Implementor_" + descriptor.common.implementor + "_name_" +
+                               descriptor.common.name + "_UUID_" +
+                               descriptor.common.id.uuid.toString() + "_enable_" + enable +
+                               "_bands_" + mbcBands + "_stageInUse_" + stageInUse;
+            std::replace_if(
+                    name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
+            return name;
+        });
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(DynamicsProcessingTestMbcBandConfig);
+
+int main(int argc, char** argv) {
+    ::testing::InitGoogleTest(&argc, argv);
+    ABinderProcess_setThreadPoolMaxThreadCount(1);
+    ABinderProcess_startThreadPool();
+    return RUN_ALL_TESTS();
+}
diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml
index 568d94c..71afee3 100644
--- a/compatibility_matrices/compatibility_matrix.current.xml
+++ b/compatibility_matrices/compatibility_matrix.current.xml
@@ -590,6 +590,16 @@
         </interface>
     </hal>
     <hal format="aidl" optional="true">
+        <name>android.hardware.radio.sap</name>
+        <version>1</version>
+        <interface>
+            <name>ISap</name>
+            <instance>slot1</instance>
+            <instance>slot2</instance>
+            <instance>slot3</instance>
+        </interface>
+    </hal>
+    <hal format="aidl" optional="true">
         <name>android.hardware.radio.voice</name>
         <version>1</version>
         <interface>
diff --git a/graphics/Android.bp b/graphics/Android.bp
index 4c51454..4ae7ec7 100644
--- a/graphics/Android.bp
+++ b/graphics/Android.bp
@@ -21,6 +21,9 @@
     static_libs: [
         "android.hardware.graphics.allocator-V2-ndk",
     ],
+    defaults: [
+        "android.hardware.graphics.common-ndk_static",
+    ],
 }
 
 cc_defaults {
@@ -28,6 +31,9 @@
     shared_libs: [
         "android.hardware.graphics.allocator-V2-ndk",
     ],
+    defaults: [
+        "android.hardware.graphics.common-ndk_shared",
+    ],
 }
 
 cc_defaults {
diff --git a/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
index 980e246..49c2497 100644
--- a/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
+++ b/graphics/allocator/aidl/aidl_api/android.hardware.graphics.allocator/current/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
@@ -41,4 +41,5 @@
   android.hardware.graphics.common.PixelFormat format = android.hardware.graphics.common.PixelFormat.UNSPECIFIED;
   android.hardware.graphics.common.BufferUsage usage = android.hardware.graphics.common.BufferUsage.CPU_READ_NEVER;
   long reservedSize;
+  android.hardware.graphics.common.ExtendableType[] additionalOptions;
 }
diff --git a/graphics/allocator/aidl/android/hardware/graphics/allocator/AllocationResult.aidl b/graphics/allocator/aidl/android/hardware/graphics/allocator/AllocationResult.aidl
index 0774e25..76f1c95 100644
--- a/graphics/allocator/aidl/android/hardware/graphics/allocator/AllocationResult.aidl
+++ b/graphics/allocator/aidl/android/hardware/graphics/allocator/AllocationResult.aidl
@@ -18,13 +18,17 @@
 
 import android.hardware.common.NativeHandle;
 
- /**
+/**
  * Result of an IAllocator::allocate call.
  *
  * @sa +ndk libnativewindow#AHardwareBuffer_Desc
  */
 @VintfStability
 parcelable AllocationResult {
+    /**
+     * The number of pixels between two consecutive rows of an allocated buffer, when the concept
+     * of consecutive rows is defined. Otherwise, it has no meaning.
+     */
     int stride;
     NativeHandle[] buffers;
-}
\ No newline at end of file
+}
diff --git a/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl b/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
index ffc50b8..50aa2b7 100644
--- a/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
+++ b/graphics/allocator/aidl/android/hardware/graphics/allocator/BufferDescriptorInfo.aidl
@@ -17,6 +17,7 @@
 package android.hardware.graphics.allocator;
 
 import android.hardware.graphics.common.BufferUsage;
+import android.hardware.graphics.common.ExtendableType;
 import android.hardware.graphics.common.PixelFormat;
 
 @VintfStability
@@ -54,6 +55,9 @@
     /**
      * Buffer usage mask; valid flags can be found in the definition of
      * BufferUsage.aidl in graphics/common
+     *
+     * The allocator must report isSupported() == false and reject any allocations
+     * with unrecognized buffer usages.
      */
     BufferUsage usage = BufferUsage.CPU_READ_NEVER;
 
@@ -62,4 +66,16 @@
      * See getReservedRegion for more information.
      */
     long reservedSize;
+
+    /**
+     * Extensible additional options that can be set.
+     *
+     * This is intended for options that do not change the overall usage, but which do impact
+     * how a buffer is allocated. An example of this is compression level, such as for
+     * the EGL_EXT_surface_compression extension.
+     *
+     * The allocator must report isSupported() == false and reject any allocations
+     * with unrecognized options.
+     */
+    ExtendableType[] additionalOptions;
 }
diff --git a/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp b/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp
index 09f1c15..4778020 100644
--- a/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp
+++ b/graphics/allocator/aidl/vts/VtsHalGraphicsAllocatorAidl_TargetTest.cpp
@@ -217,6 +217,8 @@
         }
         return ret;
     }
+
+    int32_t allocatorVersion() const { return mIAllocatorVersion; }
 };
 
 BufferHandle::~BufferHandle() {
@@ -309,6 +311,62 @@
     EXPECT_GE(buffer->stride(), 64);
 }
 
+TEST_P(GraphicsAllocatorAidlTests, RejectsUnknownUsages) {
+    if (allocatorVersion() < 2) {
+        GTEST_SKIP() << "Must be version 2+";
+        return;
+    }
+
+    constexpr auto FirstInvalidV2Usage = static_cast<BufferUsage>(1LL << 33);
+
+    BufferUsage invalidUsage;
+    if (allocatorVersion() == 2) {
+        invalidUsage = FirstInvalidV2Usage;
+    } else {
+        GTEST_FAIL() << "Unknown version " << allocatorVersion();
+    }
+
+    BufferDescriptorInfo info{
+            .name = {"CPU_8888"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+            .reservedSize = 0,
+    };
+
+    // First make sure we can allocate a known usage buffer as expected
+    EXPECT_TRUE(isSupported(info));
+    EXPECT_TRUE(allocate(info));
+
+    // Now add the unknown bit and verify it's rejected
+    info.usage |= invalidUsage;
+    EXPECT_FALSE(isSupported(info)) << "isSupported() returned true for unknown-to-HAL usage";
+    EXPECT_FALSE(allocate(info)) << "allocate succeeded for unknown-to-HAL usage";
+}
+
+TEST_P(GraphicsAllocatorAidlTests, RejectsUnknownOptions) {
+    if (allocatorVersion() < 2) {
+        GTEST_SKIP() << "Must be version 2+";
+        return;
+    }
+
+    BufferDescriptorInfo info{
+            .name = {"CPU_8888"},
+            .width = 64,
+            .height = 64,
+            .layerCount = 1,
+            .format = PixelFormat::RGBA_8888,
+            .usage = BufferUsage::CPU_READ_OFTEN | BufferUsage::CPU_WRITE_OFTEN,
+            .reservedSize = 0,
+    };
+    info.additionalOptions.push_back({"android.hardware.graphics.common.NotARealOption", 1});
+
+    EXPECT_FALSE(isSupported(info)) << "isSupported() returned true for unknown-to-HAL option";
+    EXPECT_FALSE(allocate(info)) << "allocate succeeded for unknown-to-HAL option";
+}
+
 TEST_P(GraphicsFrontBufferTests, FrontBufferGpuToCpu) {
     BufferDescriptorInfo info{
             .name = {"CPU_8888"},
diff --git a/graphics/common/aidl/aidl_api/android.hardware.graphics.common/current/android/hardware/graphics/common/StandardMetadataType.aidl b/graphics/common/aidl/aidl_api/android.hardware.graphics.common/current/android/hardware/graphics/common/StandardMetadataType.aidl
index 06b31a2..6e2e106 100644
--- a/graphics/common/aidl/aidl_api/android.hardware.graphics.common/current/android/hardware/graphics/common/StandardMetadataType.aidl
+++ b/graphics/common/aidl/aidl_api/android.hardware.graphics.common/current/android/hardware/graphics/common/StandardMetadataType.aidl
@@ -58,4 +58,5 @@
   CTA861_3 = 20,
   SMPTE2094_40 = 21,
   SMPTE2094_10 = 22,
+  STRIDE = 23,
 }
diff --git a/graphics/common/aidl/android/hardware/graphics/common/StandardMetadataType.aidl b/graphics/common/aidl/android/hardware/graphics/common/StandardMetadataType.aidl
index 4bca795..8cfdae6 100644
--- a/graphics/common/aidl/android/hardware/graphics/common/StandardMetadataType.aidl
+++ b/graphics/common/aidl/android/hardware/graphics/common/StandardMetadataType.aidl
@@ -368,4 +368,17 @@
      * If this is unset when encoded into a byte stream, the byte stream is empty.
      */
     SMPTE2094_10 = 22,
+
+    /**
+     * Can be used to get the stride in pixels of the buffer allocation. This is the number of
+     * pixels between two consecutive rows of an allocated buffer, when the concept of consecutive
+     * rows is defined. Otherwise, it has no meaning.
+     *
+     * Must match the value returned in android.hardware.graphics.allocator.AllocationResult#stride
+     *
+     * This is required metadata in mapper5 and should be read-only.
+     *
+     * The metadata type is a uint32_t.
+     */
+    STRIDE = 23,
 }
diff --git a/graphics/composer/aidl/vts/VtsComposerClient.cpp b/graphics/composer/aidl/vts/VtsComposerClient.cpp
index 5bc7296..dd4161e 100644
--- a/graphics/composer/aidl/vts/VtsComposerClient.cpp
+++ b/graphics/composer/aidl/vts/VtsComposerClient.cpp
@@ -393,14 +393,31 @@
                 return {std::move(configs.first), vtsDisplays};
             }
             for (int config : configs.second) {
-                auto status = updateDisplayProperties(&vtsDisplay, config);
+                auto status = addDisplayConfig(&vtsDisplay, config);
                 if (!status.isOk()) {
-                    ALOGE("Unable to get the displays for test, failed to update the properties "
+                    ALOGE("Unable to get the displays for test, failed to add config "
                           "for display %" PRId64,
                           display);
                     return {std::move(status), vtsDisplays};
                 }
             }
+
+            auto config = getActiveConfig(display);
+            if (!config.first.isOk()) {
+                ALOGE("Unable to get the displays for test, failed to get active config "
+                      "for display %" PRId64, display);
+                return {std::move(config.first), vtsDisplays};
+            }
+
+            auto status = updateDisplayProperties(&vtsDisplay, config.second);
+            if (!status.isOk()) {
+                ALOGE("Unable to get the displays for test, "
+                      "failed to update the properties "
+                      "for display %" PRId64,
+                      display);
+                return {std::move(status), vtsDisplays};
+            }
+
             vtsDisplays.emplace_back(vtsDisplay);
             addDisplayToDisplayResources(display, /*isVirtual*/ false);
         }
@@ -409,7 +426,7 @@
     }
 }
 
-ScopedAStatus VtsComposerClient::updateDisplayProperties(VtsDisplay* vtsDisplay, int32_t config) {
+ScopedAStatus VtsComposerClient::addDisplayConfig(VtsDisplay* vtsDisplay, int32_t config) {
     const auto width =
             getDisplayAttribute(vtsDisplay->getDisplayId(), config, DisplayAttribute::WIDTH);
     const auto height =
@@ -420,7 +437,6 @@
             getDisplayAttribute(vtsDisplay->getDisplayId(), config, DisplayAttribute::CONFIG_GROUP);
     if (width.first.isOk() && height.first.isOk() && vsyncPeriod.first.isOk() &&
         configGroup.first.isOk()) {
-        vtsDisplay->setDimensions(width.second, height.second);
         vtsDisplay->addDisplayConfig(config, {vsyncPeriod.second, configGroup.second});
         return ScopedAStatus::ok();
     }
@@ -431,6 +447,21 @@
     return ScopedAStatus::fromServiceSpecificError(IComposerClient::EX_BAD_CONFIG);
 }
 
+ScopedAStatus VtsComposerClient::updateDisplayProperties(VtsDisplay* vtsDisplay, int32_t config) {
+    const auto width =
+            getDisplayAttribute(vtsDisplay->getDisplayId(), config, DisplayAttribute::WIDTH);
+    const auto height =
+            getDisplayAttribute(vtsDisplay->getDisplayId(), config, DisplayAttribute::HEIGHT);
+    if (width.first.isOk() && height.first.isOk()) {
+        vtsDisplay->setDimensions(width.second, height.second);
+        return ScopedAStatus::ok();
+    }
+
+    LOG(ERROR) << "Failed to update display property for width: " << width.first.isOk()
+               << ", height: " << height.first.isOk();
+    return ScopedAStatus::fromServiceSpecificError(IComposerClient::EX_BAD_CONFIG);
+}
+
 ScopedAStatus VtsComposerClient::addDisplayToDisplayResources(int64_t display, bool isVirtual) {
     if (mDisplayResources.insert({display, DisplayResource(isVirtual)}).second) {
         return ScopedAStatus::ok();
diff --git a/graphics/composer/aidl/vts/VtsComposerClient.h b/graphics/composer/aidl/vts/VtsComposerClient.h
index 3625c8c..74c5da5 100644
--- a/graphics/composer/aidl/vts/VtsComposerClient.h
+++ b/graphics/composer/aidl/vts/VtsComposerClient.h
@@ -173,6 +173,7 @@
     std::pair<ScopedAStatus, std::vector<VtsDisplay>> getDisplays();
 
   private:
+    ScopedAStatus addDisplayConfig(VtsDisplay* vtsDisplay, int32_t config);
     ScopedAStatus updateDisplayProperties(VtsDisplay* vtsDisplay, int32_t config);
 
     ScopedAStatus addDisplayToDisplayResources(int64_t display, bool isVirtual);
diff --git a/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h
index 25af6d1..3dba86d 100644
--- a/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h
+++ b/graphics/mapper/stable-c/implutils/include/android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h
@@ -563,6 +563,7 @@
 DEFINE_TYPE(CTA861_3, std::optional<Cta861_3>);
 DEFINE_TYPE(SMPTE2094_10, std::optional<std::vector<uint8_t>>);
 DEFINE_TYPE(SMPTE2094_40, std::optional<std::vector<uint8_t>>);
+DEFINE_TYPE(STRIDE, uint32_t);
 
 #undef DEFINE_TYPE
 
diff --git a/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp b/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp
index 85246ee..e29d16e 100644
--- a/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp
+++ b/graphics/mapper/stable-c/vts/VtsHalGraphicsMapperStableC_TargetTest.cpp
@@ -1557,6 +1557,16 @@
     }
 }
 
+TEST_P(GraphicsMapperStableCTests, GetStride) {
+    auto buffer = allocateGeneric();
+    ASSERT_TRUE(buffer);
+    auto bufferHandle = buffer->import();
+    ASSERT_TRUE(bufferHandle);
+    auto value = getStandardMetadata<StandardMetadataType::STRIDE>(*bufferHandle);
+    ASSERT_TRUE(value.has_value());
+    EXPECT_EQ(buffer->stride(), *value);
+}
+
 TEST_P(GraphicsMapperStableCTests, SupportsRequiredGettersSetters) {
     auto buffer = allocateGeneric();
     ASSERT_TRUE(buffer);
@@ -1587,6 +1597,7 @@
             StandardMetadataType::BLEND_MODE,
             StandardMetadataType::SMPTE2086,
             StandardMetadataType::CTA861_3,
+            StandardMetadataType::STRIDE,
     };
 
     std::vector<StandardMetadataType> requiredSetters = {
diff --git a/radio/aidl/Android.bp b/radio/aidl/Android.bp
index 80ad14a..08bcc28 100644
--- a/radio/aidl/Android.bp
+++ b/radio/aidl/Android.bp
@@ -153,6 +153,28 @@
 }
 
 aidl_interface {
+    name: "android.hardware.radio.sap",
+    vendor_available: true,
+    host_supported: true,
+    srcs: ["android/hardware/radio/sap/*.aidl"],
+    stability: "vintf",
+    backend: {
+        cpp: {
+            enabled: true,
+        },
+        java: {
+            apex_available: [
+                "//apex_available:platform",
+                "com.android.btservices",
+            ],
+            sdk_version: "module_current",
+            min_sdk_version: "Tiramisu",
+        },
+    },
+
+}
+
+aidl_interface {
     name: "android.hardware.radio.sim",
     vendor_available: true,
     host_supported: true,
diff --git a/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/ISap.aidl b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/ISap.aidl
new file mode 100644
index 0000000..37391e9
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/ISap.aidl
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.radio.sap;
+@VintfStability
+interface ISap {
+  oneway void apduReq(in int serial, in android.hardware.radio.sap.SapApduType type, in byte[] command);
+  oneway void connectReq(in int serial, in int maxMsgSizeBytes);
+  oneway void disconnectReq(in int serial);
+  oneway void powerReq(in int serial, in boolean powerOn);
+  oneway void resetSimReq(in int serial);
+  oneway void setCallback(in android.hardware.radio.sap.ISapCallback sapCallback);
+  oneway void setTransferProtocolReq(in int serial, in android.hardware.radio.sap.SapTransferProtocol transferProtocol);
+  oneway void transferAtrReq(in int serial);
+  oneway void transferCardReaderStatusReq(in int serial);
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/ISapCallback.aidl b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/ISapCallback.aidl
new file mode 100644
index 0000000..d507709
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/ISapCallback.aidl
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.radio.sap;
+@VintfStability
+interface ISapCallback {
+  oneway void apduResponse(in int serial, in android.hardware.radio.sap.SapResultCode resultCode, in byte[] apduRsp);
+  oneway void connectResponse(in int serial, in android.hardware.radio.sap.SapConnectRsp sapConnectRsp, in int maxMsgSizeBytes);
+  oneway void disconnectIndication(in int serial, in android.hardware.radio.sap.SapDisconnectType disconnectType);
+  oneway void disconnectResponse(in int serial);
+  oneway void errorResponse(in int serial);
+  oneway void powerResponse(in int serial, in android.hardware.radio.sap.SapResultCode resultCode);
+  oneway void resetSimResponse(in int serial, in android.hardware.radio.sap.SapResultCode resultCode);
+  oneway void statusIndication(in int serial, in android.hardware.radio.sap.SapStatus status);
+  oneway void transferAtrResponse(in int serial, in android.hardware.radio.sap.SapResultCode resultCode, in byte[] atr);
+  oneway void transferCardReaderStatusResponse(in int serial, in android.hardware.radio.sap.SapResultCode resultCode, in int cardReaderStatus);
+  oneway void transferProtocolResponse(in int serial, in android.hardware.radio.sap.SapResultCode resultCode);
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapApduType.aidl b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapApduType.aidl
new file mode 100644
index 0000000..e0e2a03
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapApduType.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.radio.sap;
+@Backing(type="int") @VintfStability
+enum SapApduType {
+  APDU = 0,
+  APDU7816 = 1,
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapConnectRsp.aidl b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapConnectRsp.aidl
new file mode 100644
index 0000000..aceac37
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapConnectRsp.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.radio.sap;
+@Backing(type="int") @VintfStability
+enum SapConnectRsp {
+  SUCCESS = 0,
+  CONNECT_FAILURE = 1,
+  MSG_SIZE_TOO_LARGE = 2,
+  MSG_SIZE_TOO_SMALL = 3,
+  CONNECT_OK_CALL_ONGOING = 4,
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapDisconnectType.aidl b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapDisconnectType.aidl
new file mode 100644
index 0000000..0447f9b
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapDisconnectType.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.radio.sap;
+@Backing(type="int") @VintfStability
+enum SapDisconnectType {
+  GRACEFUL = 0,
+  IMMEDIATE = 1,
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapResultCode.aidl b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapResultCode.aidl
new file mode 100644
index 0000000..1db226b
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapResultCode.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.radio.sap;
+@Backing(type="int") @VintfStability
+enum SapResultCode {
+  SUCCESS = 0,
+  GENERIC_FAILURE = 1,
+  CARD_NOT_ACCESSSIBLE = 2,
+  CARD_ALREADY_POWERED_OFF = 3,
+  CARD_REMOVED = 4,
+  CARD_ALREADY_POWERED_ON = 5,
+  DATA_NOT_AVAILABLE = 6,
+  NOT_SUPPORTED = 7,
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapStatus.aidl b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapStatus.aidl
new file mode 100644
index 0000000..32f71c2
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapStatus.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.radio.sap;
+@Backing(type="int") @VintfStability
+enum SapStatus {
+  UNKNOWN_ERROR = 0,
+  CARD_RESET = 1,
+  CARD_NOT_ACCESSIBLE = 2,
+  CARD_REMOVED = 3,
+  CARD_INSERTED = 4,
+  RECOVERED = 5,
+}
diff --git a/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapTransferProtocol.aidl b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapTransferProtocol.aidl
new file mode 100644
index 0000000..e3ffdb0
--- /dev/null
+++ b/radio/aidl/aidl_api/android.hardware.radio.sap/current/android/hardware/radio/sap/SapTransferProtocol.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.hardware.radio.sap;
+@Backing(type="int") @VintfStability
+enum SapTransferProtocol {
+  T0 = 0,
+  T1 = 1,
+}
diff --git a/radio/aidl/android/hardware/radio/sap/ISap.aidl b/radio/aidl/android/hardware/radio/sap/ISap.aidl
new file mode 100644
index 0000000..552e602
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/sap/ISap.aidl
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.radio.sap;
+
+import android.hardware.radio.sap.ISapCallback;
+import android.hardware.radio.sap.SapApduType;
+import android.hardware.radio.sap.SapTransferProtocol;
+
+@VintfStability
+oneway interface ISap {
+    /**
+     * TRANSFER_APDU_REQ from SAP 1.1 spec 5.1.6
+     *
+     * @param serial Id to match req-resp. Resp must include same serial.
+     * @param type APDU command type
+     * @param command CommandAPDU/CommandAPDU7816 parameter depending on type
+     */
+    void apduReq(in int serial, in SapApduType type, in byte[] command);
+
+    /**
+     * CONNECT_REQ from SAP 1.1 spec 5.1.1
+     *
+     * @param serial Id to match req-resp. Resp must include same serial.
+     * @param maxMsgSizeBytes MaxMsgSize to be used for SIM Access Profile connection
+     */
+    void connectReq(in int serial, in int maxMsgSizeBytes);
+
+    /**
+     * DISCONNECT_REQ from SAP 1.1 spec 5.1.3
+     *
+     * @param serial Id to match req-resp. Resp must include same serial.
+     */
+    void disconnectReq(in int serial);
+
+    /**
+     * POWER_SIM_OFF_REQ and POWER_SIM_ON_REQ from SAP 1.1 spec 5.1.10 + 5.1.12
+     *
+     * @param serial Id to match req-resp. Resp must include same serial.
+     * @param powerOn true for on, false for off
+     */
+    void powerReq(in int serial, in boolean powerOn);
+
+    /**
+     * RESET_SIM_REQ from SAP 1.1 spec 5.1.14
+     *
+     * @param serial Id to match req-resp. Resp must include same serial.
+     */
+    void resetSimReq(in int serial);
+
+    /**
+     * Set callback that has response and unsolicited indication functions
+     *
+     * @param sapCallback Object containing response and unosolicited indication callbacks
+     */
+    void setCallback(in ISapCallback sapCallback);
+
+    /**
+     * SET_TRANSPORT_PROTOCOL_REQ from SAP 1.1 spec 5.1.20
+     *
+     * @param serial Id to match req-resp. Resp must include same serial.
+     * @param transferProtocol Transport Protocol
+     */
+    void setTransferProtocolReq(in int serial, in SapTransferProtocol transferProtocol);
+
+    /**
+     * TRANSFER_ATR_REQ from SAP 1.1 spec 5.1.8
+     *
+     * @param serial Id to match req-resp. Resp must include same serial.
+     */
+    void transferAtrReq(in int serial);
+
+    /**
+     * TRANSFER_CARD_READER_STATUS_REQ from SAP 1.1 spec 5.1.17
+     *
+     * @param serial Id to match req-resp. Resp must include same serial.
+     */
+    void transferCardReaderStatusReq(in int serial);
+}
diff --git a/radio/aidl/android/hardware/radio/sap/ISapCallback.aidl b/radio/aidl/android/hardware/radio/sap/ISapCallback.aidl
new file mode 100644
index 0000000..34111eb
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/sap/ISapCallback.aidl
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.radio.sap;
+
+import android.hardware.radio.sap.SapConnectRsp;
+import android.hardware.radio.sap.SapDisconnectType;
+import android.hardware.radio.sap.SapResultCode;
+import android.hardware.radio.sap.SapStatus;
+
+@VintfStability
+oneway interface ISapCallback {
+    /**
+     * TRANSFER_APDU_RESP from SAP 1.1 spec 5.1.7
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     * @param resultCode ResultCode to indicate if command was processed correctly
+     *        Possible values:
+     *        SapResultCode:SUCCESS,
+     *        SapResultCode:GENERIC_FAILURE,
+     *        SapResultCode:CARD_NOT_ACCESSSIBLE,
+     *        SapResultCode:CARD_ALREADY_POWERED_OFF,
+     *        SapResultCode:CARD_REMOVED
+     * @param apduRsp APDU Response. Valid only if command was processed correctly and no error
+     *        occurred.
+     */
+    void apduResponse(in int serial, in SapResultCode resultCode, in byte[] apduRsp);
+
+    /**
+     * CONNECT_RESP from SAP 1.1 spec 5.1.2
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     * @param sapConnectRsp Connection Status
+     * @param maxMsgSizeBytes MaxMsgSize supported by server if request cannot be fulfilled.
+     *        Valid only if connectResponse is SapConnectResponse:MSG_SIZE_TOO_LARGE.
+     */
+    void connectResponse(in int serial, in SapConnectRsp sapConnectRsp, in int maxMsgSizeBytes);
+
+    /**
+     * DISCONNECT_IND from SAP 1.1 spec 5.1.5
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     * @param disconnectType Disconnect Type to indicate if shutdown is graceful or immediate
+     */
+    void disconnectIndication(in int serial, in SapDisconnectType disconnectType);
+
+    /**
+     * DISCONNECT_RESP from SAP 1.1 spec 5.1.4
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     */
+    void disconnectResponse(in int serial);
+
+    /**
+     * ERROR_RESP from SAP 1.1 spec 5.1.19
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     */
+    void errorResponse(in int serial);
+
+    /**
+     * POWER_SIM_OFF_RESP and POWER_SIM_ON_RESP from SAP 1.1 spec 5.1.11 + 5.1.13
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     * @param resultCode ResultCode to indicate if command was processed correctly
+     *        Possible values:
+     *        SapResultCode:SUCCESS,
+     *        SapResultCode:GENERIC_FAILURE,
+     *        SapResultCode:CARD_NOT_ACCESSSIBLE, (possible only for power on req)
+     *        SapResultCode:CARD_ALREADY_POWERED_OFF, (possible only for power off req)
+     *        SapResultCode:CARD_REMOVED,
+     *        SapResultCode:CARD_ALREADY_POWERED_ON (possible only for power on req)
+     */
+    void powerResponse(in int serial, in SapResultCode resultCode);
+
+    /**
+     * RESET_SIM_RESP from SAP 1.1 spec 5.1.15
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     * @param resultCode ResultCode to indicate if command was processed correctly
+     *        Possible values:
+     *        SapResultCode:SUCCESS,
+     *        SapResultCode:GENERIC_FAILURE,
+     *        SapResultCode:CARD_NOT_ACCESSSIBLE,
+     *        SapResultCode:CARD_ALREADY_POWERED_OFF,
+     *        SapResultCode:CARD_REMOVED
+     */
+    void resetSimResponse(in int serial, in SapResultCode resultCode);
+
+    /**
+     * STATUS_IND from SAP 1.1 spec 5.1.16
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     * @param status Parameter to indicate reason for the status change.
+     */
+    void statusIndication(in int serial, in SapStatus status);
+
+    /**
+     * TRANSFER_ATR_RESP from SAP 1.1 spec 5.1.9
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     * @param resultCode ResultCode to indicate if command was processed correctly
+     *        Possible values:
+     *        SapResultCode:SUCCESS,
+     *        SapResultCode:GENERIC_FAILURE,
+     *        SapResultCode:CARD_ALREADY_POWERED_OFF,
+     *        SapResultCode:CARD_REMOVED,
+     *        SapResultCode:DATA_NOT_AVAILABLE
+     * @param atr Answer to Reset from the subscription module. Included only if no error occurred,
+     *        otherwise empty.
+     */
+    void transferAtrResponse(in int serial, in SapResultCode resultCode, in byte[] atr);
+
+    /**
+     * TRANSFER_CARD_READER_STATUS_REQ from SAP 1.1 spec 5.1.18
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     * @param resultCode ResultCode to indicate if command was processed correctly
+     *        Possible values:
+     *        SapResultCode:SUCCESS,
+     *        SapResultCode:GENERIC_FAILURE
+     *        SapResultCode:DATA_NOT_AVAILABLE
+     * @param cardReaderStatus Card Reader Status coded as described in 3GPP TS 11.14 Section 12.33
+     *        and TS 31.111 Section 8.33
+     */
+    void transferCardReaderStatusResponse(
+            in int serial, in SapResultCode resultCode, in int cardReaderStatus);
+
+    /**
+     * SET_TRANSPORT_PROTOCOL_RESP from SAP 1.1 spec 5.1.21
+     *
+     * @param serial Id to match req-resp. Value must match the one in req.
+     * @param resultCode ResultCode to indicate if command was processed correctly
+     *        Possible values:
+     *        SapResultCode:SUCCESS
+     *        SapResultCode:NOT_SUPPORTED
+     */
+    void transferProtocolResponse(in int serial, in SapResultCode resultCode);
+}
diff --git a/radio/aidl/android/hardware/radio/sap/SapApduType.aidl b/radio/aidl/android/hardware/radio/sap/SapApduType.aidl
new file mode 100644
index 0000000..ab8ffb9
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/sap/SapApduType.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.radio.sap;
+
+@VintfStability
+@Backing(type="int")
+enum SapApduType {
+    APDU,
+    APDU7816,
+}
diff --git a/radio/aidl/android/hardware/radio/sap/SapConnectRsp.aidl b/radio/aidl/android/hardware/radio/sap/SapConnectRsp.aidl
new file mode 100644
index 0000000..0e1d528
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/sap/SapConnectRsp.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.radio.sap;
+
+@VintfStability
+@Backing(type="int")
+enum SapConnectRsp {
+    SUCCESS,
+    CONNECT_FAILURE,
+    MSG_SIZE_TOO_LARGE,
+    MSG_SIZE_TOO_SMALL,
+    CONNECT_OK_CALL_ONGOING,
+}
diff --git a/radio/aidl/android/hardware/radio/sap/SapDisconnectType.aidl b/radio/aidl/android/hardware/radio/sap/SapDisconnectType.aidl
new file mode 100644
index 0000000..16d7cc6
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/sap/SapDisconnectType.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.radio.sap;
+
+@VintfStability
+@Backing(type="int")
+enum SapDisconnectType {
+    GRACEFUL,
+    IMMEDIATE,
+}
diff --git a/radio/aidl/android/hardware/radio/sap/SapResultCode.aidl b/radio/aidl/android/hardware/radio/sap/SapResultCode.aidl
new file mode 100644
index 0000000..4728ebe
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/sap/SapResultCode.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.radio.sap;
+
+@VintfStability
+@Backing(type="int")
+enum SapResultCode {
+    SUCCESS,
+    GENERIC_FAILURE,
+    CARD_NOT_ACCESSSIBLE,
+    CARD_ALREADY_POWERED_OFF,
+    CARD_REMOVED,
+    CARD_ALREADY_POWERED_ON,
+    DATA_NOT_AVAILABLE,
+    NOT_SUPPORTED,
+}
diff --git a/radio/aidl/android/hardware/radio/sap/SapStatus.aidl b/radio/aidl/android/hardware/radio/sap/SapStatus.aidl
new file mode 100644
index 0000000..309352d
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/sap/SapStatus.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.radio.sap;
+
+@VintfStability
+@Backing(type="int")
+enum SapStatus {
+    UNKNOWN_ERROR,
+    CARD_RESET,
+    CARD_NOT_ACCESSIBLE,
+    CARD_REMOVED,
+    CARD_INSERTED,
+    RECOVERED,
+}
diff --git a/radio/aidl/android/hardware/radio/sap/SapTransferProtocol.aidl b/radio/aidl/android/hardware/radio/sap/SapTransferProtocol.aidl
new file mode 100644
index 0000000..823c5f2
--- /dev/null
+++ b/radio/aidl/android/hardware/radio/sap/SapTransferProtocol.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2022 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 android.hardware.radio.sap;
+
+@VintfStability
+@Backing(type="int")
+enum SapTransferProtocol {
+    T0,
+    T1,
+}
diff --git a/radio/aidl/compat/libradiocompat/Android.bp b/radio/aidl/compat/libradiocompat/Android.bp
index 487d91b..aa20fc3 100644
--- a/radio/aidl/compat/libradiocompat/Android.bp
+++ b/radio/aidl/compat/libradiocompat/Android.bp
@@ -40,6 +40,7 @@
         "android.hardware.radio.messaging-V1-ndk",
         "android.hardware.radio.modem-V1-ndk",
         "android.hardware.radio.network-V1-ndk",
+        "android.hardware.radio.sap-V1-ndk",
         "android.hardware.radio.sim-V1-ndk",
         "android.hardware.radio.voice-V1-ndk",
         "android.hardware.radio@1.0",
@@ -82,6 +83,9 @@
         "network/RadioResponse-network.cpp",
         "network/structs.cpp",
         "network/utils.cpp",
+        "sap/Sap.cpp",
+        "sap/SapCallback.cpp",
+        "sap/structs.cpp",
         "sim/RadioIndication-sim.cpp",
         "sim/RadioResponse-sim.cpp",
         "sim/RadioSim.cpp",
diff --git a/radio/aidl/compat/libradiocompat/commonStructs.cpp b/radio/aidl/compat/libradiocompat/commonStructs.cpp
index 6e4c873..d65ed1a 100644
--- a/radio/aidl/compat/libradiocompat/commonStructs.cpp
+++ b/radio/aidl/compat/libradiocompat/commonStructs.cpp
@@ -48,6 +48,10 @@
     return v;
 }
 
+uint8_t toHidl(int8_t v) {
+    return v;
+}
+
 aidl::RadioIndicationType toAidl(V1_0::RadioIndicationType type) {
     return aidl::RadioIndicationType(type);
 }
diff --git a/radio/aidl/compat/libradiocompat/commonStructs.h b/radio/aidl/compat/libradiocompat/commonStructs.h
index a4a4869..f43a599 100644
--- a/radio/aidl/compat/libradiocompat/commonStructs.h
+++ b/radio/aidl/compat/libradiocompat/commonStructs.h
@@ -28,6 +28,7 @@
 uint8_t toAidl(int8_t v);
 int8_t toAidl(uint8_t v);
 int32_t toAidl(uint32_t v);
+uint8_t toHidl(int8_t v);
 
 aidl::android::hardware::radio::RadioIndicationType toAidl(V1_0::RadioIndicationType type);
 aidl::android::hardware::radio::RadioResponseType toAidl(V1_0::RadioResponseType type);
diff --git a/radio/aidl/compat/libradiocompat/include/libradiocompat/Sap.h b/radio/aidl/compat/libradiocompat/include/libradiocompat/Sap.h
new file mode 100644
index 0000000..a293d11
--- /dev/null
+++ b/radio/aidl/compat/libradiocompat/include/libradiocompat/Sap.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#pragma once
+
+#include "RadioCompatBase.h"
+#include "SapCallback.h"
+
+#include <aidl/android/hardware/radio/sap/BnSap.h>
+#include <android/hardware/radio/1.0/ISap.h>
+#include <android/hardware/radio/1.0/ISapCallback.h>
+
+namespace android::hardware::radio::compat {
+
+/**
+ * HAL translator from HIDL ISap to AIDL ISap
+ *
+ * This class wraps existing HIDL implementation (either a binder stub or real
+ * class implementing the HAL) and implements AIDL HAL. It's up to the caller to
+ * fetch source implementation and publish resulting HAL instance.
+ */
+class Sap : public aidl::android::hardware::radio::sap::BnSap {
+    const sp<radio::V1_0::ISap> mHal;
+
+    const sp<SapCallback> mSapCallback;
+
+    ::ndk::ScopedAStatus apduReq(int32_t serial,
+                                 aidl::android::hardware::radio::sap::SapApduType type,
+                                 const std::vector<uint8_t>& command) override;
+    ::ndk::ScopedAStatus connectReq(int32_t serial, int32_t maxMsgSize) override;
+    ::ndk::ScopedAStatus disconnectReq(int32_t serial) override;
+    ::ndk::ScopedAStatus powerReq(int32_t serial, bool state) override;
+    ::ndk::ScopedAStatus resetSimReq(int32_t serial) override;
+    ::ndk::ScopedAStatus setCallback(
+            const std::shared_ptr<::aidl::android::hardware::radio::sap::ISapCallback>& sapCallback)
+            override;
+    ::ndk::ScopedAStatus setTransferProtocolReq(
+            int32_t serial,
+            aidl::android::hardware::radio::sap::SapTransferProtocol transferProtocol) override;
+    ::ndk::ScopedAStatus transferAtrReq(int32_t serial) override;
+    ::ndk::ScopedAStatus transferCardReaderStatusReq(int32_t serial) override;
+
+  public:
+    /**
+     * Constructs AIDL ISap instance wrapping existing HIDL ISap instance.
+     *
+     * \param hidlHal existing HIDL ISap HAL instance
+     */
+    Sap(sp<V1_0::ISap> hidlHal);
+};
+
+}  // namespace android::hardware::radio::compat
diff --git a/radio/aidl/compat/libradiocompat/include/libradiocompat/SapCallback.h b/radio/aidl/compat/libradiocompat/include/libradiocompat/SapCallback.h
new file mode 100644
index 0000000..7e72106
--- /dev/null
+++ b/radio/aidl/compat/libradiocompat/include/libradiocompat/SapCallback.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#pragma once
+
+#include "GuaranteedCallback.h"
+
+#include <aidl/android/hardware/radio/sap/ISapCallback.h>
+#include <android/hardware/radio/1.0/ISapCallback.h>
+
+namespace android::hardware::radio::compat {
+
+class SapCallback : public V1_0::ISapCallback {
+    GuaranteedCallback<aidl::android::hardware::radio::sap::ISapCallback,
+                       aidl::android::hardware::radio::sap::ISapCallbackDefault>
+            mCallback;
+
+    Return<void> apduResponse(int32_t serial, V1_0::SapResultCode resultCode,
+                              const ::android::hardware::hidl_vec<uint8_t>& apduRsp) override;
+    Return<void> connectResponse(int32_t serial, V1_0::SapConnectRsp sapConnectRsp,
+                                 int32_t maxMsgSize) override;
+    Return<void> disconnectIndication(int32_t serial,
+                                      V1_0::SapDisconnectType disconnectType) override;
+    Return<void> disconnectResponse(int32_t serial) override;
+    Return<void> errorResponse(int32_t serial) override;
+    Return<void> powerResponse(int32_t serial, V1_0::SapResultCode resultCode) override;
+    Return<void> resetSimResponse(int32_t serial, V1_0::SapResultCode resultCode) override;
+    Return<void> statusIndication(int32_t serial, V1_0::SapStatus status) override;
+    Return<void> transferAtrResponse(int32_t serial, V1_0::SapResultCode resultCode,
+                                     const ::android::hardware::hidl_vec<uint8_t>& atr) override;
+    Return<void> transferCardReaderStatusResponse(int32_t serial, V1_0::SapResultCode resultCode,
+                                                  int32_t cardReaderStatus) override;
+    Return<void> transferProtocolResponse(int32_t serial, V1_0::SapResultCode resultCode) override;
+
+  public:
+    void setResponseFunction(
+            const std::shared_ptr<aidl::android::hardware::radio::sap::ISapCallback>& callback);
+
+    std::shared_ptr<aidl::android::hardware::radio::sap::ISapCallback> respond();
+};
+
+}  // namespace android::hardware::radio::compat
diff --git a/radio/aidl/compat/libradiocompat/sap/Sap.cpp b/radio/aidl/compat/libradiocompat/sap/Sap.cpp
new file mode 100644
index 0000000..1a77169
--- /dev/null
+++ b/radio/aidl/compat/libradiocompat/sap/Sap.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <libradiocompat/Sap.h>
+
+#include "commonStructs.h"
+#include "debug.h"
+#include "structs.h"
+
+#include "collections.h"
+
+#define RADIO_MODULE "Sap"
+
+namespace android::hardware::radio::compat {
+
+using ::ndk::ScopedAStatus;
+namespace aidl = ::aidl::android::hardware::radio::sap;
+constexpr auto ok = &ScopedAStatus::ok;
+
+Sap::Sap(sp<V1_0::ISap> hidlHal) : mHal(hidlHal), mSapCallback(sp<SapCallback>::make()) {}
+
+ScopedAStatus Sap::apduReq(int32_t serial, aidl::SapApduType type,
+                           const std::vector<uint8_t>& command) {
+    LOG_CALL << serial;
+    mHal->apduReq(serial, toHidl(type), toHidl(command));
+    return ok();
+}
+
+ScopedAStatus Sap::connectReq(int32_t serial, int32_t maxMsgSize) {
+    LOG_CALL << serial;
+    mHal->connectReq(serial, maxMsgSize);
+    return ok();
+}
+
+ScopedAStatus Sap::disconnectReq(int32_t serial) {
+    LOG_CALL << serial;
+    mHal->disconnectReq(serial);
+    return ok();
+}
+
+ScopedAStatus Sap::powerReq(int32_t serial, bool state) {
+    LOG_CALL << serial;
+    mHal->powerReq(serial, state);
+    return ok();
+}
+
+ScopedAStatus Sap::resetSimReq(int32_t serial) {
+    LOG_CALL << serial;
+    mHal->resetSimReq(serial);
+    return ok();
+}
+
+ScopedAStatus Sap::setCallback(
+        const std::shared_ptr<::aidl::android::hardware::radio::sap::ISapCallback>& sapCallback) {
+    LOG_CALL << sapCallback;
+
+    CHECK(sapCallback);
+
+    mSapCallback->setResponseFunction(sapCallback);
+    mHal->setCallback(mSapCallback).assertOk();
+    return ok();
+}
+ScopedAStatus Sap::setTransferProtocolReq(int32_t serial,
+                                          aidl::SapTransferProtocol transferProtocol) {
+    LOG_CALL << serial;
+    mHal->setTransferProtocolReq(serial, toHidl(transferProtocol));
+    return ok();
+}
+
+ScopedAStatus Sap::transferAtrReq(int32_t serial) {
+    LOG_CALL << serial;
+    mHal->transferAtrReq(serial);
+    return ok();
+}
+ScopedAStatus Sap::transferCardReaderStatusReq(int32_t serial) {
+    LOG_CALL << serial;
+    mHal->transferCardReaderStatusReq(serial);
+    return ok();
+}
+
+}  // namespace android::hardware::radio::compat
diff --git a/radio/aidl/compat/libradiocompat/sap/SapCallback.cpp b/radio/aidl/compat/libradiocompat/sap/SapCallback.cpp
new file mode 100644
index 0000000..a40dff8
--- /dev/null
+++ b/radio/aidl/compat/libradiocompat/sap/SapCallback.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include <libradiocompat/SapCallback.h>
+
+#include "commonStructs.h"
+#include "debug.h"
+#include "structs.h"
+
+#include "collections.h"
+
+#define RADIO_MODULE "SapCallback"
+
+namespace android::hardware::radio::compat {
+
+namespace aidl = ::aidl::android::hardware::radio::sap;
+
+void SapCallback::setResponseFunction(const std::shared_ptr<aidl::ISapCallback>& callback) {
+    mCallback = callback;
+}
+
+std::shared_ptr<aidl::ISapCallback> SapCallback::respond() {
+    return mCallback.get();
+}
+
+Return<void> SapCallback::apduResponse(int32_t serial, V1_0::SapResultCode resultCode,
+                                       const hidl_vec<uint8_t>& apduRsp) {
+    LOG_CALL << serial;
+    respond()->apduResponse(serial, toAidl(resultCode), apduRsp);
+    return {};
+}
+
+Return<void> SapCallback::connectResponse(int32_t serial, V1_0::SapConnectRsp sapConnectRsp,
+                                          int32_t maxMsgSize) {
+    LOG_CALL << serial;
+    respond()->connectResponse(serial, toAidl(sapConnectRsp), maxMsgSize);
+    return {};
+}
+
+Return<void> SapCallback::disconnectIndication(int32_t serial,
+                                               V1_0::SapDisconnectType disconnectType) {
+    LOG_CALL << serial;
+    respond()->disconnectIndication(serial, toAidl(disconnectType));
+    return {};
+}
+
+Return<void> SapCallback::disconnectResponse(int32_t serial) {
+    LOG_CALL << serial;
+    respond()->disconnectResponse(serial);
+    return {};
+}
+
+Return<void> SapCallback::errorResponse(int32_t serial) {
+    LOG_CALL << serial;
+    respond()->errorResponse(serial);
+    return {};
+}
+
+Return<void> SapCallback::powerResponse(int32_t serial, V1_0::SapResultCode resultCode) {
+    LOG_CALL << serial;
+    respond()->powerResponse(serial, toAidl(resultCode));
+    return {};
+}
+
+Return<void> SapCallback::resetSimResponse(int32_t serial, V1_0::SapResultCode resultCode) {
+    LOG_CALL << serial;
+    respond()->resetSimResponse(serial, toAidl(resultCode));
+    return {};
+}
+
+Return<void> SapCallback::statusIndication(int32_t serial, V1_0::SapStatus status) {
+    LOG_CALL << serial;
+    respond()->statusIndication(serial, toAidl(status));
+    return {};
+}
+
+Return<void> SapCallback::transferAtrResponse(int32_t serial, V1_0::SapResultCode resultCode,
+                                              const hidl_vec<uint8_t>& atr) {
+    LOG_CALL << serial;
+    respond()->transferAtrResponse(serial, toAidl(resultCode), atr);
+    return {};
+}
+
+Return<void> SapCallback::transferCardReaderStatusResponse(int32_t serial,
+                                                           V1_0::SapResultCode resultCode,
+                                                           int32_t cardReaderStatus) {
+    LOG_CALL << serial;
+    respond()->transferCardReaderStatusResponse(serial, toAidl(resultCode), cardReaderStatus);
+    return {};
+}
+
+Return<void> SapCallback::transferProtocolResponse(int32_t serial, V1_0::SapResultCode resultCode) {
+    LOG_CALL << serial;
+    respond()->transferProtocolResponse(serial, toAidl(resultCode));
+    return {};
+}
+
+}  // namespace android::hardware::radio::compat
diff --git a/radio/aidl/compat/libradiocompat/sap/structs.cpp b/radio/aidl/compat/libradiocompat/sap/structs.cpp
new file mode 100644
index 0000000..522056b
--- /dev/null
+++ b/radio/aidl/compat/libradiocompat/sap/structs.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "structs.h"
+
+namespace android::hardware::radio::compat {
+
+namespace aidl = ::aidl::android::hardware::radio::sap;
+
+V1_0::SapApduType toHidl(const aidl::SapApduType sapApduType) {
+    return V1_0::SapApduType(sapApduType);
+}
+
+V1_0::SapTransferProtocol toHidl(const aidl::SapTransferProtocol sapTransferProtocol) {
+    return V1_0::SapTransferProtocol(sapTransferProtocol);
+}
+
+aidl::SapResultCode toAidl(const V1_0::SapResultCode sapResultCode) {
+    return aidl::SapResultCode(sapResultCode);
+}
+
+aidl::SapConnectRsp toAidl(const V1_0::SapConnectRsp sapConnectRsp) {
+    return aidl::SapConnectRsp(sapConnectRsp);
+}
+
+aidl::SapDisconnectType toAidl(const V1_0::SapDisconnectType sapDisconnectType) {
+    return aidl::SapDisconnectType(sapDisconnectType);
+}
+
+aidl::SapStatus toAidl(const V1_0::SapStatus sapStatus) {
+    return aidl::SapStatus(sapStatus);
+}
+
+}  // namespace android::hardware::radio::compat
diff --git a/radio/aidl/compat/libradiocompat/sap/structs.h b/radio/aidl/compat/libradiocompat/sap/structs.h
new file mode 100644
index 0000000..d88120d
--- /dev/null
+++ b/radio/aidl/compat/libradiocompat/sap/structs.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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, eithe r express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <aidl/android/hardware/radio/sap/ISap.h>
+#include <android/hardware/radio/1.0/types.h>
+
+namespace android::hardware::radio::compat {
+
+V1_0::SapApduType toHidl(aidl::android::hardware::radio::sap::SapApduType sapAdpuType);
+V1_0::SapTransferProtocol toHidl(
+        aidl::android::hardware::radio::sap::SapTransferProtocol sapTransferProtocol);
+
+aidl::android::hardware::radio::sap::SapResultCode toAidl(V1_0::SapResultCode sapResultCode);
+aidl::android::hardware::radio::sap::SapConnectRsp toAidl(V1_0::SapConnectRsp sapConnectRsp);
+aidl::android::hardware::radio::sap::SapDisconnectType toAidl(
+        V1_0::SapDisconnectType sapDisconnectType);
+aidl::android::hardware::radio::sap::SapStatus toAidl(V1_0::SapStatus sapStatus);
+
+}  // namespace android::hardware::radio::compat
diff --git a/radio/aidl/compat/service/Android.bp b/radio/aidl/compat/service/Android.bp
index 52eb71f..933e3e1 100644
--- a/radio/aidl/compat/service/Android.bp
+++ b/radio/aidl/compat/service/Android.bp
@@ -43,6 +43,7 @@
         "android.hardware.radio.messaging-V1-ndk",
         "android.hardware.radio.modem-V1-ndk",
         "android.hardware.radio.network-V1-ndk",
+        "android.hardware.radio.sap-V1-ndk",
         "android.hardware.radio.sim-V1-ndk",
         "android.hardware.radio.voice-V1-ndk",
         "android.hardware.radio@1.0",
diff --git a/radio/aidl/vts/Android.bp b/radio/aidl/vts/Android.bp
index 021ee89..97964b4 100644
--- a/radio/aidl/vts/Android.bp
+++ b/radio/aidl/vts/Android.bp
@@ -50,6 +50,8 @@
         "radio_network_indication.cpp",
         "radio_network_response.cpp",
         "radio_network_test.cpp",
+        "radio_sap_callback.cpp",
+        "radio_sap_test.cpp",
         "radio_sim_indication.cpp",
         "radio_sim_response.cpp",
         "radio_sim_test.cpp",
@@ -69,6 +71,7 @@
         "android.hardware.radio.messaging-V1-ndk",
         "android.hardware.radio.modem-V1-ndk",
         "android.hardware.radio.network-V1-ndk",
+        "android.hardware.radio.sap-V1-ndk",
         "android.hardware.radio.sim-V1-ndk",
         "android.hardware.radio.voice-V1-ndk",
     ],
diff --git a/radio/aidl/vts/VtsHalRadioTargetTest.cpp b/radio/aidl/vts/VtsHalRadioTargetTest.cpp
index 1ebc6af..03d3c5a 100644
--- a/radio/aidl/vts/VtsHalRadioTargetTest.cpp
+++ b/radio/aidl/vts/VtsHalRadioTargetTest.cpp
@@ -21,6 +21,7 @@
 #include "radio_messaging_utils.h"
 #include "radio_modem_utils.h"
 #include "radio_network_utils.h"
+#include "radio_sap_utils.h"
 #include "radio_sim_utils.h"
 #include "radio_voice_utils.h"
 
@@ -54,6 +55,11 @@
         testing::ValuesIn(android::getAidlHalInstanceNames(IRadioNetwork::descriptor)),
         android::PrintInstanceNameToString);
 
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(RadioSapTest);
+INSTANTIATE_TEST_SUITE_P(PerInstance, SapTest,
+                         testing::ValuesIn(android::getAidlHalInstanceNames(ISap::descriptor)),
+                         android::PrintInstanceNameToString);
+
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(RadioSimTest);
 INSTANTIATE_TEST_SUITE_P(PerInstance, RadioSimTest,
                          testing::ValuesIn(android::getAidlHalInstanceNames(IRadioSim::descriptor)),
diff --git a/radio/aidl/vts/radio_sap_callback.cpp b/radio/aidl/vts/radio_sap_callback.cpp
new file mode 100644
index 0000000..3b21ede
--- /dev/null
+++ b/radio/aidl/vts/radio_sap_callback.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#include "radio_sap_utils.h"
+
+#include <android-base/logging.h>
+
+SapCallback::SapCallback(SapTest& parent) : parent_sap(parent) {}
+
+::ndk::ScopedAStatus SapCallback::apduResponse(int32_t serialNumber, SapResultCode resultCode,
+                                               const std::vector<uint8_t>& /*apduRsp*/) {
+    sapResponseSerial = serialNumber;
+    sapResultCode = resultCode;
+    parent_sap.notify(serialNumber);
+    return ndk::ScopedAStatus::ok();
+}
+::ndk::ScopedAStatus SapCallback::connectResponse(int32_t serialNumber,
+                                                  SapConnectRsp /*sapConnectRsp*/,
+                                                  int32_t /*maxMsgSize*/) {
+    sapResponseSerial = serialNumber;
+    parent_sap.notify(serialNumber);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus SapCallback::disconnectIndication(int32_t /*serialNumber*/,
+                                                       SapDisconnectType /*sapDisconnectType*/) {
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus SapCallback::disconnectResponse(int32_t serialNumber) {
+    sapResponseSerial = serialNumber;
+    parent_sap.notify(serialNumber);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus SapCallback::errorResponse(int32_t /*serialNumber*/) {
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus SapCallback::powerResponse(int32_t serialNumber, SapResultCode resultCode) {
+    sapResponseSerial = serialNumber;
+    sapResultCode = resultCode;
+    parent_sap.notify(serialNumber);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus SapCallback::resetSimResponse(int32_t serialNumber, SapResultCode resultCode) {
+    sapResponseSerial = serialNumber;
+    sapResultCode = resultCode;
+    parent_sap.notify(serialNumber);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus SapCallback::statusIndication(int32_t /*serialNumber*/,
+                                                   SapStatus /*sapStatus*/) {
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus SapCallback::transferAtrResponse(int32_t serialNumber,
+                                                      SapResultCode resultCode,
+                                                      const std::vector<uint8_t>& /*atr*/) {
+    sapResponseSerial = serialNumber;
+    sapResultCode = resultCode;
+    parent_sap.notify(serialNumber);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus SapCallback::transferCardReaderStatusResponse(int32_t serialNumber,
+                                                                   SapResultCode resultCode,
+                                                                   int32_t /*cardReaderStatus*/) {
+    sapResponseSerial = serialNumber;
+    sapResultCode = resultCode;
+    parent_sap.notify(serialNumber);
+    return ndk::ScopedAStatus::ok();
+}
+
+::ndk::ScopedAStatus SapCallback::transferProtocolResponse(int32_t serialNumber,
+                                                           SapResultCode resultCode) {
+    sapResponseSerial = serialNumber;
+    sapResultCode = resultCode;
+    parent_sap.notify(serialNumber);
+    return ndk::ScopedAStatus::ok();
+}
diff --git a/radio/aidl/vts/radio_sap_test.cpp b/radio/aidl/vts/radio_sap_test.cpp
new file mode 100644
index 0000000..c94379c
--- /dev/null
+++ b/radio/aidl/vts/radio_sap_test.cpp
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+
+#include "radio_sap_utils.h"
+
+#define ASSERT_OK(ret) ASSERT_TRUE((ret).isOk())
+#define TIMEOUT_PERIOD 40
+
+void SapTest::SetUp() {
+    std::string serviceName = GetParam();
+    if (!isServiceValidForDeviceConfiguration(serviceName)) {
+        LOG(DEBUG) << "Skipped the test due to device configuration.";
+        GTEST_SKIP();
+    }
+    sap = ISap::fromBinder(ndk::SpAIBinder(AServiceManager_waitForService(serviceName.c_str())));
+    ASSERT_NE(sap.get(), nullptr);
+
+    sapCb = ndk::SharedRefBase::make<SapCallback>(*this);
+    ASSERT_NE(sapCb.get(), nullptr);
+
+    count = 0;
+
+    ndk::ScopedAStatus res = sap->setCallback(sapCb);
+    ASSERT_OK(res) << res;
+}
+
+void SapTest::TearDown() {}
+
+::testing::AssertionResult SapTest::CheckAnyOfErrors(SapResultCode err,
+                                                     std::vector<SapResultCode> errors) {
+    for (size_t i = 0; i < errors.size(); i++) {
+        if (err == errors[i]) {
+            return testing::AssertionSuccess();
+        }
+    }
+    return testing::AssertionFailure() << "SapError:" + toString(err) + " is returned";
+}
+
+void SapTest::notify(int receivedSerial) {
+    std::unique_lock<std::mutex> lock(mtx);
+    count++;
+    if (serial == receivedSerial) {
+        cv.notify_one();
+    }
+}
+
+std::cv_status SapTest::wait() {
+    std::unique_lock<std::mutex> lock(mtx);
+
+    std::cv_status status = std::cv_status::no_timeout;
+    auto now = std::chrono::system_clock::now();
+    while (count == 0) {
+        status = cv.wait_until(lock, now + std::chrono::seconds(TIMEOUT_PERIOD));
+        if (status == std::cv_status::timeout) {
+            return status;
+        }
+    }
+    count--;
+    return status;
+}
+
+/*
+ * Test ISap.connectReq() for the response returned.
+ */
+TEST_P(SapTest, connectReq) {
+    LOG(DEBUG) << "connectReq";
+    serial = GetRandomSerialNumber();
+    int32_t maxMsgSize = 100;
+
+    ndk::ScopedAStatus res = sap->connectReq(serial, maxMsgSize);
+    ASSERT_OK(res) << res;
+
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(sapCb->sapResponseSerial, serial);
+
+    // Modem side need time for connect to finish. Adding a waiting time to prevent
+    // disconnect being requested right after connect request.
+    sleep(1);
+}
+
+/*
+ * Test ISap.disconnectReq() for the response returned
+ */
+TEST_P(SapTest, disconnectReq) {
+    LOG(DEBUG) << "disconnectReq";
+    serial = GetRandomSerialNumber();
+
+    ndk::ScopedAStatus res = sap->disconnectReq(serial);
+    ASSERT_OK(res) << res;
+
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(sapCb->sapResponseSerial, serial);
+    LOG(DEBUG) << "disconnectReq finished";
+}
+
+/*
+ * Test ISap.apduReq() for the response returned.
+ */
+TEST_P(SapTest, apduReq) {
+    LOG(DEBUG) << "apduReq";
+    serial = GetRandomSerialNumber();
+    SapApduType sapApduType = SapApduType::APDU;
+    std::vector<uint8_t> command = {};
+
+    ndk::ScopedAStatus res = sap->apduReq(serial, sapApduType, command);
+    ASSERT_OK(res) << res;
+
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(sapCb->sapResponseSerial, serial);
+
+    ASSERT_TRUE(CheckAnyOfErrors(
+            sapCb->sapResultCode,
+            {SapResultCode::GENERIC_FAILURE, SapResultCode::CARD_ALREADY_POWERED_OFF,
+             SapResultCode::CARD_NOT_ACCESSSIBLE, SapResultCode::CARD_REMOVED,
+             SapResultCode::SUCCESS}));
+    LOG(DEBUG) << "apduReq finished";
+}
+
+/*
+ * Test ISap.transferAtrReq() for the response returned.
+ */
+TEST_P(SapTest, transferAtrReq) {
+    LOG(DEBUG) << "transferAtrReq";
+    serial = GetRandomSerialNumber();
+
+    ndk::ScopedAStatus res = sap->transferAtrReq(serial);
+    ASSERT_OK(res) << res;
+
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(sapCb->sapResponseSerial, serial);
+
+    ASSERT_TRUE(CheckAnyOfErrors(sapCb->sapResultCode,
+                                 {SapResultCode::GENERIC_FAILURE, SapResultCode::DATA_NOT_AVAILABLE,
+                                  SapResultCode::CARD_ALREADY_POWERED_OFF,
+                                  SapResultCode::CARD_REMOVED, SapResultCode::SUCCESS}));
+    LOG(DEBUG) << "transferAtrReq finished";
+}
+
+/*
+ * Test ISap.powerReq() for the response returned.
+ */
+TEST_P(SapTest, powerReq) {
+    LOG(DEBUG) << "powerReq";
+    serial = GetRandomSerialNumber();
+    bool state = true;
+
+    ndk::ScopedAStatus res = sap->powerReq(serial, state);
+    ASSERT_OK(res) << res;
+
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(sapCb->sapResponseSerial, serial);
+
+    ASSERT_TRUE(
+            CheckAnyOfErrors(sapCb->sapResultCode,
+                             {SapResultCode::GENERIC_FAILURE, SapResultCode::CARD_NOT_ACCESSSIBLE,
+                              SapResultCode::CARD_ALREADY_POWERED_OFF, SapResultCode::CARD_REMOVED,
+                              SapResultCode::CARD_ALREADY_POWERED_ON, SapResultCode::SUCCESS}));
+    LOG(DEBUG) << "powerReq finished";
+}
+
+/*
+ * Test ISap.resetSimReq() for the response returned.
+ */
+TEST_P(SapTest, resetSimReq) {
+    LOG(DEBUG) << "resetSimReq";
+    serial = GetRandomSerialNumber();
+
+    ndk::ScopedAStatus res = sap->resetSimReq(serial);
+    ASSERT_OK(res) << res;
+
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(sapCb->sapResponseSerial, serial);
+
+    ASSERT_TRUE(
+            CheckAnyOfErrors(sapCb->sapResultCode,
+                             {SapResultCode::GENERIC_FAILURE, SapResultCode::CARD_NOT_ACCESSSIBLE,
+                              SapResultCode::CARD_ALREADY_POWERED_OFF, SapResultCode::CARD_REMOVED,
+                              SapResultCode::SUCCESS}));
+    LOG(DEBUG) << "resetSimReq finished";
+}
+
+/*
+ * Test ISap.transferCardReaderStatusReq() for the response returned.
+ */
+TEST_P(SapTest, transferCardReaderStatusReq) {
+    LOG(DEBUG) << "transferCardReaderStatusReq";
+    serial = GetRandomSerialNumber();
+
+    ndk::ScopedAStatus res = sap->transferCardReaderStatusReq(serial);
+    ASSERT_OK(res) << res;
+
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(sapCb->sapResponseSerial, serial);
+
+    ASSERT_TRUE(CheckAnyOfErrors(sapCb->sapResultCode,
+                                 {SapResultCode::GENERIC_FAILURE, SapResultCode::DATA_NOT_AVAILABLE,
+                                  SapResultCode::SUCCESS}));
+    LOG(DEBUG) << "transferCardReaderStatusReq finished";
+}
+
+/*
+ * Test ISap.setTransferProtocolReq() for the response returned.
+ */
+TEST_P(SapTest, setTransferProtocolReq) {
+    LOG(DEBUG) << "setTransferProtocolReq";
+    serial = GetRandomSerialNumber();
+    SapTransferProtocol sapTransferProtocol = SapTransferProtocol::T0;
+
+    ndk::ScopedAStatus res = sap->setTransferProtocolReq(serial, sapTransferProtocol);
+    ASSERT_OK(res) << res;
+
+    EXPECT_EQ(std::cv_status::no_timeout, wait());
+    EXPECT_EQ(sapCb->sapResponseSerial, serial);
+
+    ASSERT_TRUE(CheckAnyOfErrors(sapCb->sapResultCode,
+                                 {SapResultCode::NOT_SUPPORTED, SapResultCode::SUCCESS}));
+    LOG(DEBUG) << "setTransferProtocolReq finished";
+}
diff --git a/radio/aidl/vts/radio_sap_utils.h b/radio/aidl/vts/radio_sap_utils.h
new file mode 100644
index 0000000..bf17006
--- /dev/null
+++ b/radio/aidl/vts/radio_sap_utils.h
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+#pragma once
+
+#include <aidl/Gtest.h>
+#include <aidl/android/hardware/radio/sap/BnSapCallback.h>
+#include <aidl/android/hardware/radio/sap/ISap.h>
+
+#include "radio_aidl_hal_utils.h"
+
+using namespace aidl::android::hardware::radio::sap;
+
+class SapTest;
+
+/* Callback class for radio sap response */
+class SapCallback : public BnSapCallback {
+  protected:
+    SapTest& parent_sap;
+
+  public:
+    SapCallback(SapTest& parent_config);
+    virtual ~SapCallback() = default;
+
+    int32_t sapResponseSerial;
+    SapResultCode sapResultCode;
+
+    virtual ::ndk::ScopedAStatus apduResponse(int32_t serial, SapResultCode resultCode,
+                                              const std::vector<uint8_t>& adpuRsp) override;
+
+    virtual ::ndk::ScopedAStatus connectResponse(int32_t serial, SapConnectRsp sapConnectRsp,
+                                                 int32_t maxMsgSize) override;
+
+    virtual ::ndk::ScopedAStatus disconnectIndication(int32_t serial,
+                                                      SapDisconnectType sapDisconnectType) override;
+
+    virtual ::ndk::ScopedAStatus disconnectResponse(int32_t serial) override;
+
+    virtual ::ndk::ScopedAStatus errorResponse(int32_t serial) override;
+
+    virtual ::ndk::ScopedAStatus powerResponse(int32_t serial, SapResultCode resultCode) override;
+
+    virtual ::ndk::ScopedAStatus resetSimResponse(int32_t serial,
+                                                  SapResultCode resultCode) override;
+
+    virtual ::ndk::ScopedAStatus statusIndication(int32_t serial, SapStatus sapStatus) override;
+
+    virtual ::ndk::ScopedAStatus transferAtrResponse(int32_t serial, SapResultCode resultCode,
+                                                     const std::vector<uint8_t>& atr) override;
+
+    virtual ::ndk::ScopedAStatus transferCardReaderStatusResponse(
+            int32_t serial, SapResultCode resultCode, int32_t cardReaderStatus) override;
+
+    virtual ::ndk::ScopedAStatus transferProtocolResponse(int32_t serial,
+                                                          SapResultCode resultCode) override;
+};
+
+// The main test class for  AIDL SAP.
+class SapTest : public ::testing::TestWithParam<std::string> {
+  private:
+    std::mutex mtx;
+    std::condition_variable cv;
+    int count;
+
+  public:
+    virtual void SetUp() override;
+
+    virtual void TearDown() override;
+
+    ::testing::AssertionResult CheckAnyOfErrors(SapResultCode err,
+                                                std::vector<SapResultCode> errors);
+
+    /* Used as a mechanism to inform the test about data/event callback */
+    void notify(int receivedSerial);
+
+    /* Test code calls this function to wait for response */
+    std::cv_status wait();
+
+    /* Sap service */
+    std::shared_ptr<ISap> sap;
+
+    /* Sap Callback object */
+    std::shared_ptr<SapCallback> sapCb;
+
+    /* Serial for sap request */
+    int32_t serial;
+};
diff --git a/secure_element/aidl/default/main.cpp b/secure_element/aidl/default/main.cpp
index 16b8236..9b5a8fc 100644
--- a/secure_element/aidl/default/main.cpp
+++ b/secure_element/aidl/default/main.cpp
@@ -15,141 +15,673 @@
  */
 
 #include <aidl/android/hardware/secure_element/BnSecureElement.h>
-
 #include <android-base/hex.h>
 #include <android-base/logging.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
 
+#include <algorithm>
+
 using aidl::android::hardware::secure_element::BnSecureElement;
 using aidl::android::hardware::secure_element::ISecureElementCallback;
 using aidl::android::hardware::secure_element::LogicalChannelResponse;
 using android::base::HexString;
 using ndk::ScopedAStatus;
 
-static const std::vector<uint8_t> kAndroidTestAid = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41,
-                                                     0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64,
-                                                     0x43, 0x54, 0x53, 0x31};
-static const std::vector<uint8_t> kLongAndroidTestAid = {0xA0, 0x00, 0x00, 0x04, 0x76, 0x41,
-                                                         0x6E, 0x64, 0x72, 0x6F, 0x69, 0x64,
-                                                         0x43, 0x54, 0x53, 0x32};
+static const std::vector<uint8_t> kIssuerSecurityDomainSelectResponse = {0x00, 0x00, 0x90, 0x00};
 
-class MySecureElement : public BnSecureElement {
+namespace se {
+// Application identifier.
+using Aid = std::vector<uint8_t>;
+
+// ISO7816 APDU status codes.
+enum Status : uint16_t {
+    SW_WRONG_DATA = 0x6A80,
+    SW_LOGICAL_CHANNEL_NOT_SUPPORTED = 0x6881,
+    SW_CONDITIONS_NOT_SATISFIED = 0x6985,
+    SW_INCORRECT_P1P2 = 0x6A86,
+    SW_BYTES_REMAINING_00 = 0x6100,
+    SW_WRONG_LENGTH = 0x6700,
+    SW_CORRECT_LENGTH_00 = 0x6C00,
+    SW_INS_NOT_SUPPORTED = 0x6D00,
+    SW_NO_ERROR = 0x9000,
+};
+
+// Type for raw APDUs.
+using RawApdu = std::vector<uint8_t>;
+
+// Wrap a command APDU (Application Processing Data Unit) to provide
+// accessors for header fields.
+struct Apdu {
   public:
-    ScopedAStatus closeChannel(int8_t channelNumber) override {
-        LOG(INFO) << __func__ << " channel number: " << channelNumber;
-        return ScopedAStatus::ok();
+    // Construct a command Apdu.
+    Apdu(std::vector<uint8_t> packet) : bytes_(std::move(packet)) {
+        CHECK(bytes_.size() >= kHeaderSize) << "command APDU created with invalid length";
+        size_t payload_len = bytes_.size() - kHeaderSize;
+
+        // TODO(b/123254068) - add support for extended command APDUs.
+        // Pre compute Lc and Le.
+
+        // Case 1: CLA | INS | P1 | P2
+        if (payload_len == 0) {
+            lc_ = 0;
+            le_ = 0;
+            return;
+        }
+
+        // Case 2: CLA | INS | P1 | P2 | Le
+        // Le has a value of 1 to 255.
+        if (payload_len == 1) {
+            le_ = bytes_[kHeaderSize];
+            le_ = le_ == 0 ? 256 : le_;
+            lc_ = 0;
+            return;
+        }
+
+        // Case 3: CLA | INS | P1 | P2 | Lc | Data
+        // Lc is less than 256 bytes
+        // of data, and Le is zero.
+        lc_ = bytes_[kHeaderSize];
+        if (payload_len <= (1 + lc_)) {
+            le_ = 0;
+        }
+
+        // Case 4: CLA | INS | P1 | P2 | Lc | Data | Le
+        // The legacy Case 4. Lc and Le
+        // are less than 256 bytes of data.
+        else {
+            le_ = bytes_[bytes_.size() - 1];
+            le_ = le_ == 0 ? 256 : le_;
+        }
     }
-    ScopedAStatus getAtr(std::vector<uint8_t>* _aidl_return) override {
-        LOG(INFO) << __func__;
-        _aidl_return->clear();
-        return ScopedAStatus::ok();
+
+    // Construct a response Apdu with data.
+    static RawApdu CreateResponse(std::vector<uint8_t> data, Status status) {
+        // Append status word.
+        data.push_back(status >> 8);
+        data.push_back(status);
+        return data;
     }
+
+    // Construct a response Apdu with no data.
+    static RawApdu CreateResponse(Status status) {
+        // Append status word.
+        return std::vector<uint8_t>{static_cast<uint8_t>(status >> 8),
+                                    static_cast<uint8_t>(status)};
+    }
+
+    // Return if command APDU is extended.
+    // The ISO/IEC 7816-4:2013 specification defines an extended APDU as any APDU
+    // whose payload data, response data or expected data length exceeds the 256
+    // byte limit.
+    bool IsExtended() const { return (bytes_.size() - kHeaderSize) > 256; }
+
+    // Return if command APDU has payload bytes.
+    bool HasPayload() const { return bytes_.size() > kHeaderSize; }
+
+    uint8_t get_cla() const { return bytes_[0]; }
+    uint8_t get_ins() const { return bytes_[1]; }
+    uint8_t get_p1() const { return bytes_[2]; }
+    uint8_t get_p2() const { return bytes_[3]; }
+
+    // Return the channel number encoded in the CLA field.
+    uint8_t get_channel_number() const {
+        // Type 4 commands — Encode legacy ISO/IEC 7816-4 logical channel
+        // information. Type 16 commands — Defined by the ISO/IEC 7816-4:2013
+        // specification to
+        //   encode information for additional 16 logical channels in the card.
+        uint8_t cla = get_cla();
+        return (cla & 0x40) == 0 ? cla & 0x3 : 4 + (cla & 0xf);
+    }
+
+    // Return the length of the command data field.
+    uint16_t get_lc() const { return lc_; }
+
+    // Return the expected length of the response data field.
+    // Le should be have the same format as Lc.
+    uint16_t get_le() const { return le_; }
+
+    // Get the pointer to the APDU raw data.
+    std::vector<uint8_t> const& get_data() const { return bytes_; }
+
+  private:
+    // Size of command header, including CLA, INS, P1, P2 fields.
+    const size_t kHeaderSize = 4;
+
+    // Command or response buffer.
+    std::vector<uint8_t> bytes_{};
+
+    // Lengths of command data field and expected response data field.
+    uint16_t lc_{0};
+    uint16_t le_{0};
+};
+
+// Type of SE applets.
+class Applet {
+  public:
+    virtual ~Applet() {}
+
+    // Called to inform this applet that it has been selected.
+    virtual RawApdu Select(Aid const& aid, uint8_t p2) = 0;
+
+    // Called by the Java Card runtime environment to process an
+    // incoming APDU command. SELECT commands are processed by \ref select
+    // instead.
+    virtual RawApdu Process(Apdu const& apdu) = 0;
+};
+};  // namespace se
+
+// Implement the Google-eSE-test.cap test applet for passing OMAPI CTS tests
+// on Cuttlefish. The reference can be found here:
+// cts/tests/tests/secure_element/sample_applet/src/com/android/cts/omapi/test/CtsAndroidOmapiTestApplet.java
+class CtsAndroidOmapiTestApplet : public se::Applet {
+  public:
+    CtsAndroidOmapiTestApplet() {}
+    virtual ~CtsAndroidOmapiTestApplet() {}
+
+    se::RawApdu Select(se::Aid const& aid, uint8_t /*p2*/) override {
+        if (aid[aid.size() - 1] == 0x31) {
+            // AID: A000000476416E64726F696443545331
+            return se::Apdu::CreateResponse(se::Status::SW_NO_ERROR);
+        } else {
+            // AID: A000000476416E64726F696443545332
+            return se::Apdu::CreateResponse(GenerateBerTLVBytes(SELECT_RESPONSE_DATA_LENGTH),
+                                            se::Status::SW_NO_ERROR);
+        }
+    }
+
+    se::RawApdu ReadNextResponseChunk(uint16_t max_output_len) {
+        uint16_t output_len = static_cast<uint16_t>(response_.size() - response_offset_);
+        output_len = std::min<uint16_t>(max_output_len, output_len);
+        std::vector<uint8_t> output{
+                &response_[response_offset_],
+                &response_[response_offset_ + output_len],
+        };
+        response_offset_ += output_len;
+        uint16_t remaining_len = response_.size() - response_offset_;
+        se::Status status = se::Status::SW_NO_ERROR;
+        if (remaining_len > 0) {
+            if (remaining_len > 256) {
+                remaining_len = 0x00;
+            }
+            status = se::Status(se::Status::SW_BYTES_REMAINING_00 | remaining_len);
+        } else {
+            response_.clear();
+            response_offset_ = 0;
+        }
+        return se::Apdu::CreateResponse(output, status);
+    }
+
+    se::RawApdu Process(se::Apdu const& apdu) override {
+        uint16_t lc;
+        uint16_t le = apdu.get_le();
+        uint8_t p1 = apdu.get_p1();
+        uint8_t p2 = apdu.get_p2();
+
+        switch (apdu.get_ins()) {
+            case NO_DATA_INS_1:
+            case NO_DATA_INS_2:
+                LOG(INFO) << __func__ << ": NO_DATA_INS_1|2";
+                return se::Apdu::CreateResponse(se::Status::SW_NO_ERROR);
+
+            case DATA_INS_1:
+            case DATA_INS_2:
+                // Return 256 bytes of data.
+                LOG(INFO) << __func__ << ": DATA_INS_1|2";
+                return se::Apdu::CreateResponse(GeneratesBytes(256), se::Status::SW_NO_ERROR);
+
+            case GET_RESPONSE_INS:
+                // ISO GET_RESPONSE command.
+                LOG(INFO) << __func__ << ": GET_RESPONSE_INS";
+                if (response_.empty()) {
+                    return se::Apdu::CreateResponse(se::Status::SW_CONDITIONS_NOT_SATISFIED);
+                }
+                return ReadNextResponseChunk(apdu.get_le());
+
+            case SW_62xx_APDU_INS:
+                LOG(INFO) << __func__ << ": SW_62xx_APDU_INS";
+                if (p1 < 1 || p1 > 16) {
+                    return se::Apdu::CreateResponse(se::Status::SW_INCORRECT_P1P2);
+                }
+                if (p2 == SW_62xx_DATA_APDU_P2) {
+                    return se::Apdu::CreateResponse(GeneratesBytes(3),
+                                                    se::Status(SW_62xx_resp[p1 - 1]));
+                }
+                if (p2 == SW_62xx_VALIDATE_DATA_P2) {
+                    std::vector<uint8_t> output{SW_62xx_VALIDATE_DATA_RESP.begin(),
+                                                SW_62xx_VALIDATE_DATA_RESP.end()};
+                    output[2] = p1;
+                    return se::Apdu::CreateResponse(std::move(output),
+                                                    se::Status(SW_62xx_resp[p1 - 1]));
+                }
+                return se::Apdu::CreateResponse(se::Status(SW_62xx_resp[p1 - 1]));
+
+            case SEGMENTED_RESP_INS_1:
+            case SEGMENTED_RESP_INS_2:
+                LOG(INFO) << __func__ << ": SEGMENTED_RESP_INS_1|2";
+                response_ = GeneratesBytes((static_cast<uint16_t>(p1) << 8) | p2);
+                response_offset_ = 0;
+                return ReadNextResponseChunk(std::min<uint16_t>(apdu.get_le(), 256));
+
+            case SEGMENTED_RESP_INS_3:
+            case SEGMENTED_RESP_INS_4:
+                LOG(INFO) << __func__ << ": SEGMENTED_RESP_INS_3|4";
+                response_ = GeneratesBytes((static_cast<uint16_t>(p1) << 8) | p2);
+                response_offset_ = 0;
+                return ReadNextResponseChunk(apdu.get_le());
+
+            case SEGMENTED_RESP_INS_5:
+                LOG(INFO) << __func__ << ": SEGMENTED_RESP_INS_5";
+                if (le == 0xff) {
+                    return se::Apdu::CreateResponse(
+                            se::Status(se::Status::SW_CORRECT_LENGTH_00 | 0xff));
+                }
+                response_ = GeneratesBytes((static_cast<uint16_t>(p1) << 8) | p2);
+                response_offset_ = 0;
+                return ReadNextResponseChunk(apdu.get_le());
+
+            case CHECK_SELECT_P2_APDU:
+                LOG(INFO) << __func__ << ": CHECK_SELECT_P2_APDU";
+                return se::Apdu::CreateResponse(std::vector<uint8_t>{apdu.get_p2()},
+                                                se::Status::SW_NO_ERROR);
+
+            default:
+                // Case is not known.
+                LOG(INFO) << __func__ << ": UNKNOWN_INS";
+                return se::Apdu::CreateResponse(se::Status::SW_INS_NOT_SUPPORTED);
+        }
+    }
+
+  private:
+    std::vector<uint8_t> response_{};
+    uint16_t response_offset_{0};
+
+    static const uint8_t NO_DATA_INS_1 = 0x06;
+    static const uint8_t NO_DATA_INS_2 = 0x0A;
+    static const uint8_t DATA_INS_1 = 0x08;
+    static const uint8_t DATA_INS_2 = 0x0C;
+    static const uint8_t SW_62xx_APDU_INS = 0xF3;
+    static const uint8_t SW_62xx_DATA_APDU_P2 = 0x08;
+    static const uint8_t SW_62xx_VALIDATE_DATA_P2 = 0x0C;
+
+    static constexpr std::array<uint8_t, 7> SW_62xx_VALIDATE_DATA_RESP = {0x01, 0xF3, 0x00, 0x0C,
+                                                                          0x01, 0xAA, 0x00};
+    static constexpr uint16_t SW_62xx_resp[] = {
+            0x6200, 0x6281, 0x6282, 0x6283, 0x6285, 0x62F1, 0x62F2, 0x63F1,
+            0x63F2, 0x63C2, 0x6202, 0x6280, 0x6284, 0x6286, 0x6300, 0x6381,
+    };
+
+    static const uint8_t SEGMENTED_RESP_INS_1 = 0xC2;
+    static const uint8_t SEGMENTED_RESP_INS_2 = 0xC4;
+    static const uint8_t SEGMENTED_RESP_INS_3 = 0xC6;
+    static const uint8_t SEGMENTED_RESP_INS_4 = 0xC8;
+    static const uint8_t SEGMENTED_RESP_INS_5 = 0xCF;
+    static const uint8_t CHECK_SELECT_P2_APDU = 0xF4;
+    static const uint8_t GET_RESPONSE_INS = 0xC0;
+    static const uint8_t BER_TLV_TYPE = 0x1F;
+    static const uint16_t SELECT_RESPONSE_DATA_LENGTH = 252;
+
+    static const uint16_t LENGTH_256 = 0x0100;
+    static constexpr std::array<uint8_t, 256> resp_bytes256{
+            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
+            0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B,
+            0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
+            0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+            0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 0x40, 0x41, 0x42, 0x43, 0x44, 0x45,
+            0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53,
+            0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 0x60, 0x61,
+            0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
+            0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D,
+            0x7E, 0x7F, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B,
+            0x8C, 0x8D, 0x8E, 0x8F, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+            0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
+            0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5,
+            0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3,
+            0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1,
+            0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
+            0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED,
+            0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB,
+            0xFC, 0xFD, 0xFE, 0xFF};
+
+    // Generate a response buffer of the selected length containing valid
+    // BER TLV bytes.
+    static std::vector<uint8_t> GenerateBerTLVBytes(uint16_t le) {
+        // Support length from 0x00 - 0x7FFF.
+        uint16_t le_len = 1;
+        if (le < (uint16_t)0x80) {
+            le_len = 1;
+        } else if (le < (uint16_t)0x100) {
+            le_len = 2;
+        } else {
+            le_len = 3;
+        }
+
+        uint16_t total_len = (uint16_t)(le + 2 + le_len);
+        std::vector<uint8_t> output(total_len);
+        uint16_t i = 0;
+
+        output[i++] = BER_TLV_TYPE;
+        output[i++] = 0x00;  // second byte of Type
+        if (le < 0x80) {
+            output[i++] = le;
+        } else if (le < 0x100) {
+            output[i++] = 0x81;
+            output[i++] = le;
+        } else {
+            output[i++] = 0x82;
+            output[i++] = (le >> 8);
+            output[i++] = (le & 0xFF);
+        }
+        while (i < total_len) {
+            output[i++] = ((i - 2 - le_len) & 0xFF);
+        }
+
+        // Set the last byte to 0xFF for CTS validation.
+        output[total_len - 1] = 0xFF;
+        return output;
+    }
+
+    // Generate a response buffer of the selected length using the
+    // array resp_bytes256 as input.
+    static std::vector<uint8_t> GeneratesBytes(uint16_t total_len) {
+        std::vector<uint8_t> output(total_len);
+        uint16_t i = 0;
+
+        while (i < total_len) {
+            if ((total_len - i) >= resp_bytes256.size()) {
+                std::memcpy(&output[i], resp_bytes256.data(), resp_bytes256.size());
+                i += resp_bytes256.size();
+            } else {
+                output[i] = i & 0xFF;
+                i += 1;
+            }
+        }
+
+        // Set the last byte to 0xFF for CTS validation.
+        output[total_len - 1] = 0xFF;
+        return output;
+    }
+};
+
+class EmulatedSecureElement : public BnSecureElement {
+  public:
+    EmulatedSecureElement() {
+        std::shared_ptr<CtsAndroidOmapiTestApplet> test_applet =
+                std::make_shared<CtsAndroidOmapiTestApplet>();
+
+        applets_.push_back(std::pair{se::Aid{0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, 0x72,
+                                             0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x31},
+                                     test_applet});
+
+        applets_.push_back(std::pair{se::Aid{0xA0, 0x00, 0x00, 0x04, 0x76, 0x41, 0x6E, 0x64, 0x72,
+                                             0x6F, 0x69, 0x64, 0x43, 0x54, 0x53, 0x32},
+                                     test_applet});
+    }
+
     ScopedAStatus init(const std::shared_ptr<ISecureElementCallback>& clientCallback) override {
         LOG(INFO) << __func__ << " callback: " << clientCallback.get();
         if (!clientCallback) {
             return ScopedAStatus::fromExceptionCode(EX_NULL_POINTER);
         }
-        mCb = clientCallback;
-        mCb->onStateChange(true, "");
+        client_callback_ = clientCallback;
+        client_callback_->onStateChange(true, "init");
         return ScopedAStatus::ok();
     }
-    ScopedAStatus isCardPresent(bool* _aidl_return) override {
+
+    ScopedAStatus getAtr(std::vector<uint8_t>* aidl_return) override {
         LOG(INFO) << __func__;
-        *_aidl_return = true;
+        *aidl_return = atr_;
         return ScopedAStatus::ok();
     }
+
+    ScopedAStatus reset() override {
+        LOG(INFO) << __func__;
+        CHECK(client_callback_ != nullptr) << " init not invoked";
+        client_callback_->onStateChange(false, "reset");
+        client_callback_->onStateChange(true, "reset");
+        // All channels are closed after reset.
+        for (auto channel : channels_) {
+            channel = Channel();
+        }
+        return ScopedAStatus::ok();
+    }
+
+    ScopedAStatus isCardPresent(bool* aidl_return) override {
+        LOG(INFO) << __func__;
+        *aidl_return = true;
+        return ScopedAStatus::ok();
+    }
+
     ScopedAStatus openBasicChannel(const std::vector<uint8_t>& aid, int8_t p2,
-                                   std::vector<uint8_t>* _aidl_return) override {
+                                   std::vector<uint8_t>* aidl_return) override {
         LOG(INFO) << __func__ << " aid: " << HexString(aid.data(), aid.size()) << " (" << aid.size()
                   << ") p2 " << p2;
 
-        // TODO(b/123254068) - this is not an implementation of the OMAPI protocol or APDU.
-        // The functionality here is enough to exercise the framework, but actual
-        // calls to the secure element will fail. This implementation does not model
-        // channel isolation or any other aspects important to implementing secure element.
-        *_aidl_return = {0x90, 0x00, 0x00};  // DO NOT COPY
+        std::vector<uint8_t> select_response;
+        std::shared_ptr<se::Applet> applet = nullptr;
+
+        // The basic channel can only be opened once, and stays opened
+        // and locked until the channel is closed.
+        if (channels_[0].opened) {
+            LOG(INFO) << __func__ << " basic channel already opened";
+            return ScopedAStatus::fromServiceSpecificError(CHANNEL_NOT_AVAILABLE);
+        }
+
+        // If the AID is defined (the AID is not Null and the length of the
+        // AID is not 0) and the channel is not locked then the corresponding
+        // applet shall be selected.
+        if (aid.size() > 0) {
+            applet = SelectApplet(aid);
+            if (applet == nullptr) {
+                // No applet registered with matching AID.
+                LOG(INFO) << __func__ << " basic channel AID not found";
+                return ScopedAStatus::fromServiceSpecificError(NO_SUCH_ELEMENT_ERROR);
+            }
+            select_response = applet->Select(aid, p2);
+        }
+
+        // If the AID is a 0 length AID and the channel is not locked, the
+        // method will select the Issuer Security Domain of the SE by sending a
+        // SELECT command with a 0 length AID as defined in
+        // [GP Card specification].
+        if (aid.size() == 0) {
+            select_response = kIssuerSecurityDomainSelectResponse;
+        }
+
+        LOG(INFO) << __func__ << " sending response: "
+                  << HexString(select_response.data(), select_response.size());
+
+        // TODO(b/123254068) - this is not an implementation of the OMAPI protocol
+        // or APDU. The functionality here is enough to exercise the framework,
+        // but actual calls to the secure element will fail. This implementation
+        // does not model channel isolation or any other aspects important to
+        // implementing secure element.
+        channels_[0] = Channel(aid, p2, applet);
+        *aidl_return = select_response;
         return ScopedAStatus::ok();
     }
+
     ScopedAStatus openLogicalChannel(
             const std::vector<uint8_t>& aid, int8_t p2,
-            ::aidl::android::hardware::secure_element::LogicalChannelResponse* _aidl_return)
+            ::aidl::android::hardware::secure_element::LogicalChannelResponse* aidl_return)
             override {
         LOG(INFO) << __func__ << " aid: " << HexString(aid.data(), aid.size()) << " (" << aid.size()
                   << ") p2 " << p2;
 
-        if (aid != kAndroidTestAid && aid != kLongAndroidTestAid) {
-            return ScopedAStatus::fromServiceSpecificError(NO_SUCH_ELEMENT_ERROR);
+        size_t channel_number = 1;
+        std::vector<uint8_t> select_response;
+        std::shared_ptr<se::Applet> applet = nullptr;
+
+        // Look for an available channel number.
+        for (; channel_number < channels_.size(); channel_number++) {
+            if (channels_[channel_number].opened == false) {
+                break;
+            }
         }
 
-        *_aidl_return = LogicalChannelResponse{.channelNumber = 1, .selectResponse = {}};
+        // All channels are currently allocated.
+        if (channel_number >= channels_.size()) {
+            LOG(INFO) << __func__ << " all logical channels already opened";
+            return ScopedAStatus::fromServiceSpecificError(CHANNEL_NOT_AVAILABLE);
+        }
 
-        // TODO(b/123254068) - this is not an implementation of the OMAPI protocol or APDU.
-        // The functionality here is enough to exercise the framework, but actual
-        // calls to the secure element will fail. This implementation does not model
-        // channel isolation or any other aspects important to implementing secure element.
-        if (aid == kAndroidTestAid) {                                 // DO NOT COPY
-            size_t size = 2050;                                       // DO NOT COPY
-            _aidl_return->selectResponse.resize(size);                // DO NOT COPY
-            _aidl_return->selectResponse[size - 1] = 0x00;            // DO NOT COPY
-            _aidl_return->selectResponse[size - 2] = 0x90;            // DO NOT COPY
-        } else {                                                      // DO NOT COPY
-            _aidl_return->selectResponse = {0x00, 0x00, 0x90, 0x00};  // DO NOT COPY
-        }                                                             // DO NOT COPY
+        // If the AID is defined (the AID is not Null and the length of the
+        // AID is not 0) then the corresponding applet shall be selected.
+        if (aid.size() > 0) {
+            applet = SelectApplet(aid);
+            if (applet == nullptr) {
+                // No applet registered with matching AID.
+                LOG(INFO) << __func__ << " logical channel AID not found";
+                return ScopedAStatus::fromServiceSpecificError(NO_SUCH_ELEMENT_ERROR);
+            }
+            select_response = applet->Select(aid, p2);
+        }
+
+        // If the length of the AID is 0, the method will select the
+        // Issuer Security Domain of the SE by sending a SELECT command
+        // with 0 length AID as defined in [GPCS].
+        if (aid.size() == 0) {
+            select_response = kIssuerSecurityDomainSelectResponse;
+        }
 
         LOG(INFO) << __func__ << " sending response: "
-                  << HexString(_aidl_return->selectResponse.data(),
-                               _aidl_return->selectResponse.size());
+                  << HexString(select_response.data(), select_response.size());
 
+        // TODO(b/123254068) - this is not an implementation of the OMAPI protocol
+        // or APDU. The functionality here is enough to exercise the framework,
+        // but actual calls to the secure element will fail. This implementation
+        // does not model channel isolation or any other aspects important to
+        // implementing secure element.
+        channels_[channel_number] = Channel(aid, p2, applet);
+        *aidl_return = LogicalChannelResponse{
+                .channelNumber = static_cast<int8_t>(channel_number),
+                .selectResponse = select_response,
+        };
         return ScopedAStatus::ok();
     }
-    ScopedAStatus reset() override {
-        LOG(INFO) << __func__;
-        mCb->onStateChange(false, "reset");
-        mCb->onStateChange(true, "reset");
+
+    ScopedAStatus closeChannel(int8_t channel_number) override {
+        LOG(INFO) << __func__ << " channel number: " << static_cast<int>(channel_number);
+        // The selected basic or logical channel is not opened.
+        if (channel_number >= channels_.size() || !channels_[channel_number].opened) {
+            return ScopedAStatus::ok();
+        }
+
+        // TODO(b/123254068) - this is not an implementation of the OMAPI protocol
+        // or APDU. The functionality here is enough to exercise the framework,
+        // but actual calls to the secure element will fail. This implementation
+        // does not model channel isolation or any other aspects important to
+        // implementing secure element.
+        channels_[channel_number].opened = false;
         return ScopedAStatus::ok();
     }
+
     ScopedAStatus transmit(const std::vector<uint8_t>& data,
-                           std::vector<uint8_t>* _aidl_return) override {
+                           std::vector<uint8_t>* aidl_return) override {
         LOG(INFO) << __func__ << " data: " << HexString(data.data(), data.size()) << " ("
                   << data.size() << ")";
 
-        // TODO(b/123254068) - this is not an implementation of the OMAPI protocol or APDU.
-        // The functionality here is enough to exercise the framework, but actual
-        // calls to the secure element will fail. This implementation does not model
-        // channel isolation or any other aspects important to implementing secure element.
+        se::Apdu apdu(data);
+        uint8_t channel_number = apdu.get_channel_number();
+        std::vector<uint8_t> response_apdu;
 
-        std::string hex = HexString(data.data(), data.size());                    // DO NOT COPY
-        if (hex == "01a4040210a000000476416e64726f696443545331") {                // DO NOT COPY
-            *_aidl_return = {0x00, 0x6A, 0x00};                                   // DO NOT COPY
-        } else if (data == std::vector<uint8_t>{0x00, 0xF4, 0x00, 0x00, 0x00}) {  // DO NOT COPY
-            // CHECK_SELECT_P2_APDU w/ channel 1 // DO NOT COPY
-            *_aidl_return = {0x00, 0x90, 0x00};                                   // DO NOT COPY
-        } else if (data == std::vector<uint8_t>{0x01, 0xF4, 0x00, 0x00, 0x00}) {  // DO NOT COPY
-            // CHECK_SELECT_P2_APDU w/ channel 1 // DO NOT COPY
-            *_aidl_return = {0x00, 0x90, 0x00};             // DO NOT COPY
-        } else if (data.size() == 5 || data.size() == 8) {  // DO NOT COPY
-            // SEGMENTED_RESP_APDU - happens to use length 5 and 8 // DO NOT COPY
-            size_t size = (data[2] << 8 | data[3]) + 2;       // DO NOT COPY
-            _aidl_return->resize(size);                       // DO NOT COPY
-            (*_aidl_return)[size - 1] = 0x00;                 // DO NOT COPY
-            (*_aidl_return)[size - 2] = 0x90;                 // DO NOT COPY
-            if (size >= 3) (*_aidl_return)[size - 3] = 0xFF;  // DO NOT COPY
-        } else {                                              // DO NOT COPY
-            *_aidl_return = {0x90, 0x00, 0x00};               // DO NOT COPY
-        }                                                     // DO NOT COPY
+        switch (apdu.get_ins()) {
+            // TODO(b/123254068) - Implement support channel management APDUs.
+            case MANAGE_CHANNEL_INS:
+                // P1 = '00' to open
+                // P1 = '80' to close
+                LOG(INFO) << __func__ << " MANAGE_CHANNEL apdu";
+                response_apdu =
+                        se::Apdu::CreateResponse(se::Status::SW_LOGICAL_CHANNEL_NOT_SUPPORTED);
+                break;
 
+            // TODO(b/123254068) - Implement support channel management APDUs.
+            case SELECT_INS:
+                LOG(INFO) << __func__ << " SELECT apdu";
+                response_apdu =
+                        se::Apdu::CreateResponse(se::Status::SW_LOGICAL_CHANNEL_NOT_SUPPORTED);
+                break;
+
+            default:
+                CHECK(channel_number < channels_.size()) << " invalid channel number";
+                if (!channels_[channel_number].opened) {
+                    LOG(INFO) << __func__ << " the channel " << static_cast<int>(channel_number)
+                              << " is not opened";
+                    response_apdu =
+                            se::Apdu::CreateResponse(se::Status::SW_LOGICAL_CHANNEL_NOT_SUPPORTED);
+                    break;
+                }
+                // Send the APDU to the applet for processing.
+                // Applet implementation is optional, default to sending
+                // SW_INS_NOT_SUPPORTED.
+                if (channels_[channel_number].applet == nullptr) {
+                    response_apdu = se::Apdu::CreateResponse(se::Status::SW_INS_NOT_SUPPORTED);
+                } else {
+                    response_apdu = channels_[channel_number].applet->Process(apdu);
+                }
+                break;
+        }
+
+        aidl_return->assign(response_apdu.begin(), response_apdu.end());
+        LOG(INFO) << __func__
+                  << " response: " << HexString(aidl_return->data(), aidl_return->size()) << " ("
+                  << aidl_return->size() << ")";
         return ScopedAStatus::ok();
     }
 
   private:
-    std::shared_ptr<ISecureElementCallback> mCb;
+    struct Channel {
+      public:
+        Channel() = default;
+        Channel(Channel const&) = default;
+        Channel(se::Aid const& aid, uint8_t p2, std::shared_ptr<se::Applet> applet)
+            : opened(true), aid(aid), p2(p2), applet(std::move(applet)) {}
+        Channel& operator=(Channel const&) = default;
+
+        bool opened{false};
+        se::Aid aid{};
+        uint8_t p2{0};
+        std::shared_ptr<se::Applet> applet{nullptr};
+    };
+
+    // OMAPI abstraction.
+
+    // Channel 0 is the basic channel, channels 1-19 are the logical channels.
+    std::array<Channel, 20> channels_{};
+    std::shared_ptr<ISecureElementCallback> client_callback_;
+
+    // Secure element abstraction.
+
+    static const uint8_t MANAGE_CHANNEL_INS = 0x70;
+    static const uint8_t SELECT_INS = 0xa4;
+
+    // Secure element ATR (Answer-To-Reset).
+    // The format is specified by ISO/IEC 1816-4 2020 and lists
+    // the capabilities of the card.
+    //
+    // TODO(b/123254068): encode the default SE properties in the ATR:
+    // support for extended Lc / Le fields, maximum number of logical channels.
+    // The CTS tests are *not* checking this value.
+    std::vector<uint8_t> const atr_{};
+
+    // Applet registration.
+    std::vector<std::pair<se::Aid, std::shared_ptr<se::Applet>>> applets_{};
+
+    // Return the first applet that matches the selected aid.
+    std::shared_ptr<se::Applet> SelectApplet(se::Aid const& aid) {
+        for (auto& [applet_aid, applet] : applets_) {
+            if (applet_aid == aid) {
+                return applet;
+            }
+        }
+        return nullptr;
+    }
 };
 
 int main() {
     ABinderProcess_setThreadPoolMaxThreadCount(0);
 
-    auto se = ndk::SharedRefBase::make<MySecureElement>();
+    auto se = ndk::SharedRefBase::make<EmulatedSecureElement>();
     const std::string name = std::string() + BnSecureElement::descriptor + "/eSE1";
     binder_status_t status = AServiceManager_addService(se->asBinder().get(), name.c_str());
     CHECK_EQ(status, STATUS_OK);
diff --git a/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl b/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
index 5485db3..ff710f1 100644
--- a/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
+++ b/security/rkp/aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.aidl
@@ -115,11 +115,9 @@
 interface IRemotelyProvisionedComponent {
     const int STATUS_FAILED = 1;
     const int STATUS_INVALID_MAC = 2;
-    // --------- START: Versions 1 and 2 Only ----------
-    const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3;
+    const int STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 3; // Versions 1 and 2 Only
     const int STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 4;
-    const int STATUS_INVALID_EEK = 5;
-    // --------- END: Versions 1 and 2 Only ------------
+    const int STATUS_INVALID_EEK = 5; // Versions 1 and 2 Only
     const int STATUS_REMOVED = 6;
 
     /**
@@ -224,7 +222,7 @@
      *                2 : bstr             ; KID : EEK ID
      *                3 : -25,             ; Algorithm : ECDH-ES + HKDF-256
      *                -1 : 4,              ; Curve : X25519
-     *                -2 : bstr            ; X25519 public key
+     *                -2 : bstr            ; X25519 public key, little-endian
      *            }
      *
      *            EekP256 = {              ; COSE_Key
@@ -347,8 +345,8 @@
      *     protected: bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
      *     unprotected: {},
      *     payload: bstr .cbor Data / nil,
-     *     signature: bstr      ; PureEd25519(CDI_Leaf_Priv, bstr .cbor SignedDataSigStruct<Data>) /
-     *                          ; ECDSA(CDI_Leaf_Priv, bstr .cbor SignedDataSigStruct<Data>)
+     *     signature: bstr      ; PureEd25519(CDI_Leaf_Priv, SignedDataSigStruct<Data>) /
+     *                          ; ECDSA(CDI_Leaf_Priv, SignedDataSigStruct<Data>)
      * ]
      *
      * ; Sig_structure for SignedData
@@ -427,8 +425,8 @@
      *     protected : bstr .cbor { 1 : AlgorithmEdDSA / AlgorithmES256 },
      *     unprotected: {},
      *     payload: bstr .cbor DiceChainEntryPayload,
-     *     signature: bstr ; PureEd25519(SigningKey, bstr .cbor DiceChainEntryInput) /
-     *                     ; ECDSA(SigningKey, bstr .cbor DiceChainEntryInput)
+     *     signature: bstr ; PureEd25519(SigningKey, DiceChainEntryInput) /
+     *                     ; ECDSA(SigningKey, DiceChainEntryInput)
      *                     ; See RFC 8032 for details of how to encode the signature value
      *                     ; for Ed25519.
      * ]
@@ -442,12 +440,9 @@
      *
      * ; The following section defines some types that are reused throughout the above
      * ; data structures.
-     * PubKeyX25519 = {                 ; COSE_Key
-     *      1 : 1,                      ; Key type : Octet Key Pair
-     *     -1 : 4,                      ; Curve : X25519
-     *     -2 : bstr                    ; Sender X25519 public key
-     * }
-     *
+     * ; NOTE: Integer encoding is different for Ed25519 and P256 keys:
+     * ;       - Ed25519 is LE: https://www.rfc-editor.org/rfc/rfc8032#section-3.1
+     * ;       - P256 is BE: https://www.secg.org/sec1-v2.pdf#page=19 (section 2.3.7)
      * PubKeyEd25519 = {                ; COSE_Key
      *     1 : 1,                       ; Key type : octet key pair
      *     3 : AlgorithmEdDSA,          ; Algorithm : EdDSA
@@ -455,19 +450,12 @@
      *     -2 : bstr                    ; X coordinate, little-endian
      * }
      *
-     * PubKeyEcdhP256 = {               ; COSE_Key
-     *      1 : 2,                      ; Key type : EC2
-     *      -1 : 1,                     ; Curve : P256
-     *      -2 : bstr                   ; Sender X coordinate
-     *      -3 : bstr                   ; Sender Y coordinate
-     * }
-     *
      * PubKeyECDSA256 = {               ; COSE_Key
      *     1 : 2,                       ; Key type : EC2
      *     3 : AlgorithmES256,          ; Algorithm : ECDSA w/ SHA-256
      *     -1 : 1,                      ; Curve: P256
-     *     -2 : bstr,                   ; X coordinate
-     *     -3 : bstr                    ; Y coordinate
+     *     -2 : bstr,                   ; X coordinate, big-endian
+     *     -3 : bstr                    ; Y coordinate, big-endian
      * }
      *
      * AlgorithmES256 = -7
diff --git a/security/rkp/aidl/android/hardware/security/keymint/MacedPublicKey.aidl b/security/rkp/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
index 275e322..1e41d1b 100644
--- a/security/rkp/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
+++ b/security/rkp/aidl/android/hardware/security/keymint/MacedPublicKey.aidl
@@ -36,12 +36,15 @@
      *     ]
      *
      *     ; NOTE: -70000 is deprecated for v3 HAL implementations.
+     *     ; NOTE: Integer encoding is different for Ed25519 and P256 keys:
+     *     ;       - Ed25519 is LE: https://www.rfc-editor.org/rfc/rfc8032#section-3.1
+     *     ;       - P256 is BE: https://www.secg.org/sec1-v2.pdf#page=19 (section 2.3.7)
      *     PublicKey = {               ; COSE_Key
      *         1 : 2,                  ; Key type : EC2
      *         3 : -7,                 ; Algorithm : ES256
      *         -1 : 1,                 ; Curve : P256
-     *         -2 : bstr,              ; X coordinate, little-endian
-     *         -3 : bstr,              ; Y coordinate, little-endian
+     *         -2 : bstr,              ; X coordinate, big-endian
+     *         -3 : bstr,              ; Y coordinate, big-endian
      *         -70000 : nil            ; Presence indicates this is a test key. If set, K_mac is
      *                                 ; all zeros.
      *     },
diff --git a/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl b/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl
index d59508b..3f699bc 100644
--- a/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl
+++ b/security/rkp/aidl/android/hardware/security/keymint/ProtectedData.aidl
@@ -209,7 +209,7 @@
      *     PubKeyX25519 = {                 ; COSE_Key
      *          1 : 1,                      ; Key type : Octet Key Pair
      *         -1 : 4,                      ; Curve : X25519
-     *         -2 : bstr                    ; Sender X25519 public key
+     *         -2 : bstr                    ; Sender X25519 public key, little-endian
      *     }
      *
      *     PubKeyEd25519 = {                ; COSE_Key
@@ -222,16 +222,16 @@
      *     PubKeyEcdhP256 = {               ; COSE_Key
      *          1 : 2,                      ; Key type : EC2
      *          -1 : 1,                     ; Curve : P256
-     *          -2 : bstr                   ; Sender X coordinate
-     *          -3 : bstr                   ; Sender Y coordinate
+     *          -2 : bstr                   ; Sender X coordinate, big-endian
+     *          -3 : bstr                   ; Sender Y coordinate, big-endian
      *     }
      *
      *     PubKeyECDSA256 = {               ; COSE_Key
      *         1 : 2,                       ; Key type : EC2
      *         3 : AlgorithmES256,          ; Algorithm : ECDSA w/ SHA-256
      *         -1 : 1,                      ; Curve: P256
-     *         -2 : bstr,                   ; X coordinate
-     *         -3 : bstr                    ; Y coordinate
+     *         -2 : bstr,                   ; X coordinate, big-endian
+     *         -3 : bstr                    ; Y coordinate, big-endian
      *     }
      *
      *     AlgorithmES256 = -7
diff --git a/security/rkp/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/rkp/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
index aebcf67..bbda56d 100644
--- a/security/rkp/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
+++ b/security/rkp/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp
@@ -223,6 +223,20 @@
     ASSERT_TRUE(provisionable_->getHardwareInfo(&hwInfo).isOk());
 
     const std::set<int> validCurves = {RpcHardwareInfo::CURVE_P256, RpcHardwareInfo::CURVE_25519};
+    // First check for the implementations that supports only IRPC V3+.
+    if (rpcHardwareInfo.versionNumber >= VERSION_WITHOUT_TEST_MODE) {
+        bytevec keysToSignMac;
+        DeviceInfo deviceInfo;
+        ProtectedData protectedData;
+        auto status = provisionable_->generateCertificateRequest(false, {}, {}, {}, &deviceInfo,
+                                                                 &protectedData, &keysToSignMac);
+        if (!status.isOk() &&
+            (status.getServiceSpecificError() == BnRemotelyProvisionedComponent::STATUS_REMOVED)) {
+            ASSERT_EQ(hwInfo.supportedEekCurve, RpcHardwareInfo::CURVE_NONE)
+                    << "Invalid curve: " << hwInfo.supportedEekCurve;
+            return;
+        }
+    }
     ASSERT_EQ(validCurves.count(hwInfo.supportedEekCurve), 1)
             << "Invalid curve: " << hwInfo.supportedEekCurve;
 }