Merge "Improve ownership model of AudioMixes in AudioPolicies" into main
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 070d07c..5223798 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -18,6 +18,8 @@
 //#define LOG_NDEBUG 0
 
 #define LOG_TAG "AudioSystem-JNI"
+#include <android/binder_ibinder_jni.h>
+#include <android/binder_libbinder.h>
 #include <android/media/AudioVibratorInfo.h>
 #include <android/media/INativeSpatializerCallback.h>
 #include <android/media/ISpatializer.h>
@@ -25,6 +27,7 @@
 #include <android_media_audiopolicy.h>
 #include <android_os_Parcel.h>
 #include <audiomanager/AudioManager.h>
+#include <binder/IBinder.h>
 #include <jni.h>
 #include <media/AidlConversion.h>
 #include <media/AudioContainers.h>
@@ -150,13 +153,14 @@
 static jclass gAudioMixClass;
 static jmethodID gAudioMixCstor;
 static struct {
-    jfieldID    mRule;
-    jfieldID    mFormat;
-    jfieldID    mRouteFlags;
-    jfieldID    mDeviceType;
-    jfieldID    mDeviceAddress;
-    jfieldID    mMixType;
-    jfieldID    mCallbackFlags;
+    jfieldID mRule;
+    jfieldID mFormat;
+    jfieldID mRouteFlags;
+    jfieldID mDeviceType;
+    jfieldID mDeviceAddress;
+    jfieldID mMixType;
+    jfieldID mCallbackFlags;
+    jfieldID mToken;
 } gAudioMixFields;
 
 static jclass gAudioFormatClass;
@@ -2300,11 +2304,15 @@
     if (status != AUDIO_JAVA_SUCCESS) {
         return status;
     }
+    std::unique_ptr<AIBinder, decltype(&AIBinder_decStrong)> aiBinder(AIBinder_fromPlatformBinder(
+                                                                              nAudioMix.mToken),
+                                                                      &AIBinder_decStrong);
+    jobject jBinderToken = AIBinder_toJavaBinder(env, aiBinder.get());
 
     jstring deviceAddress = env->NewStringUTF(nAudioMix.mDeviceAddress.c_str());
     *jAudioMix = env->NewObject(gAudioMixClass, gAudioMixCstor, jAudioMixingRule, jAudioFormat,
                                 nAudioMix.mRouteFlags, nAudioMix.mCbFlags, nAudioMix.mDeviceType,
-                                deviceAddress);
+                                deviceAddress, jBinderToken);
     return AUDIO_JAVA_SUCCESS;
 }
 
@@ -2333,6 +2341,12 @@
     nAudioMix->mVoiceCommunicationCaptureAllowed =
             env->GetBooleanField(jRule, gAudioMixingRuleFields.mVoiceCommunicationCaptureAllowed);
 
+    jobject jToken = env->GetObjectField(jAudioMix, gAudioMixFields.mToken);
+
+    std::unique_ptr<AIBinder, decltype(&AIBinder_decStrong)>
+            aiBinder(AIBinder_fromJavaBinder(env, jToken), &AIBinder_decStrong);
+    nAudioMix->mToken = AIBinder_toPlatformBinder(aiBinder.get());
+
     jint status = convertAudioMixingRuleToNative(env, jRule, &(nAudioMix->mCriteria));
 
     env->DeleteLocalRef(jRule);
@@ -3659,9 +3673,10 @@
     jclass audioMixClass = FindClassOrDie(env, "android/media/audiopolicy/AudioMix");
     gAudioMixClass = MakeGlobalRefOrDie(env, audioMixClass);
     if (audio_flags::audio_mix_test_api()) {
-        gAudioMixCstor = GetMethodIDOrDie(env, audioMixClass, "<init>",
-                                          "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/"
-                                          "media/AudioFormat;IIILjava/lang/String;)V");
+        gAudioMixCstor =
+                GetMethodIDOrDie(env, audioMixClass, "<init>",
+                                 "(Landroid/media/audiopolicy/AudioMixingRule;Landroid/"
+                                 "media/AudioFormat;IIILjava/lang/String;Landroid/os/IBinder;)V");
     }
     gAudioMixFields.mRule = GetFieldIDOrDie(env, audioMixClass, "mRule",
                                                 "Landroid/media/audiopolicy/AudioMixingRule;");
@@ -3673,6 +3688,7 @@
                                                       "Ljava/lang/String;");
     gAudioMixFields.mMixType = GetFieldIDOrDie(env, audioMixClass, "mMixType", "I");
     gAudioMixFields.mCallbackFlags = GetFieldIDOrDie(env, audioMixClass, "mCallbackFlags", "I");
+    gAudioMixFields.mToken = GetFieldIDOrDie(env, audioMixClass, "mToken", "Landroid/os/IBinder;");
 
     jclass audioFormatClass = FindClassOrDie(env, "android/media/AudioFormat");
     gAudioFormatClass = MakeGlobalRefOrDie(env, audioFormatClass);
diff --git a/media/java/android/media/audiopolicy/AudioMix.java b/media/java/android/media/audiopolicy/AudioMix.java
index 48b4766..60ab1a4 100644
--- a/media/java/android/media/audiopolicy/AudioMix.java
+++ b/media/java/android/media/audiopolicy/AudioMix.java
@@ -27,7 +27,9 @@
 import android.media.AudioDeviceInfo;
 import android.media.AudioFormat;
 import android.media.AudioSystem;
+import android.os.Binder;
 import android.os.Build;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -52,6 +54,8 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private int mMixType = MIX_TYPE_INVALID;
 
+    private final IBinder mToken;
+
     // written by AudioPolicy
     int mMixState = MIX_STATE_DISABLED;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
@@ -68,7 +72,7 @@
      */
     private AudioMix(@NonNull AudioMixingRule rule, @NonNull AudioFormat format,
             int routeFlags, int callbackFlags,
-            int deviceType, @Nullable String deviceAddress) {
+            int deviceType, @Nullable String deviceAddress, IBinder token) {
         mRule = Objects.requireNonNull(rule);
         mFormat = Objects.requireNonNull(format);
         mRouteFlags = routeFlags;
@@ -76,6 +80,7 @@
         mCallbackFlags = callbackFlags;
         mDeviceSystemType = deviceType;
         mDeviceAddress = (deviceAddress == null) ? new String("") : deviceAddress;
+        mToken = token;
     }
 
     // CALLBACK_FLAG_* values: keep in sync with AudioMix::kCbFlag* values defined
@@ -273,13 +278,14 @@
         return Objects.equals(this.mRouteFlags, that.mRouteFlags)
             && Objects.equals(this.mRule, that.mRule)
             && Objects.equals(this.mMixType, that.mMixType)
-            && Objects.equals(this.mFormat, that.mFormat);
+            && Objects.equals(this.mFormat, that.mFormat)
+            && Objects.equals(this.mToken, that.mToken);
     }
 
     /** @hide */
     @Override
     public int hashCode() {
-        return Objects.hash(mRouteFlags, mRule, mMixType, mFormat);
+        return Objects.hash(mRouteFlags, mRule, mMixType, mFormat, mToken);
     }
 
     @Override
@@ -298,6 +304,7 @@
         dest.writeString8(mDeviceAddress);
         mFormat.writeToParcel(dest, flags);
         mRule.writeToParcel(dest, flags);
+        dest.writeStrongBinder(mToken);
     }
 
     public static final @NonNull Parcelable.Creator<AudioMix> CREATOR = new Parcelable.Creator<>() {
@@ -317,6 +324,7 @@
             mixBuilder.setDevice(p.readInt(), p.readString8());
             mixBuilder.setFormat(AudioFormat.CREATOR.createFromParcel(p));
             mixBuilder.setMixingRule(AudioMixingRule.CREATOR.createFromParcel(p));
+            mixBuilder.setToken(p.readStrongBinder());
             return mixBuilder.build();
         }
 
@@ -339,6 +347,7 @@
         private AudioFormat mFormat = null;
         private int mRouteFlags = 0;
         private int mCallbackFlags = 0;
+        private IBinder mToken = null;
         // an AudioSystem.DEVICE_* value, not AudioDeviceInfo.TYPE_*
         private int mDeviceSystemType = AudioSystem.DEVICE_NONE;
         private String mDeviceAddress = null;
@@ -380,6 +389,15 @@
 
         /**
          * @hide
+         * Only used by AudioMix internally.
+         */
+        Builder setToken(IBinder token) {
+            mToken = token;
+            return this;
+        }
+
+        /**
+         * @hide
          * Only used by AudioPolicyConfig, not a public API.
          * @param callbackFlags which callbacks are called from native
          * @return the same Builder instance.
@@ -540,8 +558,13 @@
                     throw new IllegalArgumentException(error);
                 }
             }
+
+            if (mToken == null) {
+                mToken = new Binder();
+            }
+
             return new AudioMix(mRule, mFormat, mRouteFlags, mCallbackFlags, mDeviceSystemType,
-                    mDeviceAddress);
+                    mDeviceAddress, mToken);
         }
 
         private int getLoopbackDeviceSystemTypeForAudioMixingRule(AudioMixingRule rule) {
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index bbe461c..508c0a2b 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -337,7 +337,9 @@
 
     /**
      * Update the current configuration of the set of audio mixes by adding new ones, while
-     * keeping the policy registered.
+     * keeping the policy registered. If any of the provided audio mixes is invalid then none of
+     * the passed mixes will be registered.
+     *
      * This method can only be called on a registered policy.
      * @param mixes the list of {@link AudioMix} to add
      * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
@@ -375,12 +377,15 @@
     }
 
     /**
-     * Update the current configuration of the set of audio mixes by removing some, while
-     * keeping the policy registered.
-     * This method can only be called on a registered policy.
+     * Update the current configuration of the set of audio mixes for this audio policy by
+     * removing some, while keeping the policy registered. Will unregister all provided audio
+     * mixes, if possible.
+     *
+     * This method can only be called on a registered policy and only affects this current policy.
      * @param mixes the list of {@link AudioMix} to remove
      * @return {@link AudioManager#SUCCESS} if the change was successful, {@link AudioManager#ERROR}
-     *    otherwise.
+     *    otherwise. If only some of the provided audio mixes were detached but any one mix
+     *    failed to be detached, this method returns {@link AudioManager#ERROR}.
      */
     public int detachMixes(@NonNull List<AudioMix> mixes) {
         if (mixes == null) {
@@ -394,7 +399,6 @@
             for (AudioMix mix : mixes) {
                 if (mix == null) {
                     throw new IllegalArgumentException("Illegal null AudioMix in detachMixes");
-                    // TODO also check mix is currently contained in list of mixes
                 } else {
                     zeMixes.add(mix);
                 }
diff --git a/media/java/android/media/audiopolicy/AudioPolicyConfig.java b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
index d277c7d..5e7c7c4 100644
--- a/media/java/android/media/audiopolicy/AudioPolicyConfig.java
+++ b/media/java/android/media/audiopolicy/AudioPolicyConfig.java
@@ -228,7 +228,7 @@
         }
     }
 
-    private void setMixRegistration(@NonNull final AudioMix mix) {
+    protected void setMixRegistration(@NonNull final AudioMix mix) {
         if (!mRegistrationId.isEmpty()) {
             if ((mix.getRouteFlags() & AudioMix.ROUTE_FLAG_LOOP_BACK) ==
                     AudioMix.ROUTE_FLAG_LOOP_BACK) {
@@ -246,7 +246,9 @@
     @GuardedBy("mMixes")
     protected void add(@NonNull ArrayList<AudioMix> mixes) {
         for (AudioMix mix : mixes) {
-            setMixRegistration(mix);
+            if (mix.getRegistration() == null || mix.getRegistration().isEmpty()) {
+                setMixRegistration(mix);
+            }
             mMixes.add(mix);
         }
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cb6d26f..19dd7b7 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -12528,6 +12528,16 @@
             if (app == null) {
                 return AudioManager.ERROR;
             }
+            if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+                for (AudioMix mix : policyConfig.getMixes()) {
+                    if (!app.getMixes().contains(mix)) {
+                        Slog.e(TAG,
+                                "removeMixForPolicy attempted to unregister AudioMix(es) not "
+                                        + "belonging to the AudioPolicy");
+                        return AudioManager.ERROR;
+                    }
+                }
+            }
             return app.removeMixes(policyConfig.getMixes()) == AudioSystem.SUCCESS
                 ? AudioManager.SUCCESS : AudioManager.ERROR;
         }
@@ -13306,7 +13316,13 @@
             }
             final long identity = Binder.clearCallingIdentity();
             try {
-                mAudioSystem.registerPolicyMixes(mMixes, false);
+                if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+                    synchronized (mMixes) {
+                        removeMixes(new ArrayList(mMixes));
+                    }
+                } else {
+                    mAudioSystem.registerPolicyMixes(mMixes, false);
+                }
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -13350,6 +13366,17 @@
 
         int addMixes(@NonNull ArrayList<AudioMix> mixes) {
             synchronized (mMixes) {
+                if (android.media.audiopolicy.Flags.audioMixOwnership()) {
+                    for (AudioMix mix : mixes) {
+                        setMixRegistration(mix);
+                    }
+
+                    int result = mAudioSystem.registerPolicyMixes(mixes, true);
+                    if (result == AudioSystem.SUCCESS) {
+                        this.add(mixes);
+                    }
+                    return result;
+                }
                 this.add(mixes);
                 return mAudioSystem.registerPolicyMixes(mixes, true);
             }