AudioMix address and type, rule exclusion API, dynamic source

- Simplify API for defining an exclusion-based rule: don't define
  an exclusion rule, add instead a way to exclude a rule.
- API for defining rules for dynamic sources (rule match on capture
  preset).
- Verify mix type when creating AudioRecord or AudioTrack for a mix.
- Use hashcode of mix for generating the device address.
- AudioService dump prints info about registered policies.
- Annotate as SystemApi the audio policy-related APIs.
- Express mixing match and exclude rule constants as flags for
  future-proofness

Bug 16006090
Bug 16009464

Change-Id: I0dabe71204501acaffea7ef0ddbbab9700e1bd87
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index 20c4978..489f552 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -241,11 +241,11 @@
 
     /**
      * @hide
-     * CANDIDATE FOR PUBLIC API
      * Return the capture preset.
      * @return one of the values that can be set in {@link Builder#setCapturePreset(int)} or a
      *    negative value if none has been set.
      */
+    @SystemApi
     public int getCapturePreset() {
         return mSource;
     }
@@ -508,6 +508,7 @@
          *     {@link MediaRecorder.AudioSource#VOICE_COMMUNICATION}.
          * @return the same Builder instance.
          */
+        @SystemApi
         public Builder setCapturePreset(int preset) {
             switch (preset) {
                 case MediaRecorder.AudioSource.DEFAULT:
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e61b563..a764954 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2448,6 +2448,7 @@
      *     {@link #requestAudioFocus(OnAudioFocusChangeListener, AudioAttributes, int, int)}
      * @throws IllegalArgumentException
      */
+    @SystemApi
     public int requestAudioFocus(OnAudioFocusChangeListener l,
             @NonNull AudioAttributes requestAttributes,
             int durationHint,
@@ -2842,17 +2843,17 @@
 
     /**
      * @hide
-     * CANDIDATE FOR PUBLIC API
      * Register the given {@link AudioPolicy}.
      * This call is synchronous and blocks until the registration process successfully completed
      * or failed to complete.
-     * @param policy the {@link AudioPolicy} to register.
+     * @param policy the non-null {@link AudioPolicy} to register.
      * @return {@link #ERROR} if there was an error communicating with the registration service
      *    or if the user doesn't have the required
      *    {@link android.Manifest.permission#MODIFY_AUDIO_ROUTING} permission,
      *    {@link #SUCCESS} otherwise.
      */
-    public int registerAudioPolicy(AudioPolicy policy) {
+    @SystemApi
+    public int registerAudioPolicy(@NonNull AudioPolicy policy) {
         if (policy == null) {
             throw new IllegalArgumentException("Illegal null AudioPolicy argument");
         }
@@ -2874,16 +2875,17 @@
 
     /**
      * @hide
-     * CANDIDATE FOR PUBLIC API
-     * @param policy the {@link AudioPolicy} to unregister.
+     * @param policy the non-null {@link AudioPolicy} to unregister.
      */
-    public void unregisterAudioPolicyAsync(AudioPolicy policy) {
+    @SystemApi
+    public void unregisterAudioPolicyAsync(@NonNull AudioPolicy policy) {
         if (policy == null) {
             throw new IllegalArgumentException("Illegal null AudioPolicy argument");
         }
         IAudioService service = getService();
         try {
             service.unregisterAudioPolicyAsync(policy.token());
+            policy.setRegistration(null);
         } catch (RemoteException e) {
             Log.e(TAG, "Dead object in unregisterAudioPolicyAsync()", e);
         }
diff --git a/media/java/android/media/AudioService.java b/media/java/android/media/AudioService.java
index e5ae93c0..73997af 100644
--- a/media/java/android/media/AudioService.java
+++ b/media/java/android/media/AudioService.java
@@ -5520,6 +5520,8 @@
         pw.print("  mMusicActiveMs="); pw.println(mMusicActiveMs);
         pw.print("  mMcc="); pw.println(mMcc);
         pw.print("  mHasVibrator="); pw.println(mHasVibrator);
+
+        dumpAudioPolicies(pw);
     }
 
     private static String safeMediaVolumeStateToString(Integer state) {
@@ -5789,6 +5791,10 @@
         }
         synchronized (mAudioPolicies) {
             try {
+                if (mAudioPolicies.containsKey(cb)) {
+                    Slog.e(TAG, "Cannot re-register policy");
+                    return null;
+                }
                 AudioPolicyProxy app = new AudioPolicyProxy(policyConfig, cb);
                 cb.linkToDeath(app, 0/*flags*/);
                 regId = app.connectMixes();
@@ -5809,6 +5815,7 @@
             if (app == null) {
                 Slog.w(TAG, "Trying to unregister unknown audio policy for pid "
                         + Binder.getCallingPid() + " / uid " + Binder.getCallingUid());
+                return;
             } else {
                 cb.unlinkToDeath(app, 0/*flags*/);
             }
@@ -5817,12 +5824,21 @@
         // TODO implement clearing mix attribute matching info in native audio policy
     }
 
+    private void dumpAudioPolicies(PrintWriter pw) {
+        pw.println("\nAudio policies:");
+        synchronized (mAudioPolicies) {
+            for(AudioPolicyProxy policy : mAudioPolicies.values()) {
+                pw.println(policy.toLogFriendlyString());
+            }
+        }
+    }
+
     //======================
     // Audio policy proxy
     //======================
     /**
-     * This internal class inherits from AudioPolicyConfig which contains all the mixes and
-     * their configurations.
+     * This internal class inherits from AudioPolicyConfig, each instance contains all the
+     * mixes of an AudioPolicy and their configurations.
      */
     public class AudioPolicyProxy extends AudioPolicyConfig implements IBinder.DeathRecipient {
         private static final String TAG = "AudioPolicyProxy";
@@ -5830,7 +5846,7 @@
         IBinder mToken;
         AudioPolicyProxy(AudioPolicyConfig config, IBinder token) {
             super(config);
-            setRegistration(new String(config.toString() + ":ap:" + mAudioPolicyCounter++));
+            setRegistration(new String(config.hashCode() + ":ap:" + mAudioPolicyCounter++));
             mToken = token;
         }
 
@@ -5844,7 +5860,7 @@
 
         String connectMixes() {
             updateMixes(AudioSystem.DEVICE_STATE_AVAILABLE);
-            return mRegistrationId;
+            return getRegistration();
         }
 
         void disconnectMixes() {
@@ -5855,8 +5871,9 @@
             for (AudioMix mix : mMixes) {
                 // TODO implement sending the mix attribute matching info to native audio policy
                 if (DEBUG_AP) {
-                    Log.v(TAG, "AudioPolicyProxy connect mix state=" + connectionState
-                            + " addr=" + mix.getRegistration()); }
+                    Log.v(TAG, "AudioPolicyProxy mix new connection state=" + connectionState
+                            + " addr=" + mix.getRegistration());
+                }
                 AudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_IN_REMOTE_SUBMIX,
                         connectionState,
                         mix.getRegistration());
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index bb52682..1806662 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -17,21 +17,25 @@
 package android.media.audiopolicy;
 
 import android.annotation.IntDef;
+import android.annotation.SystemApi;
 import android.media.AudioFormat;
 import android.media.AudioSystem;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 
 /**
  * @hide
  */
+@SystemApi
 public class AudioMix {
 
     private AudioMixingRule mRule;
     private AudioFormat mFormat;
     private int mRouteFlags;
     private String mRegistrationId;
+    private int mMixType = MIX_TYPE_INVALID;
 
     /**
      * All parameters are guaranteed valid through the Builder.
@@ -41,20 +45,39 @@
         mFormat = format;
         mRouteFlags = routeFlags;
         mRegistrationId = null;
+        mMixType = rule.getTargetMixType();
     }
 
     /**
      * An audio mix behavior where the output of the mix is sent to the original destination of
      * the audio signal, i.e. an output device for an output mix, or a recording for an input mix.
      */
+    @SystemApi
     public static final int ROUTE_FLAG_RENDER    = 0x1;
     /**
      * An audio mix behavior where the output of the mix is rerouted back to the framework and
-     * is accessible for injection or capture through the {@link Audiotrack} and {@link AudioRecord}
+     * is accessible for injection or capture through the {@link AudioTrack} and {@link AudioRecord}
      * APIs.
      */
+    @SystemApi
     public static final int ROUTE_FLAG_LOOP_BACK = 0x1 << 1;
 
+    /**
+     * @hide
+     * Invalid mix type, default value.
+     */
+    public static final int MIX_TYPE_INVALID = -1;
+    /**
+     * @hide
+     * Mix type indicating playback streams are mixed.
+     */
+    public static final int MIX_TYPE_PLAYERS = 0;
+    /**
+     * @hide
+     * Mix type indicating recording streams are mixed.
+     */
+    public static final int MIX_TYPE_RECORDERS = 1;
+
     int getRouteFlags() {
         return mRouteFlags;
     }
@@ -67,6 +90,11 @@
         return mRule;
     }
 
+    /** @hide */
+    public int getMixType() {
+        return mMixType;
+    }
+
     void setRegistration(String regId) {
         mRegistrationId = regId;
     }
@@ -77,6 +105,12 @@
     }
 
     /** @hide */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
+    }
+
+    /** @hide */
     @IntDef(flag = true,
             value = { ROUTE_FLAG_RENDER, ROUTE_FLAG_LOOP_BACK } )
     @Retention(RetentionPolicy.SOURCE)
@@ -86,6 +120,7 @@
      * Builder class for {@link AudioMix} objects
      *
      */
+    @SystemApi
     public static class Builder {
         private AudioMixingRule mRule = null;
         private AudioFormat mFormat = null;
@@ -102,6 +137,7 @@
          * @param rule a non-null {@link AudioMixingRule} instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder(AudioMixingRule rule)
                 throws IllegalArgumentException {
             if (rule == null) {
@@ -132,6 +168,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder setFormat(AudioFormat format)
                 throws IllegalArgumentException {
             if (format == null) {
@@ -148,6 +185,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder setRouteFlags(@RouteFlags int routeFlags)
                 throws IllegalArgumentException {
             if (routeFlags == 0) {
@@ -166,6 +204,7 @@
          * @return a new {@link AudioMix} object
          * @throws IllegalArgumentException if no {@link AudioMixingRule} has been set.
          */
+        @SystemApi
         public AudioMix build() throws IllegalArgumentException {
             if (mRule == null) {
                 throw new IllegalArgumentException("Illegal null AudioMixingRule");
diff --git a/media/java/android/media/audiopolicy/AudioMixingRule.java b/media/java/android/media/audiopolicy/AudioMixingRule.java
index 2e06a80..02b03d2 100644
--- a/media/java/android/media/audiopolicy/AudioMixingRule.java
+++ b/media/java/android/media/audiopolicy/AudioMixingRule.java
@@ -16,10 +16,13 @@
 
 package android.media.audiopolicy;
 
+import android.annotation.SystemApi;
 import android.media.AudioAttributes;
+import android.os.Parcel;
 
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.Objects;
 
 
 /**
@@ -35,44 +38,114 @@
  *         .build();
  * </pre>
  */
+@SystemApi
 public class AudioMixingRule {
 
-    private AudioMixingRule(ArrayList<AttributeMatchCriterion> criteria) {
+    private AudioMixingRule(int mixType, ArrayList<AttributeMatchCriterion> criteria) {
         mCriteria = criteria;
+        mTargetMixType = mixType;
     }
 
     /**
-     * A rule requiring the usage information of the {@link AudioAttributes} to match
+     * A rule requiring the usage information of the {@link AudioAttributes} to match.
      */
+    @SystemApi
     public static final int RULE_MATCH_ATTRIBUTE_USAGE = 0x1;
     /**
-     * A rule requiring the usage information of the {@link AudioAttributes} to differ
+     * A rule requiring the capture preset information of the {@link AudioAttributes} to match.
      */
-    public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE = 0x1 << 1;
+    @SystemApi
+    public static final int RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET = 0x1 << 1;
+
+    private final static int RULE_EXCLUSION_MASK = 0x8000;
+    /**
+     * @hide
+     * A rule requiring the usage information of the {@link AudioAttributes} to differ.
+     */
+    public static final int RULE_EXCLUDE_ATTRIBUTE_USAGE =
+            RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_USAGE;
+    /**
+     * @hide
+     * A rule requiring the capture preset information of the {@link AudioAttributes} to differ.
+     */
+    public static final int RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET =
+            RULE_EXCLUSION_MASK | RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET;
 
     static final class AttributeMatchCriterion {
         AudioAttributes mAttr;
         int mRule;
 
+        /** input parameters must be valid */
         AttributeMatchCriterion(AudioAttributes attributes, int rule) {
             mAttr = attributes;
             mRule = rule;
         }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mAttr, mRule);
+        }
+
+        void writeToParcel(Parcel dest) {
+            dest.writeInt(mRule);
+            if ((mRule == RULE_MATCH_ATTRIBUTE_USAGE) || (mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+                dest.writeInt(mAttr.getUsage());
+            } else {
+                // capture preset rule
+                dest.writeInt(mAttr.getCapturePreset());
+            }
+        }
     }
 
-    private ArrayList<AttributeMatchCriterion> mCriteria;
+    private final int mTargetMixType;
+    int getTargetMixType() { return mTargetMixType; }
+    private final ArrayList<AttributeMatchCriterion> mCriteria;
     ArrayList<AttributeMatchCriterion> getCriteria() { return mCriteria; }
 
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTargetMixType, mCriteria);
+    }
+
+    private static boolean isValidSystemApiRule(int rule) {
+        switch(rule) {
+            case RULE_MATCH_ATTRIBUTE_USAGE:
+            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isValidIntRule(int rule) {
+        switch(rule) {
+            case RULE_MATCH_ATTRIBUTE_USAGE:
+            case RULE_EXCLUDE_ATTRIBUTE_USAGE:
+            case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+            case RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    private static boolean isPlayerRule(int rule) {
+        return ((rule == RULE_MATCH_ATTRIBUTE_USAGE)
+                || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE));
+    }
+
     /**
      * Builder class for {@link AudioMixingRule} objects
-     *
      */
+    @SystemApi
     public static class Builder {
         private ArrayList<AttributeMatchCriterion> mCriteria;
+        private int mTargetMixType = AudioMix.MIX_TYPE_INVALID;
 
         /**
          * Constructs a new Builder with no rules.
          */
+        @SystemApi
         public Builder() {
             mCriteria = new ArrayList<AttributeMatchCriterion>();
         }
@@ -81,18 +154,80 @@
          * Add a rule for the selection of which streams are mixed together.
          * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
          *     rule hasn't been set yet.
-         * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
-         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE}.
+         * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
+        @SystemApi
         public Builder addRule(AudioAttributes attrToMatch, int rule)
                 throws IllegalArgumentException {
+            if (!isValidSystemApiRule(rule)) {
+                throw new IllegalArgumentException("Illegal rule value " + rule);
+            }
+            return addRuleInt(attrToMatch, rule);
+        }
+
+        /**
+         * Add a rule by exclusion for the selection of which streams are mixed together.
+         * <br>For instance the following code
+         * <br><pre>
+         * AudioAttributes mediaAttr = new AudioAttributes.Builder()
+         *         .setUsage(AudioAttributes.USAGE_MEDIA)
+         *         .build();
+         * AudioMixingRule noMediaRule = new AudioMixingRule.Builder()
+         *         .excludeRule(mediaAttr, AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)
+         *         .build();
+         * </pre>
+         * <br>will create a rule which maps to any usage value, except USAGE_MEDIA.
+         * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
+         *     rule hasn't been set yet.
+         * @param rule {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE} or
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET}.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        @SystemApi
+        public Builder excludeRule(AudioAttributes attrToMatch, int rule)
+                throws IllegalArgumentException {
+            if (!isValidSystemApiRule(rule)) {
+                throw new IllegalArgumentException("Illegal rule value " + rule);
+            }
+            return addRuleInt(attrToMatch, rule | RULE_EXCLUSION_MASK);
+        }
+
+        /**
+         * Add or exclude a rule for the selection of which streams are mixed together.
+         * @param attrToMatch a non-null AudioAttributes instance for which a contradictory
+         *     rule hasn't been set yet.
+         * @param rule one of {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_USAGE},
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_USAGE},
+         *     {@link AudioMixingRule#RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET} or
+         *     {@link AudioMixingRule#RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET}.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        Builder addRuleInt(AudioAttributes attrToMatch, int rule)
+                throws IllegalArgumentException {
             if (attrToMatch == null) {
                 throw new IllegalArgumentException("Illegal null AudioAttributes argument");
             }
-            if ((rule != RULE_MATCH_ATTRIBUTE_USAGE) && (rule != RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+            if (!isValidIntRule(rule)) {
                 throw new IllegalArgumentException("Illegal rule value " + rule);
+            } else {
+                // as rules are added to the Builder, we verify they are consistent with the type
+                // of mix being built. When adding the first rule, the mix type is MIX_TYPE_INVALID.
+                if (mTargetMixType == AudioMix.MIX_TYPE_INVALID) {
+                    if (isPlayerRule(rule)) {
+                        mTargetMixType = AudioMix.MIX_TYPE_PLAYERS;
+                    } else {
+                        mTargetMixType = AudioMix.MIX_TYPE_RECORDERS;
+                    }
+                } else if (((mTargetMixType == AudioMix.MIX_TYPE_PLAYERS) && !isPlayerRule(rule))
+                        || ((mTargetMixType == AudioMix.MIX_TYPE_RECORDERS) && isPlayerRule(rule)))
+                {
+                    throw new IllegalArgumentException("Incompatible rule for mix");
+                }
             }
             synchronized (mCriteria) {
                 Iterator<AttributeMatchCriterion> crIterator = mCriteria.iterator();
@@ -111,6 +246,19 @@
                                         + attrToMatch);
                             }
                         }
+                    } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
+                            || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
+                        // "capture preset"-base rule
+                        if (criterion.mAttr.getCapturePreset() == attrToMatch.getCapturePreset()) {
+                            if (criterion.mRule == rule) {
+                             // rule already exists, we're done
+                                return this;
+                            } else {
+                                // criterion already exists with a another rule, it is incompatible
+                                throw new IllegalArgumentException("Contradictory rule exists for "
+                                        + attrToMatch);
+                            }
+                        }
                     }
                 }
                 // rule didn't exist, add it
@@ -119,13 +267,32 @@
             return this;
         }
 
+        Builder addRuleFromParcel(Parcel in) throws IllegalArgumentException {
+            int rule = in.readInt();
+            AudioAttributes attr;
+            if ((rule == RULE_MATCH_ATTRIBUTE_USAGE) || (rule == RULE_EXCLUDE_ATTRIBUTE_USAGE)) {
+                int usage = in.readInt();
+                attr = new AudioAttributes.Builder()
+                        .setUsage(usage).build();
+            } else if ((rule == RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
+                    || (rule == RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET)) {
+                int preset = in.readInt();
+                attr = new AudioAttributes.Builder()
+                        .setInternalCapturePreset(preset).build();
+            } else {
+                in.readInt(); // assume there was in int value to read as for now they come in pair
+                throw new IllegalArgumentException("Illegal rule value " + rule + " in parcel");
+            }
+            return addRuleInt(attr, rule);
+        }
+
         /**
          * Combines all of the matching and exclusion rules that have been set and return a new
          * {@link AudioMixingRule} object.
          * @return a new {@link AudioMixingRule} object
          */
         public AudioMixingRule build() {
-            return new AudioMixingRule(mCriteria);
+            return new AudioMixingRule(mTargetMixType, mCriteria);
         }
     }
 }
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index e9dc3af..44d2430 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -17,17 +17,21 @@
 package android.media.audiopolicy;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioRecord;
-import android.media.AudioSystem;
 import android.media.AudioTrack;
 import android.media.MediaRecorder;
 import android.os.Binder;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
 import android.util.Log;
 import android.util.Slog;
 
@@ -39,21 +43,20 @@
  * @hide
  * AudioPolicy provides access to the management of audio routing and audio focus.
  */
+@SystemApi
 public class AudioPolicy {
 
     private static final String TAG = "AudioPolicy";
 
     /**
-     * The status of an audio policy that cannot be used because it is invalid.
-     */
-    public static final int POLICY_STATUS_INVALID = 0;
-    /**
      * The status of an audio policy that is valid but cannot be used because it is not registered.
      */
+    @SystemApi
     public static final int POLICY_STATUS_UNREGISTERED = 1;
     /**
      * The status of an audio policy that is valid, successfully registered and thus active.
      */
+    @SystemApi
     public static final int POLICY_STATUS_REGISTERED = 2;
 
     private int mStatus;
@@ -72,22 +75,29 @@
     /**
      * The parameter is guaranteed non-null through the Builder
      */
-    private AudioPolicy(AudioPolicyConfig config, Context context) {
+    private AudioPolicy(AudioPolicyConfig config, Context context, Looper looper) {
         mConfig = config;
-        if (mConfig.mMixes.isEmpty()) {
-            mStatus = POLICY_STATUS_INVALID;
-        } else {
-            mStatus = POLICY_STATUS_UNREGISTERED;
-        }
+        mStatus = POLICY_STATUS_UNREGISTERED;
         mContext = context;
+        if (looper == null) {
+            looper = Looper.getMainLooper();
+        }
+        if (looper != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else {
+            mEventHandler = null;
+            Log.e(TAG, "No event handler due to looper without a thread");
+        }
     }
 
     /**
      * Builder class for {@link AudioPolicy} objects
      */
+    @SystemApi
     public static class Builder {
         private ArrayList<AudioMix> mMixes;
         private Context mContext;
+        private Looper mLooper;
 
         /**
          * Constructs a new Builder with no audio mixes.
@@ -104,7 +114,7 @@
          * @return the same Builder instance.
          * @throws IllegalArgumentException
          */
-        public Builder addMix(AudioMix mix) throws IllegalArgumentException {
+        public Builder addMix(@NonNull AudioMix mix) throws IllegalArgumentException {
             if (mix == null) {
                 throw new IllegalArgumentException("Illegal null AudioMix argument");
             }
@@ -112,18 +122,41 @@
             return this;
         }
 
+        /**
+         * Sets the {@link Looper} on which to run the event loop.
+         * @param looper a non-null specific Looper.
+         * @return the same Builder instance.
+         * @throws IllegalArgumentException
+         */
+        public Builder setLooper(@NonNull Looper looper) throws IllegalArgumentException {
+            if (looper == null) {
+                throw new IllegalArgumentException("Illegal null Looper argument");
+            }
+            mLooper = looper;
+            return this;
+        }
+
         public AudioPolicy build() {
-            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext);
+            return new AudioPolicy(new AudioPolicyConfig(mMixes), mContext, mLooper);
         }
     }
 
-    /** @hide */
     public void setRegistration(String regId) {
         mRegistrationId = regId;
         mConfig.setRegistration(regId);
+        if (regId != null) {
+            mStatus = POLICY_STATUS_REGISTERED;
+        } else {
+            mStatus = POLICY_STATUS_UNREGISTERED;
+        }
+        sendMsg(mEventHandler, MSG_POLICY_STATUS_CHANGE);
     }
 
     private boolean policyReadyToUse() {
+        if (mStatus != POLICY_STATUS_REGISTERED) {
+            Log.e(TAG, "Cannot use unregistered AudioPolicy");
+            return false;
+        }
         if (mContext == null) {
             Log.e(TAG, "Cannot use AudioPolicy without context");
             return false;
@@ -155,11 +188,17 @@
         {
             throw new IllegalArgumentException("Invalid AudioMix: not defined for loop back");
         }
-        // TODO also check mix is defined for playback or recording, and matches forTrack argument
+        if (forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_RECORDERS)) {
+            throw new IllegalArgumentException(
+                    "Invalid AudioMix: not defined for being a recording source");
+        }
+        if (!forTrack && (mix.getMixType() != AudioMix.MIX_TYPE_PLAYERS)) {
+            throw new IllegalArgumentException(
+                    "Invalid AudioMix: not defined for capturing playback");
+        }
     }
 
     /**
-     * @hide
      * Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
      * Audio buffers recorded through the created instance will contain the mix of the audio
      * streams that fed the given mixer.
@@ -170,6 +209,7 @@
      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
      * @throws IllegalArgumentException
      */
+    @SystemApi
     public AudioRecord createAudioRecordSink(AudioMix mix) throws IllegalArgumentException {
         if (!policyReadyToUse()) {
             Log.e(TAG, "Cannot create AudioRecord sink for AudioMix");
@@ -186,7 +226,7 @@
         AudioRecord ar = new AudioRecord(
                 new AudioAttributes.Builder()
                         .setInternalCapturePreset(MediaRecorder.AudioSource.REMOTE_SUBMIX)
-                        .addTag(mix.getRegistration())
+                        .addTag(addressForTag(mix))
                         .build(),
                 mixFormat,
                 AudioRecord.getMinBufferSize(mix.getFormat().getSampleRate(),
@@ -198,17 +238,17 @@
     }
 
     /**
-     * @hide
      * Create an {@link AudioTrack} instance that is associated with the given {@link AudioMix}.
      * Audio buffers played through the created instance will be sent to the given mix
      * to be recorded through the recording APIs.
      * @param mix a non-null {@link AudioMix} instance whose routing flags was defined with
      *     {@link AudioMix#ROUTE_FLAG_LOOP_BACK}, previously added to this policy.
-     * @returna new {@link AudioTrack} instance whose data format is the one defined in the
+     * @return a new {@link AudioTrack} instance whose data format is the one defined in the
      *     {@link AudioMix}, or null if this policy was not successfully registered
      *     with {@link AudioManager#registerAudioPolicy(AudioPolicy)}.
      * @throws IllegalArgumentException
      */
+    @SystemApi
     public AudioTrack createAudioTrackSource(AudioMix mix) throws IllegalArgumentException {
         if (!policyReadyToUse()) {
             Log.e(TAG, "Cannot create AudioTrack source for AudioMix");
@@ -219,7 +259,7 @@
         AudioTrack at = new AudioTrack(
                 new AudioAttributes.Builder()
                         .setUsage(AudioAttributes.USAGE_VIRTUAL_SOURCE)
-                        .addTag(mix.getRegistration())
+                        .addTag(addressForTag(mix))
                         .build(),
                 mix.getFormat(),
                 AudioTrack.getMinBufferSize(mix.getFormat().getSampleRate(),
@@ -230,20 +270,63 @@
         return at;
     }
 
+    @SystemApi
     public int getStatus() {
         return mStatus;
     }
 
+    @SystemApi
     public static abstract class AudioPolicyStatusListener {
-        void onStatusChange() {}
-        void onMixStateUpdate(AudioMix mix) {}
+        public void onStatusChange() {}
+        public void onMixStateUpdate(AudioMix mix) {}
     }
 
-    void setStatusListener(AudioPolicyStatusListener l) {
+    @SystemApi
+    synchronized public void setAudioPolicyStatusListener(AudioPolicyStatusListener l) {
         mStatusListener = l;
     }
 
-    /** @hide */
+    synchronized private void onPolicyStatusChange() {
+        if (mStatusListener == null) {
+            return;
+        }
+        mStatusListener.onStatusChange();
+    }
+
+    //==================================================
+    // Event handling
+    private final EventHandler mEventHandler;
+    private final static int MSG_POLICY_STATUS_CHANGE = 0;
+
+    private class EventHandler extends Handler {
+        public EventHandler(AudioPolicy ap, Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case MSG_POLICY_STATUS_CHANGE:
+                    onPolicyStatusChange();
+                    break;
+                default:
+                    Log.e(TAG, "Unknown event " + msg.what);
+            }
+        }
+    }
+
+    //==========================================================
+    // Utils
+    private static String addressForTag(AudioMix mix) {
+        return "addr=" + mix.getRegistration();
+    }
+
+    private static void sendMsg(Handler handler, int msg) {
+        if (handler != null) {
+            handler.sendEmptyMessage(msg);
+        }
+    }
+
     public String toLogFriendlyString() {
         String textDump = new String("android.media.audiopolicy.AudioPolicy:\n");
         textDump += "config=" + mConfig.toLogFriendlyString();
@@ -252,7 +335,6 @@
 
     /** @hide */
     @IntDef({
-        POLICY_STATUS_INVALID,
         POLICY_STATUS_REGISTERED,
         POLICY_STATUS_UNREGISTERED
     })
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index a9a4175..e2a20da 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -27,6 +27,7 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Objects;
 
 /**
  * @hide
@@ -38,7 +39,7 @@
 
     protected ArrayList<AudioMix> mMixes;
 
-    protected String mRegistrationId = null;
+    private String mRegistrationId = null;
 
     protected AudioPolicyConfig(AudioPolicyConfig conf) {
         mMixes = conf.mMixes;
@@ -62,6 +63,11 @@
     }
 
     @Override
+    public int hashCode() {
+        return Objects.hash(mMixes);
+    }
+
+    @Override
     public int describeContents() {
         return 0;
     }
@@ -80,8 +86,7 @@
             final ArrayList<AttributeMatchCriterion> criteria = mix.getRule().getCriteria();
             dest.writeInt(criteria.size());
             for (AttributeMatchCriterion criterion : criteria) {
-                dest.writeInt(criterion.mRule);
-                dest.writeInt(criterion.mAttr.getUsage());
+                criterion.writeToParcel(dest);
             }
         }
     }
@@ -106,17 +111,7 @@
             AudioMixingRule.Builder ruleBuilder = new AudioMixingRule.Builder();
             for (int j = 0 ; j < nbRules ; j++) {
                 // read the matching rules
-                int matchRule = in.readInt();
-                if ((matchRule == AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_USAGE)
-                    || (matchRule == AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE)) {
-                    int usage = in.readInt();
-                    final AudioAttributes attr = new AudioAttributes.Builder()
-                            .setUsage(usage).build();
-                    ruleBuilder.addRule(attr, matchRule);
-                } else {
-                    Log.w(TAG, "Encountered unsupported rule, skipping");
-                    in.readInt();
-                }
+                ruleBuilder.addRuleFromParcel(in);
             }
             mixBuilder.setMixingRule(ruleBuilder.build());
             mMixes.add(mixBuilder.build());
@@ -140,7 +135,7 @@
 
     public String toLogFriendlyString () {
         String textDump = new String("android.media.audiopolicy.AudioPolicyConfig:\n");
-        textDump += mMixes.size() + " AudioMix:\n";
+        textDump += mMixes.size() + " AudioMix: "+ mRegistrationId + "\n";
         for(AudioMix mix : mMixes) {
             // write mix route flags
             textDump += "* route flags=0x" + Integer.toHexString(mix.getRouteFlags()) + "\n";
@@ -161,6 +156,14 @@
                         textDump += "  match usage ";
                         textDump += criterion.mAttr.usageToString();
                         break;
+                    case AudioMixingRule.RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET:
+                        textDump += "  exclude capture preset ";
+                        textDump += criterion.mAttr.getCapturePreset();
+                        break;
+                    case AudioMixingRule.RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
+                        textDump += "  match capture preset ";
+                        textDump += criterion.mAttr.getCapturePreset();
+                        break;
                     default:
                         textDump += "invalid rule!";
                 }
@@ -170,12 +173,32 @@
         return textDump;
     }
 
-    public void setRegistration(String regId) {
-        mRegistrationId = regId;
+    protected void setRegistration(String regId) {
+        final boolean currentRegNull = (mRegistrationId == null) || mRegistrationId.isEmpty();
+        final boolean newRegNull = (regId == null) || regId.isEmpty();
+        if (!currentRegNull && !newRegNull && !mRegistrationId.equals(regId)) {
+            Log.e(TAG, "Invalid registration transition from " + mRegistrationId + " to " + regId);
+            return;
+        }
+        mRegistrationId = regId == null ? "" : regId;
         int mixIndex = 0;
         for (AudioMix mix : mMixes) {
-            mix.setRegistration(mRegistrationId + "mix:" + mixIndex++);
+            if (!mRegistrationId.isEmpty()) {
+                mix.setRegistration(mRegistrationId + "mix" + mixTypeId(mix.getMixType()) + ":"
+                        + mixIndex++);
+            } else {
+                mix.setRegistration("");
+            }
         }
     }
 
+    private static String mixTypeId(int type) {
+        if (type == AudioMix.MIX_TYPE_PLAYERS) return "p";
+        else if (type == AudioMix.MIX_TYPE_RECORDERS) return "r";
+        else return "i";
+    }
+
+    protected String getRegistration() {
+        return mRegistrationId;
+    }
 }