Dynamic audio policies on UID

When looking for an output, take attributes and UID into account.
To find the matching mix: for each mix
- inspect which rules are used
- find a candidate mix for usage
- check UID compatibility
Rename AttributeMatchCriterion to AudioMixMatchCriterion to be consistent
 with the fact that now mixing rules are not always about audio attributes.

Bug 26798796

Change-Id: I1520b0df190a98f197ea8e0144b770e1e6d97888
diff --git a/services/audiopolicy/common/managerdefinitions/include/AudioPolicyMix.h b/services/audiopolicy/common/managerdefinitions/include/AudioPolicyMix.h
index d51f4e1..c952831 100644
--- a/services/audiopolicy/common/managerdefinitions/include/AudioPolicyMix.h
+++ b/services/audiopolicy/common/managerdefinitions/include/AudioPolicyMix.h
@@ -69,7 +69,8 @@
      * @return NO_ERROR if an output was found for the given attribute (in this case, the
      *                  descriptor output param is initialized), error code otherwise.
      */
-    status_t getOutputForAttr(audio_attributes_t attributes, sp<SwAudioOutputDescriptor> &desc);
+    status_t getOutputForAttr(audio_attributes_t attributes, uid_t uid,
+            sp<SwAudioOutputDescriptor> &desc);
 
     audio_devices_t getDeviceAndMixForInputSource(audio_source_t inputSource,
                                                   audio_devices_t availableDeviceTypes,
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
index 6f1998c..3735c05 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
@@ -98,30 +98,111 @@
     }
 }
 
-status_t AudioPolicyMixCollection::getOutputForAttr(audio_attributes_t attributes,
+status_t AudioPolicyMixCollection::getOutputForAttr(audio_attributes_t attributes, uid_t uid,
                                                     sp<SwAudioOutputDescriptor> &desc)
 {
+    desc = 0;
     for (size_t i = 0; i < size(); i++) {
         sp<AudioPolicyMix> policyMix = valueAt(i);
         AudioMix *mix = policyMix->getMix();
 
         if (mix->mMixType == MIX_TYPE_PLAYERS) {
+            // TODO if adding more player rules (currently only 2), make rule handling "generic"
+            //      as there is no difference in the treatment of usage- or uid-based rules
+            bool hasUsageMatchRules = false;
+            bool hasUsageExcludeRules = false;
+            bool usageMatchFound = false;
+            bool usageExclusionFound = false;
+
+            bool hasUidMatchRules = false;
+            bool hasUidExcludeRules = false;
+            bool uidMatchFound = false;
+            bool uidExclusionFound = false;
+
+            bool hasAddrMatch = false;
+
+            // iterate over all mix criteria to list what rules this mix contains
             for (size_t j = 0; j < mix->mCriteria.size(); j++) {
-                if ((RULE_MATCH_ATTRIBUTE_USAGE == mix->mCriteria[j].mRule &&
-                     mix->mCriteria[j].mAttr.mUsage == attributes.usage) ||
-                        (RULE_EXCLUDE_ATTRIBUTE_USAGE == mix->mCriteria[j].mRule &&
-                         mix->mCriteria[j].mAttr.mUsage != attributes.usage)) {
-                    desc = policyMix->getOutput();
-                    break;
-                }
+                ALOGV("getOutputForAttr: inspecting mix %zu of %zu", i, mix->mCriteria.size());
+
+                // if there is an address match, prioritize that match
                 if (strncmp(attributes.tags, "addr=", strlen("addr=")) == 0 &&
                         strncmp(attributes.tags + strlen("addr="),
                                 mix->mRegistrationId.string(),
                                 AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - strlen("addr=") - 1) == 0) {
-                    desc = policyMix->getOutput();
+                    hasAddrMatch = true;
                     break;
                 }
+
+                switch (mix->mCriteria[j].mRule) {
+                case RULE_MATCH_ATTRIBUTE_USAGE:
+                    ALOGV("\tmix has RULE_MATCH_ATTRIBUTE_USAGE for usage %d",
+                                                mix->mCriteria[j].mValue.mUsage);
+                    hasUsageMatchRules = true;
+                    if (mix->mCriteria[j].mValue.mUsage == attributes.usage) {
+                        // found one match against all allowed usages
+                        usageMatchFound = true;
+                    }
+                    break;
+                case RULE_EXCLUDE_ATTRIBUTE_USAGE:
+                    ALOGV("\tmix has RULE_EXCLUDE_ATTRIBUTE_USAGE for usage %d",
+                            mix->mCriteria[j].mValue.mUsage);
+                    hasUsageExcludeRules = true;
+                    if (mix->mCriteria[j].mValue.mUsage == attributes.usage) {
+                        // found this usage is to be excluded
+                        usageExclusionFound = true;
+                    }
+                    break;
+                case RULE_MATCH_UID:
+                    ALOGV("\tmix has RULE_MATCH_UID for uid %d", mix->mCriteria[j].mValue.mUid);
+                    hasUidMatchRules = true;
+                    if (mix->mCriteria[j].mValue.mUid == uid) {
+                        // found one UID match against all allowed UIDs
+                        uidMatchFound = true;
+                    }
+                    break;
+                case RULE_EXCLUDE_UID:
+                    ALOGV("\tmix has RULE_EXCLUDE_UID for uid %d", mix->mCriteria[j].mValue.mUid);
+                    hasUidExcludeRules = true;
+                    if (mix->mCriteria[j].mValue.mUid == uid) {
+                        // found this UID is to be excluded
+                        uidExclusionFound = true;
+                    }
+                    break;
+                default:
+                    break;
+                }
+
+                // consistency checks: for each "dimension" of rules (usage, uid...), we can
+                // only have MATCH rules, or EXCLUDE rules in each dimension, not a combination
+                if (hasUsageMatchRules && hasUsageExcludeRules) {
+                    ALOGE("getOutputForAttr: invalid combination of RULE_MATCH_ATTRIBUTE_USAGE"
+                            " and RULE_EXCLUDE_ATTRIBUTE_USAGE in mix %zu", i);
+                    return BAD_VALUE;
+                }
+                if (hasUidMatchRules && hasUidExcludeRules) {
+                    ALOGE("getOutputForAttr: invalid combination of RULE_MATCH_UID"
+                            " and RULE_EXCLUDE_UID in mix %zu", i);
+                    return BAD_VALUE;
+                }
+
+                if ((hasUsageExcludeRules && usageExclusionFound)
+                        || (hasUidExcludeRules && uidExclusionFound)) {
+                    break; // stop iterating on criteria because an exclusion was found (will fail)
+                }
+
+            }//iterate on mix criteria
+
+            // determine if exiting on success (or implicit failure as desc is 0)
+            if (hasAddrMatch ||
+                    !((hasUsageExcludeRules && usageExclusionFound) ||
+                      (hasUsageMatchRules && !usageMatchFound)  ||
+                      (hasUidExcludeRules && uidExclusionFound) ||
+                      (hasUidMatchRules && !uidMatchFound))) {
+                ALOGV("\tgetOutputForAttr will use mix %zu", i);
+                desc = policyMix->getOutput();
             }
+
         } else if (mix->mMixType == MIX_TYPE_RECORDERS) {
             if (attributes.usage == AUDIO_USAGE_VIRTUAL_SOURCE &&
                     strncmp(attributes.tags, "addr=", strlen("addr=")) == 0 &&
@@ -151,9 +232,9 @@
         }
         for (size_t j = 0; j < mix->mCriteria.size(); j++) {
             if ((RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET == mix->mCriteria[j].mRule &&
-                    mix->mCriteria[j].mAttr.mSource == inputSource) ||
+                    mix->mCriteria[j].mValue.mSource == inputSource) ||
                (RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET == mix->mCriteria[j].mRule &&
-                    mix->mCriteria[j].mAttr.mSource != inputSource)) {
+                    mix->mCriteria[j].mValue.mSource != inputSource)) {
                 if (availDevices & AUDIO_DEVICE_IN_REMOTE_SUBMIX) {
                     if (policyMix != NULL) {
                         *policyMix = mix;
@@ -174,6 +255,15 @@
     }
     String8 address(attr.tags + strlen("addr="));
 
+#ifdef LOG_NDEBUG
+    ALOGV("getInputMixForAttr looking for address %s\n  mixes available:", address.string());
+    for (size_t i = 0; i < size(); i++) {
+            sp<AudioPolicyMix> policyMix = valueAt(i);
+            AudioMix *mix = policyMix->getMix();
+            ALOGV("\tmix %zu address=%s", i, mix->mRegistrationId.string());
+    }
+#endif
+
     ssize_t index = indexOfKey(address);
     if (index < 0) {
         ALOGW("getInputMixForAttr() no policy for address %s", address.string());
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index ec70ed4..60ec27e 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -709,7 +709,7 @@
         stream_type_to_audio_attributes(*stream, &attributes);
     }
     sp<SwAudioOutputDescriptor> desc;
-    if (mPolicyMixes.getOutputForAttr(attributes, desc) == NO_ERROR) {
+    if (mPolicyMixes.getOutputForAttr(attributes, uid, desc) == NO_ERROR) {
         ALOG_ASSERT(desc != 0, "Invalid desc returned by getOutputForAttr");
         if (!audio_is_linear_pcm(format)) {
             return BAD_VALUE;