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/include/media/AudioPolicy.h b/include/media/AudioPolicy.h
index ff07b08..a171493 100644
--- a/include/media/AudioPolicy.h
+++ b/include/media/AudioPolicy.h
@@ -28,11 +28,13 @@
 
 // Keep in sync with AudioMix.java, AudioMixingRule.java, AudioPolicyConfig.java
 #define RULE_EXCLUSION_MASK 0x8000
-#define RULE_MATCH_ATTRIBUTE_USAGE 0x1
+#define RULE_MATCH_ATTRIBUTE_USAGE           0x1
 #define RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET (0x1 << 1)
-#define RULE_EXCLUDE_ATTRIBUTE_USAGE (RULE_EXCLUSION_MASK|RULE_MATCH_ATTRIBUTE_USAGE)
+#define RULE_MATCH_UID                      (0x1 << 2)
+#define RULE_EXCLUDE_ATTRIBUTE_USAGE  (RULE_EXCLUSION_MASK|RULE_MATCH_ATTRIBUTE_USAGE)
 #define RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET \
-    (RULE_EXCLUSION_MASK|RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
+                                      (RULE_EXCLUSION_MASK|RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET)
+#define RULE_EXCLUDE_UID              (RULE_EXCLUSION_MASK|RULE_MATCH_UID)
 
 #define MIX_TYPE_INVALID -1
 #define MIX_TYPE_PLAYERS 0
@@ -53,10 +55,10 @@
 #define MAX_MIXES_PER_POLICY 10
 #define MAX_CRITERIA_PER_MIX 20
 
-class AttributeMatchCriterion {
+class AudioMixMatchCriterion {
 public:
-    AttributeMatchCriterion() {}
-    AttributeMatchCriterion(audio_usage_t usage, audio_source_t source, uint32_t rule);
+    AudioMixMatchCriterion() {}
+    AudioMixMatchCriterion(audio_usage_t usage, audio_source_t source, uint32_t rule);
 
     status_t readFromParcel(Parcel *parcel);
     status_t writeToParcel(Parcel *parcel) const;
@@ -64,7 +66,8 @@
     union {
         audio_usage_t   mUsage;
         audio_source_t  mSource;
-    } mAttr;
+        uid_t           mUid;
+    } mValue;
     uint32_t        mRule;
 };
 
@@ -75,7 +78,7 @@
     static const uint32_t kCbFlagNotifyActivity = 0x1;
 
     AudioMix() {}
-    AudioMix(Vector<AttributeMatchCriterion> criteria, uint32_t mixType, audio_config_t format,
+    AudioMix(Vector<AudioMixMatchCriterion> criteria, uint32_t mixType, audio_config_t format,
              uint32_t routeFlags, String8 registrationId, uint32_t flags) :
         mCriteria(criteria), mMixType(mixType), mFormat(format),
         mRouteFlags(routeFlags), mRegistrationId(registrationId), mCbFlags(flags){}
@@ -83,7 +86,7 @@
     status_t readFromParcel(Parcel *parcel);
     status_t writeToParcel(Parcel *parcel) const;
 
-    Vector<AttributeMatchCriterion> mCriteria;
+    Vector<AudioMixMatchCriterion> mCriteria;
     uint32_t        mMixType;
     audio_config_t  mFormat;
     uint32_t        mRouteFlags;
diff --git a/media/libmedia/AudioPolicy.cpp b/media/libmedia/AudioPolicy.cpp
index 9d07011..ea22b6c 100644
--- a/media/libmedia/AudioPolicy.cpp
+++ b/media/libmedia/AudioPolicy.cpp
@@ -22,37 +22,37 @@
 namespace android {
 
 //
-//  AttributeMatchCriterion implementation
+//  AudioMixMatchCriterion implementation
 //
-AttributeMatchCriterion::AttributeMatchCriterion(audio_usage_t usage,
+AudioMixMatchCriterion::AudioMixMatchCriterion(audio_usage_t usage,
                                                  audio_source_t source,
                                                  uint32_t rule)
 : mRule(rule)
 {
     if (mRule == RULE_MATCH_ATTRIBUTE_USAGE ||
             mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE) {
-        mAttr.mUsage = usage;
+        mValue.mUsage = usage;
     } else {
-        mAttr.mSource = source;
+        mValue.mSource = source;
     }
 }
 
-status_t AttributeMatchCriterion::readFromParcel(Parcel *parcel)
+status_t AudioMixMatchCriterion::readFromParcel(Parcel *parcel)
 {
     mRule = parcel->readInt32();
     if (mRule == RULE_MATCH_ATTRIBUTE_USAGE ||
             mRule == RULE_EXCLUDE_ATTRIBUTE_USAGE) {
-        mAttr.mUsage = (audio_usage_t)parcel->readInt32();
+        mValue.mUsage = (audio_usage_t)parcel->readInt32();
     } else {
-        mAttr.mSource = (audio_source_t)parcel->readInt32();
+        mValue.mSource = (audio_source_t)parcel->readInt32();
     }
     return NO_ERROR;
 }
 
-status_t AttributeMatchCriterion::writeToParcel(Parcel *parcel) const
+status_t AudioMixMatchCriterion::writeToParcel(Parcel *parcel) const
 {
     parcel->writeInt32(mRule);
-    parcel->writeInt32(mAttr.mUsage);
+    parcel->writeInt32(mValue.mUsage);
     return NO_ERROR;
 }
 
@@ -74,7 +74,7 @@
         size = MAX_CRITERIA_PER_MIX;
     }
     for (size_t i = 0; i < size; i++) {
-        AttributeMatchCriterion criterion;
+        AudioMixMatchCriterion criterion;
         if (criterion.readFromParcel(parcel) == NO_ERROR) {
             mCriteria.add(criterion);
         }
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;