Make AudioMix, AudioMixMatchCriterion & AudioMixingRule Parcelable

Bug: 293874525
Test: atest AudioMixUnitTests AudioMixingRuleUnitTests
Test: atest AudioHostTest AudioServiceHostTest
Change-Id: Id84c587f1edf5754496ed7f803c217017b43e605
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 2137f47..ddf0607 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -7276,8 +7276,11 @@
 
 package android.media.audiopolicy {
 
-  public class AudioMix {
+  public class AudioMix implements android.os.Parcelable {
+    method public int describeContents();
     method public int getMixState();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioMix> CREATOR;
     field public static final int MIX_STATE_DISABLED = -1; // 0xffffffff
     field public static final int MIX_STATE_IDLE = 0; // 0x0
     field public static final int MIX_STATE_MIXING = 1; // 0x1
@@ -7286,15 +7289,18 @@
   }
 
   public static class AudioMix.Builder {
-    ctor public AudioMix.Builder(android.media.audiopolicy.AudioMixingRule) throws java.lang.IllegalArgumentException;
+    ctor public AudioMix.Builder(@NonNull android.media.audiopolicy.AudioMixingRule) throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioMix build() throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioMix.Builder setDevice(@NonNull android.media.AudioDeviceInfo) throws java.lang.IllegalArgumentException;
-    method public android.media.audiopolicy.AudioMix.Builder setFormat(android.media.AudioFormat) throws java.lang.IllegalArgumentException;
+    method public android.media.audiopolicy.AudioMix.Builder setFormat(@NonNull android.media.AudioFormat) throws java.lang.IllegalArgumentException;
     method public android.media.audiopolicy.AudioMix.Builder setRouteFlags(int) throws java.lang.IllegalArgumentException;
   }
 
-  public class AudioMixingRule {
+  public class AudioMixingRule implements android.os.Parcelable {
+    method public int describeContents();
     method public int getTargetMixRole();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.media.audiopolicy.AudioMixingRule> CREATOR;
     field public static final int MIX_ROLE_INJECTOR = 1; // 0x1
     field public static final int MIX_ROLE_PLAYERS = 0; // 0x0
     field public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 2; // 0x2
diff --git a/media/java/android/media/audiopolicy/AudioMix.aidl b/media/java/android/media/audiopolicy/AudioMix.aidl
new file mode 100644
index 0000000..d17a644
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMix.aidl
@@ -0,0 +1,3 @@
+package android.media.audiopolicy;
+
+parcelable AudioMix;
\ No newline at end of file
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index d0270d3..d592cf2 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -21,12 +21,15 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioSystem;
 import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -38,12 +41,12 @@
  * @hide
  */
 @SystemApi
-public class AudioMix {
+public class AudioMix implements Parcelable {
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private AudioMixingRule mRule;
+    private @NonNull AudioMixingRule mRule;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    private AudioFormat mFormat;
+    private @NonNull AudioFormat mFormat;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int mRouteFlags;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -54,7 +57,7 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     int mCallbackFlags;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    String mDeviceAddress;
+    @NonNull String mDeviceAddress;
 
     // initialized in constructor, read by AudioPolicyConfig
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -63,10 +66,11 @@
     /**
      * All parameters are guaranteed valid through the Builder.
      */
-    private AudioMix(AudioMixingRule rule, AudioFormat format, int routeFlags, int callbackFlags,
-            int deviceType, String deviceAddress) {
-        mRule = rule;
-        mFormat = format;
+    private AudioMix(@NonNull AudioMixingRule rule, @NonNull AudioFormat format,
+            int routeFlags, int callbackFlags,
+            int deviceType, @Nullable String deviceAddress) {
+        mRule = Objects.requireNonNull(rule);
+        mFormat = Objects.requireNonNull(format);
         mRouteFlags = routeFlags;
         mMixType = rule.getTargetMixType();
         mCallbackFlags = callbackFlags;
@@ -269,6 +273,49 @@
         return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // write mix route flags
+        dest.writeInt(mRouteFlags);
+        // write callback flags
+        dest.writeInt(mCallbackFlags);
+        // write device information
+        dest.writeInt(mDeviceSystemType);
+        dest.writeString8(mDeviceAddress);
+        mFormat.writeToParcel(dest, flags);
+        mRule.writeToParcel(dest, flags);
+    }
+
+    public static final @NonNull Parcelable.Creator<AudioMix> CREATOR = new Parcelable.Creator<>() {
+        /**
+         * Rebuilds an AudioMix previously stored with writeToParcel().
+         *
+         * @param p Parcel object to read the AudioMix from
+         * @return a new AudioMix created from the data in the parcel
+         */
+        public AudioMix createFromParcel(Parcel p) {
+            final AudioMix.Builder mixBuilder = new AudioMix.Builder();
+            // read mix route flags
+            mixBuilder.setRouteFlags(p.readInt());
+            // read callback flags
+            mixBuilder.setCallbackFlags(p.readInt());
+            // read device information
+            mixBuilder.setDevice(p.readInt(), p.readString8());
+            mixBuilder.setFormat(AudioFormat.CREATOR.createFromParcel(p));
+            mixBuilder.setMixingRule(AudioMixingRule.CREATOR.createFromParcel(p));
+            return mixBuilder.build();
+        }
+
+        public AudioMix[] newArray(int size) {
+            return new AudioMix[size];
+        }
+    };
+
     /** @hide */
     @IntDef(flag = true,
             value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
@@ -298,7 +345,7 @@
          * @param rule a non-null {@link AudioMixingRule} instance.
          * @throws IllegalArgumentException
          */
-        public Builder(AudioMixingRule rule)
+        public Builder(@NonNull AudioMixingRule rule)
                 throws IllegalArgumentException {
             if (rule == null) {
                 throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
@@ -313,7 +360,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        Builder setMixingRule(AudioMixingRule rule)
+        Builder setMixingRule(@NonNull AudioMixingRule rule)
                 throws IllegalArgumentException {
             if (rule == null) {
                 throw new IllegalArgumentException("Illegal null AudioMixingRule argument");
@@ -358,7 +405,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        public Builder setFormat(AudioFormat format)
+        public Builder setFormat(@NonNull AudioFormat format)
                 throws IllegalArgumentException {
             if (format == null) {
                 throw new IllegalArgumentException("Illegal null AudioFormat argument");
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.aidl b/media/java/android/media/audiopolicy/AudioMixingRule.aidl
new file mode 100644
index 0000000..5c06538
--- /dev/null
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.aidl
@@ -0,0 +1,3 @@
+package android.media.audiopolicy;
+
+parcelable AudioMixingRule;
\ No newline at end of file
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 9c0b825f..e5debb8 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -26,8 +26,11 @@
 import android.media.MediaRecorder;
 import android.os.Build;
 import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -50,7 +53,7 @@
  * </pre>
  */
 @SystemApi
-public class AudioMixingRule {
+public class AudioMixingRule implements Parcelable {
 
     private AudioMixingRule(int mixType, Collection<AudioMixMatchCriterion> criteria,
                             boolean allowPrivilegedMediaPlaybackCapture,
@@ -130,7 +133,7 @@
             RULE_EXCLUSION_MASK | RULE_MATCH_AUDIO_SESSION_ID;
 
     /** @hide */
-    public static final class AudioMixMatchCriterion {
+    public static final class AudioMixMatchCriterion implements Parcelable {
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
         final AudioAttributes mAttr;
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -139,18 +142,44 @@
         final int mRule;
 
         /** input parameters must be valid */
-        AudioMixMatchCriterion(AudioAttributes attributes, int rule) {
+        @VisibleForTesting
+        public AudioMixMatchCriterion(AudioAttributes attributes, int rule) {
             mAttr = attributes;
             mIntProp = Integer.MIN_VALUE;
             mRule = rule;
         }
         /** input parameters must be valid */
-        AudioMixMatchCriterion(Integer intProp, int rule) {
+        @VisibleForTesting
+        public AudioMixMatchCriterion(Integer intProp, int rule) {
             mAttr = null;
             mIntProp = intProp.intValue();
             mRule = rule;
         }
 
+        private AudioMixMatchCriterion(@NonNull Parcel in) {
+            Objects.requireNonNull(in);
+            mRule = in.readInt();
+            final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
+            switch (match_rule) {
+                case RULE_MATCH_ATTRIBUTE_USAGE:
+                case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+                    mAttr = AudioAttributes.CREATOR.createFromParcel(in);
+                    mIntProp = Integer.MIN_VALUE;
+                    break;
+                case RULE_MATCH_UID:
+                case RULE_MATCH_USERID:
+                case RULE_MATCH_AUDIO_SESSION_ID:
+                    mIntProp = in.readInt();
+                    mAttr = null;
+                    break;
+                default:
+                    // assume there was in int value to read as for now they come in pair
+                    in.readInt();
+                    throw new IllegalArgumentException(
+                            "Illegal rule value " + mRule + " in parcel");
+            }
+        }
+
         @Override
         public int hashCode() {
             return Objects.hash(mAttr, mIntProp, mRule);
@@ -170,7 +199,13 @@
                     && Objects.equals(mAttr, other.mAttr);
         }
 
-        void writeToParcel(Parcel dest) {
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel dest, int flags) {
             dest.writeInt(mRule);
             final int match_rule = mRule & ~RULE_EXCLUSION_MASK;
             switch (match_rule) {
@@ -190,6 +225,22 @@
             }
         }
 
+        public static final @NonNull Parcelable.Creator<AudioMixMatchCriterion> CREATOR =
+                new Parcelable.Creator<>() {
+            /**
+             * Rebuilds an AudioMixMatchCriterion previously stored with writeToParcel().
+             *
+             * @param p Parcel object to read the AudioMix from
+             * @return a new AudioMixMatchCriterion created from the data in the parcel
+             */
+            public AudioMixMatchCriterion createFromParcel(Parcel p) {
+                return new AudioMixMatchCriterion(p);
+            }
+            public AudioMixMatchCriterion[] newArray(int size) {
+                return new AudioMixMatchCriterion[size];
+            }
+        };
+
         public AudioAttributes getAudioAttributes() { return mAttr; }
         public int getIntProp() { return mIntProp; }
         public int getRule() { return mRule; }
@@ -605,13 +656,14 @@
                 if (!(property instanceof AudioAttributes)) {
                     throw new IllegalArgumentException("Invalid AudioAttributes argument");
                 }
-                return addRuleInternal((AudioAttributes) property, null, rule);
+                return addRuleInternal(
+                        new AudioMixMatchCriterion((AudioAttributes) property, rule));
             } else {
                 // implies integer match rule
                 if (!(property instanceof Integer)) {
                     throw new IllegalArgumentException("Invalid Integer argument");
                 }
-                return addRuleInternal(null, (Integer) property, rule);
+                return addRuleInternal(new AudioMixMatchCriterion((Integer) property, rule));
             }
         }
 
@@ -636,12 +688,13 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        private Builder addRuleInternal(AudioAttributes attrToMatch, Integer intProp, int rule)
+        private Builder addRuleInternal(AudioMixMatchCriterion criterion)
                 throws IllegalArgumentException {
             // If mix type is invalid and added rule is valid only for the players / recorders,
             // adjust the mix type accordingly.
             // Otherwise, if the mix type was already deduced or set explicitly, verify the rule
             // is valid for the mix type.
+            final int rule = criterion.mRule;
             if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
                 if (isPlayerRule(rule)) {
                     mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
@@ -655,51 +708,16 @@
             }
             synchronized (mCriteria) {
                 int oppositeRule = rule ^ RULE_EXCLUSION_MASK;
-                if (mCriteria.stream().anyMatch(criterion -> criterion.mRule == oppositeRule)) {
+                if (mCriteria.stream().anyMatch(
+                        otherCriterion -> otherCriterion.mRule == oppositeRule)) {
                     throw new IllegalArgumentException("AudioMixingRule cannot contain RULE_MATCH_*"
                             + " and RULE_EXCLUDE_* for the same dimension.");
                 }
-                int ruleWithoutExclusion = rule & ~RULE_EXCLUSION_MASK;
-                switch (ruleWithoutExclusion) {
-                    case RULE_MATCH_ATTRIBUTE_USAGE:
-                    case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
-                        mCriteria.add(new AudioMixMatchCriterion(attrToMatch, rule));
-                        break;
-                    case RULE_MATCH_UID:
-                    case RULE_MATCH_USERID:
-                    case RULE_MATCH_AUDIO_SESSION_ID:
-                        mCriteria.add(new AudioMixMatchCriterion(intProp, rule));
-                        break;
-                    default:
-                        throw new IllegalStateException("Unreachable code in addRuleInternal()");
-                }
+                mCriteria.add(criterion);
             }
             return this;
         }
 
-        Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
-            final int rule = in.readInt();
-            final int match_rule = rule & ~RULE_EXCLUSION_MASK;
-            AudioAttributes attr = null;
-            Integer intProp = null;
-            switch (match_rule) {
-                case RULE_MATCH_ATTRIBUTE_USAGE:
-                case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
-                    attr =  AudioAttributes.CREATOR.createFromParcel(in);
-                    break;
-                case RULE_MATCH_UID:
-                case RULE_MATCH_USERID:
-                case RULE_MATCH_AUDIO_SESSION_ID:
-                    intProp = new Integer(in.readInt());
-                    break;
-                default:
-                    // assume there was in int value to read as for now they come in pair
-                    in.readInt();
-                    throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
-            }
-            return addRuleInternal(attr, intProp, rule);
-        }
-
         /**
          * Combines all of the matching and exclusion rules that have been set and return a new
          * {@link AudioMixingRule} object.
@@ -717,4 +735,52 @@
                     mVoiceCommunicationCaptureAllowed);
         }
     }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        // write opt-out respect
+        dest.writeBoolean(mAllowPrivilegedPlaybackCapture);
+        // write voice communication capture allowed flag
+        dest.writeBoolean(mVoiceCommunicationCaptureAllowed);
+        // write specified mix type
+        dest.writeInt(mTargetMixType);
+        // write mix rules
+        dest.writeInt(mCriteria.size());
+        for (AudioMixingRule.AudioMixMatchCriterion criterion : mCriteria) {
+            criterion.writeToParcel(dest, flags);
+        }
+    }
+
+    public static final @NonNull Parcelable.Creator<AudioMixingRule> CREATOR =
+            new Parcelable.Creator<>() {
+
+        @Override
+        public AudioMixingRule createFromParcel(Parcel source) {
+            AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
+            // read opt-out respect
+            ruleBuilder.allowPrivilegedPlaybackCapture(source.readBoolean());
+            // read voice capture allowed flag
+            ruleBuilder.voiceCommunicationCaptureAllowed(source.readBoolean());
+            // read specified mix type
+            ruleBuilder.setTargetMixRole(source.readInt());
+            // read mix rules
+            int nbRules = source.readInt();
+            for (int j = 0; j < nbRules; j++) {
+                // read the matching rules
+                ruleBuilder.addRuleInternal(
+                        AudioMixMatchCriterion.CREATOR.createFromParcel(source));
+            }
+            return ruleBuilder.build();
+        }
+
+        @Override
+        public AudioMixingRule[] newArray(int size) {
+            return new AudioMixingRule[size];
+        }
+    };
 }
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index 7a85d21..0505b80 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -17,7 +17,6 @@
 package android.media.audiopolicy;
 
 import android.annotation.NonNull;
-import android.media.AudioFormat;
 import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -85,72 +84,20 @@
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeInt(mMixes.size());
         for (AudioMix mix : mMixes) {
-            // write mix route flags
-            dest.writeInt(mix.getRouteFlags());
-            // write callback flags
-            dest.writeInt(mix.mCallbackFlags);
-            // write device information
-            dest.writeInt(mix.mDeviceSystemType);
-            dest.writeString(mix.mDeviceAddress);
-            // write mix format
-            dest.writeInt(mix.getFormat().getSampleRate());
-            dest.writeInt(mix.getFormat().getEncoding());
-            dest.writeInt(mix.getFormat().getChannelMask());
-            // write opt-out respect
-            dest.writeBoolean(mix.getRule().allowPrivilegedMediaPlaybackCapture());
-            // write voice communication capture allowed flag
-            dest.writeBoolean(mix.getRule().voiceCommunicationCaptureAllowed());
-            // write specified mix type
-            dest.writeInt(mix.getRule().getTargetMixRole());
-            // write mix rules
-            final ArrayList<AudioMixMatchCriterion> criteria = mix.getRule().getCriteria();
-            dest.writeInt(criteria.size());
-            for (AudioMixMatchCriterion criterion : criteria) {
-                criterion.writeToParcel(dest);
-            }
+            mix.writeToParcel(dest, flags);
         }
     }
 
     private AudioPolicyConfig(Parcel in) {
-        mMixes = new ArrayList<AudioMix>();
         int nbMixes = in.readInt();
+        mMixes = new ArrayList<>(nbMixes);
         for (int i = 0 ; i < nbMixes ; i++) {
-            final AudioMix.Builder mixBuilder = new AudioMix.Builder();
-            // read mix route flags
-            int routeFlags = in.readInt();
-            mixBuilder.setRouteFlags(routeFlags);
-            // read callback flags
-            mixBuilder.setCallbackFlags(in.readInt());
-            // read device information
-            mixBuilder.setDevice(in.readInt(), in.readString());
-            // read mix format
-            int sampleRate = in.readInt();
-            int encoding = in.readInt();
-            int channelMask = in.readInt();
-            final AudioFormat format = new AudioFormat.Builder().setSampleRate(sampleRate)
-                    .setChannelMask(channelMask).setEncoding(encoding).build();
-            mixBuilder.setFormat(format);
-
-            AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
-            // read opt-out respect
-            ruleBuilder.allowPrivilegedPlaybackCapture(in.readBoolean());
-            // read voice capture allowed flag
-            ruleBuilder.voiceCommunicationCaptureAllowed(in.readBoolean());
-            // read specified mix type
-            ruleBuilder.setTargetMixRole(in.readInt());
-            // read mix rules
-            int nbRules = in.readInt();
-            for (int j = 0 ; j < nbRules ; j++) {
-                // read the matching rules
-                ruleBuilder.addRuleFromParcel(in);
-            }
-            mixBuilder.setMixingRule(ruleBuilder.build());
-            mMixes.add(mixBuilder.build());
+            mMixes.add(AudioMix.CREATOR.createFromParcel(in));
         }
     }
 
-    public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR
-            = new Parcelable.Creator<AudioPolicyConfig>() {
+    public static final @android.annotation.NonNull Parcelable.Creator<AudioPolicyConfig> CREATOR =
+            new Parcelable.Creator<>() {
         /**
          * Rebuilds an AudioPolicyConfig previously stored with writeToParcel().
          * @param p Parcel object to read the AudioPolicyConfig from
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
index a26398a..95aa71d 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixUnitTests.java
@@ -31,7 +31,6 @@
 import android.media.AudioSystem;
 import android.media.audiopolicy.AudioMix;
 import android.media.audiopolicy.AudioMixingRule;
-import android.media.audiopolicy.AudioPolicyConfig;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 
@@ -42,9 +41,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
  * Unit tests for AudioMix.
  *
@@ -262,13 +258,13 @@
 
 
     private static AudioMix writeToAndFromParcel(AudioMix audioMix) {
-        AudioPolicyConfig apc = new AudioPolicyConfig(new ArrayList<>(List.of(audioMix)));
         Parcel parcel = Parcel.obtain();
-        apc.writeToParcel(parcel, /*flags=*/0);
-        parcel.setDataPosition(0);
-        AudioMix unmarshalledMix =
-                AudioPolicyConfig.CREATOR.createFromParcel(parcel).getMixes().get(0);
-        parcel.recycle();
-        return unmarshalledMix;
+        try {
+            audioMix.writeToParcel(parcel, /*parcelableFlags=*/0);
+            parcel.setDataPosition(0);
+            return AudioMix.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
     }
 }
diff --git a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
index 3cbfd50..f04661d 100644
--- a/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
+++ b/media/tests/AudioPolicyTest/src/com/android/audiopolicytest/AudioMixingRuleUnitTests.java
@@ -39,10 +39,13 @@
 import android.media.AudioAttributes;
 import android.media.audiopolicy.AudioMixingRule;
 import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion;
+import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import com.google.common.testing.EqualsTester;
+
 import org.hamcrest.CustomTypeSafeMatcher;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
@@ -218,6 +221,94 @@
                 () -> new AudioMixingRule.Builder().build());
     }
 
+    @Test
+    public void audioMixMatchCriterion_equals_isCorrect() {
+        AudioMixMatchCriterion criterionUsage = new AudioMixMatchCriterion(
+                USAGE_MEDIA_AUDIO_ATTRIBUTES, RULE_MATCH_ATTRIBUTE_USAGE);
+        AudioMixMatchCriterion criterionExcludeUsage = new AudioMixMatchCriterion(
+                USAGE_MEDIA_AUDIO_ATTRIBUTES, RULE_EXCLUDE_ATTRIBUTE_USAGE);
+        AudioMixMatchCriterion criterionCapturePreset = new AudioMixMatchCriterion(
+                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES,
+                RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET);
+        AudioMixMatchCriterion criterionExcludeCapturePreset = new AudioMixMatchCriterion(
+                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES,
+                RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET);
+        AudioMixMatchCriterion criterionUid = new AudioMixMatchCriterion(TEST_UID, RULE_MATCH_UID);
+        AudioMixMatchCriterion criterionExcludeUid = new AudioMixMatchCriterion(TEST_UID,
+                RULE_EXCLUDE_UID);
+        AudioMixMatchCriterion criterionSessionId = new AudioMixMatchCriterion(TEST_SESSION_ID,
+                RULE_MATCH_UID);
+        AudioMixMatchCriterion criterionExcludeSessionId = new AudioMixMatchCriterion(
+                TEST_SESSION_ID, RULE_EXCLUDE_UID);
+
+        final EqualsTester equalsTester = new EqualsTester();
+        equalsTester.addEqualityGroup(criterionUsage, writeToAndFromParcel(criterionUsage));
+        equalsTester.addEqualityGroup(criterionExcludeUsage,
+                writeToAndFromParcel(criterionExcludeUsage));
+        equalsTester.addEqualityGroup(criterionCapturePreset,
+                writeToAndFromParcel(criterionCapturePreset));
+        equalsTester.addEqualityGroup(criterionExcludeCapturePreset,
+                writeToAndFromParcel(criterionExcludeCapturePreset));
+        equalsTester.addEqualityGroup(criterionUid, writeToAndFromParcel(criterionUid));
+        equalsTester.addEqualityGroup(criterionExcludeUid,
+                writeToAndFromParcel(criterionExcludeUid));
+        equalsTester.addEqualityGroup(criterionSessionId, writeToAndFromParcel(criterionSessionId));
+        equalsTester.addEqualityGroup(criterionExcludeSessionId,
+                writeToAndFromParcel(criterionExcludeSessionId));
+
+        equalsTester.testEquals();
+    }
+
+    @Test
+    public void audioMixingRule_equals_isCorrect() {
+        final EqualsTester equalsTester = new EqualsTester();
+
+        AudioMixingRule mixRule1 = new AudioMixingRule.Builder().addMixRule(
+                RULE_MATCH_AUDIO_SESSION_ID, TEST_SESSION_ID).excludeMixRule(RULE_MATCH_UID,
+                TEST_UID).build();
+        AudioMixingRule mixRule2 = new AudioMixingRule.Builder().addMixRule(
+                RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES).setTargetMixRole(
+                MIX_ROLE_INJECTOR).allowPrivilegedPlaybackCapture(true).build();
+        AudioMixingRule mixRule3 = new AudioMixingRule.Builder().addMixRule(
+                RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES).setTargetMixRole(
+                MIX_ROLE_INJECTOR).allowPrivilegedPlaybackCapture(false).build();
+        AudioMixingRule mixRule4 = new AudioMixingRule.Builder().addMixRule(
+                RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET,
+                CAPTURE_PRESET_VOICE_RECOGNITION_AUDIO_ATTRIBUTES).setTargetMixRole(
+                MIX_ROLE_INJECTOR).voiceCommunicationCaptureAllowed(true).build();
+
+        equalsTester.addEqualityGroup(mixRule1, writeToAndFromParcel(mixRule1));
+        equalsTester.addEqualityGroup(mixRule2, writeToAndFromParcel(mixRule2));
+        equalsTester.addEqualityGroup(mixRule3, writeToAndFromParcel(mixRule3));
+        equalsTester.addEqualityGroup(mixRule4, writeToAndFromParcel(mixRule4));
+
+        equalsTester.testEquals();
+    }
+
+    private static AudioMixMatchCriterion writeToAndFromParcel(AudioMixMatchCriterion criterion) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            criterion.writeToParcel(parcel, /*parcelableFlags=*/0);
+            parcel.setDataPosition(0);
+            return AudioMixMatchCriterion.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
+    private static AudioMixingRule writeToAndFromParcel(AudioMixingRule audioMixingRule) {
+        Parcel parcel = Parcel.obtain();
+        try {
+            audioMixingRule.writeToParcel(parcel, /*parcelableFlags=*/0);
+            parcel.setDataPosition(0);
+            return AudioMixingRule.CREATOR.createFromParcel(parcel);
+        } finally {
+            parcel.recycle();
+        }
+    }
+
 
     private static Matcher isAudioMixUidCriterion(int uid, boolean exclude) {
         return new CustomTypeSafeMatcher<AudioMixMatchCriterion>("uid mix criterion") {