Merge "APM: uid-device affinity: fix for multiple concurrent apps" into qt-dev am: b09f621af6
am: 5f5158fe34

Change-Id: Ic766a8ef35f7d4826819ca53a51def1f6c659882
diff --git a/media/libaudioclient/AudioPolicy.cpp b/media/libaudioclient/AudioPolicy.cpp
index 65e797f..3cdf095 100644
--- a/media/libaudioclient/AudioPolicy.cpp
+++ b/media/libaudioclient/AudioPolicy.cpp
@@ -159,4 +159,29 @@
     mCriteria.add(crit);
 }
 
+bool AudioMix::hasUidRule(bool match, uid_t uid) const {
+    const uint32_t rule = match ? RULE_MATCH_UID : RULE_EXCLUDE_UID;
+    for (size_t i = 0; i < mCriteria.size(); i++) {
+        if (mCriteria[i].mRule == rule
+                && mCriteria[i].mValue.mUid == uid) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool AudioMix::hasMatchUidRule() const {
+    for (size_t i = 0; i < mCriteria.size(); i++) {
+        if (mCriteria[i].mRule == RULE_MATCH_UID) {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool AudioMix::isDeviceAffinityCompatible() const {
+    return ((mMixType == MIX_TYPE_PLAYERS)
+            && (mRouteFlags == MIX_ROUTE_FLAG_RENDER));
+}
+
 } // namespace android
diff --git a/media/libaudioclient/include/media/AudioPolicy.h b/media/libaudioclient/include/media/AudioPolicy.h
index a40e019..ef39fd1 100644
--- a/media/libaudioclient/include/media/AudioPolicy.h
+++ b/media/libaudioclient/include/media/AudioPolicy.h
@@ -106,6 +106,12 @@
 
     void setExcludeUid(uid_t uid) const;
     void setMatchUid(uid_t uid) const;
+    /** returns true if this mix has a rule to match or exclude the given uid */
+    bool hasUidRule(bool match, uid_t uid) const;
+    /** returns true if this mix has a rule for uid match (any uid) */
+    bool hasMatchUidRule() const;
+    /** returns true if this mix can be used for uid-device affinity routing */
+    bool isDeviceAffinityCompatible() const;
 
     mutable Vector<AudioMixMatchCriterion> mCriteria;
     uint32_t        mMixType;
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioPolicyMix.h b/services/audiopolicy/common/managerdefinitions/include/AudioPolicyMix.h
index f2b51d9..12b5e7d 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioPolicyMix.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioPolicyMix.h
@@ -89,6 +89,16 @@
 
     status_t getInputMixForAttr(audio_attributes_t attr, sp<AudioPolicyMix> *policyMix);
 
+    /**
+     * Updates the mix rules in order to make streams associated with the given uid
+     * be routed to the given audio devices.
+     * @param uid the uid for which the device affinity is set
+     * @param devices the vector of devices that this uid may be routed to. A typical
+     *    use is to pass the devices associated with a given zone in a multi-zone setup.
+     * @return NO_ERROR if the update was successful, INVALID_OPERATION otherwise.
+     *    An example of failure is when there are already rules in place to restrict
+     *    a mix to the given uid (i.e. when a MATCH_UID rule was set for it).
+     */
     status_t setUidDeviceAffinities(uid_t uid, const Vector<AudioDeviceTypeAddr>& devices);
     status_t removeUidDeviceAffinities(uid_t uid);
     status_t getDevicesForUid(uid_t uid, Vector<AudioDeviceTypeAddr>& devices) const;
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
index 26bb354..98a7800 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
@@ -400,13 +400,29 @@
 
 status_t AudioPolicyMixCollection::setUidDeviceAffinities(uid_t uid,
         const Vector<AudioDeviceTypeAddr>& devices) {
+    // verify feasibility: for each player mix: if it already contains a
+    //    "match uid" rule for this uid, return an error
+    //    (adding a uid-device affinity would result in contradictory rules)
+    for (size_t i = 0; i < size(); i++) {
+        const AudioPolicyMix* mix = valueAt(i).get();
+        if (!mix->isDeviceAffinityCompatible()) {
+            continue;
+        }
+        if (mix->hasUidRule(true /*match*/, uid)) {
+            return INVALID_OPERATION;
+        }
+    }
+
     // remove existing rules for this uid
     removeUidDeviceAffinities(uid);
 
-    // for each player mix: add a rule to match or exclude the uid based on the device
+    // for each player mix:
+    //   IF    device is not a target for the mix,
+    //     AND it doesn't have a "match uid" rule
+    //   THEN add a rule to exclude the uid
     for (size_t i = 0; i < size(); i++) {
         const AudioPolicyMix *mix = valueAt(i).get();
-        if (mix->mMixType != MIX_TYPE_PLAYERS) {
+        if (!mix->isDeviceAffinityCompatible()) {
             continue;
         }
         // check if this mix goes to a device in the list of devices
@@ -418,12 +434,14 @@
                 break;
             }
         }
-        if (deviceMatch) {
-            mix->setMatchUid(uid);
-        } else {
+        if (!deviceMatch && !mix->hasMatchUidRule()) {
             // this mix doesn't go to one of the listed devices for the given uid,
+            // and it's not already restricting the mix on a uid,
             // modify its rules to exclude the uid
-            mix->setExcludeUid(uid);
+            if (!mix->hasUidRule(false /*match*/, uid)) {
+                // no need to do it again if uid is already excluded
+                mix->setExcludeUid(uid);
+            }
         }
     }
 
@@ -435,14 +453,15 @@
     for (size_t i = 0; i < size(); i++) {
         bool foundUidRule = false;
         const AudioPolicyMix *mix = valueAt(i).get();
-        if (mix->mMixType != MIX_TYPE_PLAYERS) {
+        if (!mix->isDeviceAffinityCompatible()) {
             continue;
         }
         std::vector<size_t> criteriaToRemove;
         for (size_t j = 0; j < mix->mCriteria.size(); j++) {
             const uint32_t rule = mix->mCriteria[j].mRule;
-            // is this rule affecting the uid?
-            if ((rule == RULE_EXCLUDE_UID || rule == RULE_MATCH_UID)
+            // is this rule excluding the uid? (not considering uid match rules
+            // as those are not used for uid-device affinity)
+            if (rule == RULE_EXCLUDE_UID
                     && uid == mix->mCriteria[j].mValue.mUid) {
                 foundUidRule = true;
                 criteriaToRemove.insert(criteriaToRemove.begin(), j);