APM: uid-device affinity: fix for multiple concurrent apps
Audio mix rules that contain both a match and exclude for a uid
or usage are ignored for routing evaluation. The implementation
of the uid-device affinity caused uid matches and exclusions to
be added to any mixes, causing previously valid mixes to be
discarded in the routing logic.
The fix consists in implementing the uid-device affinity by only
applying uid exclusions on mixes that:
a/ are compatible with the concept (mixes of players that are
render only),
b/ do not route to the selected devices
c/ do not have a uid match rule (such mixes are already exclusive
to the uid to match)
d/ do not already have a uid exclusion for the uid
Fix removeUidDeviceAffinities() that skipped the wrong mixes.
Bug: 124319394
Bug: 131180578
Test: atest AudioHostTest#testUidDeviceAffinity
Change-Id: I813c4cfb15956e56599740948775c32562266c21
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);