/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define LOG_TAG "APM_AudioPolicyMix" //#define LOG_NDEBUG 0 #include #include #include #include #include #include "AudioPolicyMix.h" #include "TypeConverter.h" #include "HwModule.h" #include "PolicyAudioPort.h" #include "IOProfile.h" #include #include namespace audiopolicy_flags = android::media::audiopolicy; namespace android { namespace { bool matchAddressToTags(const audio_attributes_t& attr, const String8& addr) { std::optional tagAddress = extractAddressFromAudioAttributes(attr); return tagAddress.has_value() && tagAddress->compare(addr.c_str()) == 0; } // Returns true if the criterion matches. // The exclude criteria are handled in the same way as positive // ones - only condition is matched (the function will return // same result both for RULE_MATCH_X and RULE_EXCLUDE_X). bool isCriterionMatched(const AudioMixMatchCriterion& criterion, const audio_attributes_t& attr, const uid_t uid, const audio_session_t session) { uint32_t ruleWithoutExclusion = criterion.mRule & ~RULE_EXCLUSION_MASK; switch(ruleWithoutExclusion) { case RULE_MATCH_ATTRIBUTE_USAGE: return criterion.mValue.mUsage == attr.usage; case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: return criterion.mValue.mSource == attr.source; case RULE_MATCH_UID: return criterion.mValue.mUid == uid; case RULE_MATCH_USERID: { userid_t userId = multiuser_get_user_id(uid); return criterion.mValue.mUserId == userId; } case RULE_MATCH_AUDIO_SESSION_ID: return criterion.mValue.mAudioSessionId == session; } ALOGE("Encountered invalid mix rule 0x%x", criterion.mRule); return false; } // Returns true if vector of criteria is matched: // - If any of the exclude criteria is matched the criteria doesn't match. // - Otherwise, for each 'dimension' of positive rule present // (usage, capture preset, uid, userid...) at least one rule must match // for the criteria to match. bool areMixCriteriaMatched(const std::vector& criteria, const audio_attributes_t& attr, const uid_t uid, const audio_session_t session) { // If any of the exclusion criteria are matched the mix doesn't match. auto isMatchingExcludeCriterion = [&](const AudioMixMatchCriterion& c) { return c.isExcludeCriterion() && isCriterionMatched(c, attr, uid, session); }; if (std::any_of(criteria.begin(), criteria.end(), isMatchingExcludeCriterion)) { return false; } uint32_t presentPositiveRules = 0; // Bitmask of all present positive criteria. uint32_t matchedPositiveRules = 0; // Bitmask of all matched positive criteria. for (const auto& criterion : criteria) { if (criterion.isExcludeCriterion()) { continue; } presentPositiveRules |= criterion.mRule; if (isCriterionMatched(criterion, attr, uid, session)) { matchedPositiveRules |= criterion.mRule; } } return presentPositiveRules == matchedPositiveRules; } // Consistency checks: for each "dimension" of rules (usage, uid...), we can // only have MATCH rules, or EXCLUDE rules in each dimension, not a combination. bool areMixCriteriaConsistent(const std::vector& criteria) { std::set positiveCriteria; for (const AudioMixMatchCriterion& c : criteria) { if (c.isExcludeCriterion()) { continue; } positiveCriteria.insert(c.mRule); } auto isConflictingCriterion = [&positiveCriteria](const AudioMixMatchCriterion& c) { uint32_t ruleWithoutExclusion = c.mRule & ~RULE_EXCLUSION_MASK; return c.isExcludeCriterion() && (positiveCriteria.find(ruleWithoutExclusion) != positiveCriteria.end()); }; return std::none_of(criteria.begin(), criteria.end(), isConflictingCriterion); } template void EraseCriteriaIf(std::vector& v, const Predicate& predicate) { v.erase(std::remove_if(v.begin(), v.end(), predicate), v.end()); } } // namespace void AudioPolicyMix::dump(String8 *dst, int spaces, int index) const { dst->appendFormat("%*sAudio Policy Mix %d (%p):\n", spaces, "", index + 1, this); std::string mixTypeLiteral; if (!MixTypeConverter::toString(mMixType, mixTypeLiteral)) { ALOGE("%s: failed to convert mix type %d", __FUNCTION__, mMixType); return; } dst->appendFormat("%*s- mix type: %s\n", spaces, "", mixTypeLiteral.c_str()); std::string routeFlagLiteral; RouteFlagTypeConverter::maskToString(mRouteFlags, routeFlagLiteral); dst->appendFormat("%*s- Route Flags: %s\n", spaces, "", routeFlagLiteral.c_str()); dst->appendFormat("%*s- device type: %s\n", spaces, "", toString(mDeviceType).c_str()); dst->appendFormat("%*s- device address: %s\n", spaces, "", mDeviceAddress.c_str()); dst->appendFormat("%*s- output: %d\n", spaces, "", mOutput == nullptr ? 0 : mOutput->mIoHandle); int indexCriterion = 0; for (const auto &criterion : mCriteria) { dst->appendFormat("%*s- Criterion %d: ", spaces + 2, "", indexCriterion++); std::string ruleType, ruleValue; bool unknownRule = !RuleTypeConverter::toString(criterion.mRule, ruleType); switch (criterion.mRule & ~RULE_EXCLUSION_MASK) { // no need to match RULE_EXCLUDE_... case RULE_MATCH_ATTRIBUTE_USAGE: UsageTypeConverter::toString(criterion.mValue.mUsage, ruleValue); break; case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET: SourceTypeConverter::toString(criterion.mValue.mSource, ruleValue); break; case RULE_MATCH_UID: ruleValue = std::to_string(criterion.mValue.mUid); break; case RULE_MATCH_USERID: ruleValue = std::to_string(criterion.mValue.mUserId); break; case RULE_MATCH_AUDIO_SESSION_ID: ruleValue = std::to_string(criterion.mValue.mAudioSessionId); break; default: unknownRule = true; } if (!unknownRule) { dst->appendFormat("%s %s\n", ruleType.c_str(), ruleValue.c_str()); } else { dst->appendFormat("Unknown rule type value 0x%x\n", criterion.mRule); } } } status_t AudioPolicyMixCollection::registerMix(const AudioMix& mix, const sp& desc) { for (size_t i = 0; i < size(); i++) { const sp& registeredMix = itemAt(i); if (mix.mDeviceType == registeredMix->mDeviceType && mix.mDeviceAddress.compare(registeredMix->mDeviceAddress) == 0 && is_mix_loopback(mix.mRouteFlags)) { ALOGE("registerMix(): mix already registered for dev=0x%x addr=%s", mix.mDeviceType, mix.mDeviceAddress.c_str()); return BAD_VALUE; } if (audiopolicy_flags::audio_mix_ownership()) { if (mix.mToken == registeredMix->mToken) { ALOGE("registerMix(): same mix already registered - skipping"); return BAD_VALUE; } } } if (!areMixCriteriaConsistent(mix.mCriteria)) { ALOGE("registerMix(): Mix contains inconsistent criteria " "(MATCH & EXCLUDE criteria of the same type)"); return BAD_VALUE; } sp policyMix = sp::make(mix); add(policyMix); ALOGD("registerMix(): adding mix for dev=0x%x addr=%s", policyMix->mDeviceType, policyMix->mDeviceAddress.c_str()); if (desc != nullptr) { desc->mPolicyMix = policyMix; policyMix->setOutput(desc); } return NO_ERROR; } status_t AudioPolicyMixCollection::unregisterMix(const AudioMix& mix) { for (size_t i = 0; i < size(); i++) { const sp& registeredMix = itemAt(i); if (audiopolicy_flags::audio_mix_ownership()) { if (mix.mToken == registeredMix->mToken) { ALOGD("unregisterMix(): removing mix for dev=0x%x addr=%s", mix.mDeviceType, mix.mDeviceAddress.c_str()); removeAt(i); return NO_ERROR; } } else { if (mix.mDeviceType == registeredMix->mDeviceType && mix.mDeviceAddress.compare(registeredMix->mDeviceAddress) == 0) { ALOGD("unregisterMix(): removing mix for dev=0x%x addr=%s", mix.mDeviceType, mix.mDeviceAddress.c_str()); removeAt(i); return NO_ERROR; } } } ALOGE("unregisterMix(): mix not registered for dev=0x%x addr=%s", mix.mDeviceType, mix.mDeviceAddress.c_str()); return BAD_VALUE; } status_t AudioPolicyMixCollection::updateMix( const AudioMix& mix, const std::vector& updatedCriteria) { if (!areMixCriteriaConsistent(mix.mCriteria)) { ALOGE("updateMix(): updated criteria are not consistent " "(MATCH & EXCLUDE criteria of the same type)"); return BAD_VALUE; } for (size_t i = 0; i < size(); i++) { const sp& registeredMix = itemAt(i); if (mix.mDeviceType == registeredMix->mDeviceType && mix.mDeviceAddress.compare(registeredMix->mDeviceAddress) == 0 && mix.mRouteFlags == registeredMix->mRouteFlags) { registeredMix->mCriteria = updatedCriteria; ALOGV("updateMix(): updated mix for dev=0x%x addr=%s", mix.mDeviceType, mix.mDeviceAddress.c_str()); return NO_ERROR; } } ALOGE("updateMix(): mix not registered for dev=0x%x addr=%s", mix.mDeviceType, mix.mDeviceAddress.c_str()); return BAD_VALUE; } status_t AudioPolicyMixCollection::getAudioPolicyMix(audio_devices_t deviceType, const String8& address, sp &policyMix) const { ALOGV("getAudioPolicyMix() for dev=0x%x addr=%s", deviceType, address.c_str()); for (ssize_t i = 0; i < size(); i++) { // Workaround: when an in audio policy is registered, it opens an output // that tries to find the audio policy, thus the device must be ignored. if (itemAt(i)->mDeviceAddress.compare(address) == 0) { policyMix = itemAt(i); ALOGV("getAudioPolicyMix: found mix %zu match (devType=0x%x addr=%s)", i, deviceType, address.c_str()); return NO_ERROR; } } ALOGE("getAudioPolicyMix(): mix not registered for dev=0x%x addr=%s", deviceType, address.c_str()); return BAD_VALUE; } void AudioPolicyMixCollection::closeOutput(sp &desc, const SwAudioOutputCollection& allOutputs) { for (size_t i = 0; i < size(); i++) { sp policyMix = itemAt(i); if (policyMix->getOutput() != desc) { continue; } policyMix->clearOutput(); if (policyMix->mRouteFlags != MIX_ROUTE_FLAG_RENDER) { continue; } auto device = desc->supportedDevices().getDevice( policyMix->mDeviceType, policyMix->mDeviceAddress, AUDIO_FORMAT_DEFAULT); if (device == nullptr) { // This must not happen ALOGE("%s, the rerouted device is not found", __func__); continue; } // Restore the policy mix mix output to the first opened output supporting a route to // the mix device. This is because the current mix output can be changed to a direct output. for (size_t j = 0; j < allOutputs.size(); ++j) { if (allOutputs[i] != desc && !allOutputs[i]->isDuplicated() && allOutputs[i]->supportedDevices().contains(device)) { policyMix->setOutput(allOutputs[i]); break; } } } } status_t AudioPolicyMixCollection::getOutputForAttr( const audio_attributes_t& attributes, const audio_config_base_t& config, const uid_t uid, const audio_session_t session, audio_output_flags_t flags, const DeviceVector &availableOutputDevices, const sp& requestedDevice, sp &primaryMix, std::vector> *secondaryMixes, bool& usePrimaryOutputFromPolicyMixes) { ALOGV("getOutputForAttr() querying %zu mixes:", size()); primaryMix.clear(); bool mixesDisallowsRequestedDevice = false; const bool isMmapRequested = (flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ); for (size_t i = 0; i < size(); i++) { sp policyMix = itemAt(i); const bool primaryOutputMix = !is_mix_loopback_render(policyMix->mRouteFlags); sp mixDevice = getOutputDeviceForMix(policyMix.get(), availableOutputDevices); if (mixDisallowsRequestedDevice(policyMix.get(), requestedDevice, mixDevice, uid)) { ALOGV("%s: Mix %zu: does not allows device", __func__, i); mixesDisallowsRequestedDevice = true; } if (!primaryOutputMix && isMmapRequested) { // AAudio does not support MMAP_NO_IRQ loopback render, and there is no way with // the current MmapStreamInterface::start to reject a specific client added to a shared // mmap stream. // As a result all MMAP_NOIRQ requests have to be rejected when an loopback render // policy is present. That ensures no shared mmap stream is used when an loopback // render policy is registered. ALOGD("%s: Rejecting MMAP_NOIRQ request due to LOOPBACK|RENDER mix present.", __func__); return INVALID_OPERATION; } if (primaryOutputMix && primaryMix != nullptr) { ALOGV("%s: Skiping %zu: Primary output already found", __func__, i); continue; // Primary output already found } if(!mixMatch(policyMix.get(), i, attributes, config, uid, session)) { ALOGV("%s: Mix %zu: does not match", __func__, i); continue; // skip the mix } if (isMmapRequested) { if (is_mix_loopback(policyMix->mRouteFlags)) { // AAudio MMAP_NOIRQ streams cannot be routed to loopback/loopback+render // using dynamic audio policy. ALOGD("%s: Rejecting MMAP_NOIRQ request matched to loopback dynamic " "audio policy mix.", __func__); return INVALID_OPERATION; } } if (mixDevice != nullptr && mixDevice->equals(requestedDevice)) { ALOGV("%s: Mix %zu: requested device mathches", __func__, i); mixesDisallowsRequestedDevice = false; } if (primaryOutputMix) { primaryMix = policyMix; ALOGV("%s: Mix %zu: set primary desc", __func__, i); } else { ALOGV("%s: Add a secondary desc %zu", __func__, i); if (secondaryMixes != nullptr) { secondaryMixes->push_back(policyMix); } } } // Explicit routing is higher priority than dynamic policy primary output, but policy may // explicitly deny it usePrimaryOutputFromPolicyMixes = (mixesDisallowsRequestedDevice || requestedDevice == nullptr) && primaryMix != nullptr; return NO_ERROR; } sp AudioPolicyMixCollection::getOutputDeviceForMix(const AudioMix* mix, const DeviceVector& availableOutputDevices) { ALOGV("%s: device (0x%x, addr=%s) forced by mix", __func__, mix->mDeviceType, mix->mDeviceAddress.c_str()); return availableOutputDevices.getDevice(mix->mDeviceType, mix->mDeviceAddress, AUDIO_FORMAT_DEFAULT); } bool AudioPolicyMixCollection::mixDisallowsRequestedDevice(const AudioMix* mix, const sp& requestedDevice, const sp& mixDevice, const uid_t uid) { if (requestedDevice == nullptr || mixDevice == nullptr) { return false; } return is_mix_disallows_preferred_device(mix->mRouteFlags) && requestedDevice->equals(mixDevice) && mix->hasUserIdRule(false /* match */, multiuser_get_user_id(uid)); } bool AudioPolicyMixCollection::mixMatch(const AudioMix* mix, size_t mixIndex, const audio_attributes_t& attributes, const audio_config_base_t& config, uid_t uid, audio_session_t session) { if (mix->mMixType == MIX_TYPE_PLAYERS) { // Loopback render mixes are created from a public API and thus restricted // to non sensible audio that have not opted out. if (is_mix_loopback_render(mix->mRouteFlags)) { if (!(attributes.usage == AUDIO_USAGE_UNKNOWN || attributes.usage == AUDIO_USAGE_MEDIA || attributes.usage == AUDIO_USAGE_GAME || attributes.usage == AUDIO_USAGE_VOICE_COMMUNICATION)) { return false; } auto hasFlag = [](auto flags, auto flag) { return (flags & flag) == flag; }; if (hasFlag(attributes.flags, AUDIO_FLAG_NO_SYSTEM_CAPTURE)) { return false; } if (attributes.usage == AUDIO_USAGE_VOICE_COMMUNICATION) { if (!mix->mVoiceCommunicationCaptureAllowed) { return false; } } else if (!mix->mAllowPrivilegedMediaPlaybackCapture && hasFlag(attributes.flags, AUDIO_FLAG_NO_MEDIA_PROJECTION)) { return false; } } // Permit match only if requested format and mix format are PCM and can be format // adapted by the mixer, or are the same (compressed) format. if (!is_mix_loopback(mix->mRouteFlags) && !((audio_is_linear_pcm(config.format) && audio_is_linear_pcm(mix->mFormat.format)) || (config.format == mix->mFormat.format)) && config.format != AUDIO_CONFIG_BASE_INITIALIZER.format) { return false; } // if there is an address match, prioritize that match if (matchAddressToTags(attributes, mix->mDeviceAddress) || areMixCriteriaMatched(mix->mCriteria, attributes, uid, session)) { ALOGV("\tgetOutputForAttr will use mix %zu", mixIndex); return true; } } else if (mix->mMixType == MIX_TYPE_RECORDERS) { if (attributes.usage == AUDIO_USAGE_VIRTUAL_SOURCE && matchAddressToTags(attributes, mix->mDeviceAddress)) { return true; } } return false; } sp AudioPolicyMixCollection::getDeviceAndMixForOutput( const sp &output, const DeviceVector &availableOutputDevices) { for (size_t i = 0; i < size(); i++) { if (itemAt(i)->getOutput() == output) { // This Desc is involved in a Mix, which has the highest prio return getOutputDeviceForMix(itemAt(i).get(), availableOutputDevices); } } return nullptr; } sp AudioPolicyMixCollection::getDeviceAndMixForInputSource( const audio_attributes_t& attributes, const DeviceVector &availDevices, uid_t uid, audio_session_t session, sp *policyMix) const { for (size_t i = 0; i < size(); i++) { AudioPolicyMix *mix = itemAt(i).get(); if (mix->mMixType != MIX_TYPE_RECORDERS) { continue; } if (areMixCriteriaMatched(mix->mCriteria, attributes, uid, session)) { // Assuming PolicyMix only for remote submix for input // so mix->mDeviceType can only be AUDIO_DEVICE_OUT_REMOTE_SUBMIX. auto mixDevice = availDevices.getDevice(AUDIO_DEVICE_IN_REMOTE_SUBMIX, mix->mDeviceAddress, AUDIO_FORMAT_DEFAULT); if (mixDevice != nullptr) { if (policyMix != nullptr) { *policyMix = mix; } return mixDevice; } } } return nullptr; } status_t AudioPolicyMixCollection::getInputMixForAttr( audio_attributes_t attr, sp *policyMix) { std::optional address = extractAddressFromAudioAttributes(attr); if (!address.has_value()) { return BAD_VALUE; } #ifdef LOG_NDEBUG ALOGV("getInputMixForAttr looking for address %s for source %d\n mixes available:", address->c_str(), attr.source); for (size_t i = 0; i < size(); i++) { const sp audioPolicyMix = itemAt(i); ALOGV("\tmix %zu address=%s", i, audioPolicyMix->mDeviceAddress.c_str()); } #endif size_t index; for (index = 0; index < size(); index++) { const sp& registeredMix = itemAt(index); if (address->compare(registeredMix->mDeviceAddress.c_str()) == 0) { ALOGD("getInputMixForAttr found addr=%s dev=0x%x", registeredMix->mDeviceAddress.c_str(), registeredMix->mDeviceType); break; } } if (index == size()) { ALOGW("getInputMixForAttr() no policy for address %s", address->c_str()); return BAD_VALUE; } const sp audioPolicyMix = itemAt(index); if (audioPolicyMix->mMixType != MIX_TYPE_PLAYERS) { ALOGW("getInputMixForAttr() bad policy mix type for address %s", address->c_str()); return BAD_VALUE; } if (policyMix != nullptr) { *policyMix = audioPolicyMix; } return NO_ERROR; } status_t AudioPolicyMixCollection::setUidDeviceAffinities(uid_t uid, const AudioDeviceTypeAddrVector& 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 = itemAt(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: // 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++) { AudioPolicyMix *mix = itemAt(i).get(); if (!mix->isDeviceAffinityCompatible()) { continue; } // check if this mix goes to a device in the list of devices bool deviceMatch = false; const AudioDeviceTypeAddr mixDevice(mix->mDeviceType, mix->mDeviceAddress.c_str()); for (size_t j = 0; j < devices.size(); j++) { if (mixDevice.equals(devices[j])) { deviceMatch = true; break; } } 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 if (!mix->hasUidRule(false /*match*/, uid)) { // no need to do it again if uid is already excluded mix->setExcludeUid(uid); } } } return NO_ERROR; } status_t AudioPolicyMixCollection::removeUidDeviceAffinities(uid_t uid) { // for each player mix: remove existing rules that match or exclude this uid for (size_t i = 0; i < size(); i++) { AudioPolicyMix *mix = itemAt(i).get(); if (!mix->isDeviceAffinityCompatible()) { continue; } // is this rule excluding the uid? (not considering uid match rules // as those are not used for uid-device affinity) EraseCriteriaIf(mix->mCriteria, [uid](const AudioMixMatchCriterion& c) { return c.mRule == RULE_EXCLUDE_UID && c.mValue.mUid == uid; }); } return NO_ERROR; } status_t AudioPolicyMixCollection::getDevicesForUid(uid_t uid, Vector& devices) const { // for each player mix: find rules that don't exclude this uid, and add the device to the list for (size_t i = 0; i < size(); i++) { bool ruleAllowsUid = true; const AudioPolicyMix *mix = itemAt(i).get(); if (mix->mMixType != MIX_TYPE_PLAYERS) { continue; } for (size_t j = 0; j < mix->mCriteria.size(); j++) { const uint32_t rule = mix->mCriteria[j].mRule; if (rule == RULE_EXCLUDE_UID && uid == mix->mCriteria[j].mValue.mUid) { ruleAllowsUid = false; break; } } if (ruleAllowsUid) { devices.add(AudioDeviceTypeAddr(mix->mDeviceType, mix->mDeviceAddress.c_str())); } } return NO_ERROR; } status_t AudioPolicyMixCollection::setUserIdDeviceAffinities(int userId, const AudioDeviceTypeAddrVector& devices) { // verify feasibility: for each player mix: if it already contains a // "match userId" rule for this userId, return an error // (adding a userId-device affinity would result in contradictory rules) for (size_t i = 0; i < size(); i++) { AudioPolicyMix* mix = itemAt(i).get(); if (!mix->isDeviceAffinityCompatible()) { continue; } if (mix->hasUserIdRule(true /*match*/, userId)) { return INVALID_OPERATION; } } // remove existing rules for this userId removeUserIdDeviceAffinities(userId); // for each player mix: // IF device is not a target for the mix, // AND it doesn't have a "match userId" rule // THEN add a rule to exclude the userId for (size_t i = 0; i < size(); i++) { AudioPolicyMix *mix = itemAt(i).get(); if (!mix->isDeviceAffinityCompatible()) { continue; } // check if this mix goes to a device in the list of devices bool deviceMatch = false; const AudioDeviceTypeAddr mixDevice(mix->mDeviceType, mix->mDeviceAddress.c_str()); for (size_t j = 0; j < devices.size(); j++) { if (mixDevice.equals(devices[j])) { deviceMatch = true; break; } } if (!deviceMatch && !mix->hasUserIdRule(true /*match*/)) { // this mix doesn't go to one of the listed devices for the given userId, // and it's not already restricting the mix on a userId, // modify its rules to exclude the userId if (!mix->hasUserIdRule(false /* match */, userId)) { // no need to do it again if userId is already excluded mix->setExcludeUserId(userId); mix->mRouteFlags = mix->mRouteFlags | MIX_ROUTE_FLAG_DISALLOWS_PREFERRED_DEVICE; } } } return NO_ERROR; } status_t AudioPolicyMixCollection::removeUserIdDeviceAffinities(int userId) { // for each player mix: remove existing rules that match or exclude this userId for (size_t i = 0; i < size(); i++) { AudioPolicyMix *mix = itemAt(i).get(); if (!mix->isDeviceAffinityCompatible()) { continue; } // is this rule excluding the userId? (not considering userId match rules // as those are not used for userId-device affinity) EraseCriteriaIf(mix->mCriteria, [userId](const AudioMixMatchCriterion& c) { return c.mRule == RULE_EXCLUDE_USERID && c.mValue.mUserId == userId; }); if (!mix->hasUserIdRule(false /* match */)) { mix->mRouteFlags = mix->mRouteFlags & ~MIX_ROUTE_FLAG_DISALLOWS_PREFERRED_DEVICE; } } return NO_ERROR; } status_t AudioPolicyMixCollection::getDevicesForUserId(int userId, AudioDeviceTypeAddrVector& devices) const { // for each player mix: // find rules that don't exclude this userId, and add the device to the list for (size_t i = 0; i < size(); i++) { bool ruleAllowsUserId = true; const AudioPolicyMix *mix = itemAt(i).get(); if (mix->mMixType != MIX_TYPE_PLAYERS) { continue; } for (size_t j = 0; j < mix->mCriteria.size(); j++) { const uint32_t rule = mix->mCriteria[j].mRule; if (rule == RULE_EXCLUDE_USERID && userId == mix->mCriteria[j].mValue.mUserId) { ruleAllowsUserId = false; break; } } if (ruleAllowsUserId) { devices.push_back(AudioDeviceTypeAddr(mix->mDeviceType, mix->mDeviceAddress.c_str())); } } return NO_ERROR; } void AudioPolicyMixCollection::dump(String8 *dst) const { dst->append("\n Audio Policy Mix:\n"); for (size_t i = 0; i < size(); i++) { itemAt(i)->dump(dst, 2, i); } } std::optional extractAddressFromAudioAttributes(const audio_attributes_t& attr) { static const std::regex addrTagRegex("addr=([^;]+)"); std::cmatch match; if (std::regex_search(attr.tags, match, addrTagRegex)) { return match[1].str(); } return std::nullopt; } }; //namespace android