1 /*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_TAG "APM_AudioPolicyMix"
18 //#define LOG_NDEBUG 0
19
20 #include "AudioPolicyMix.h"
21 #include "TypeConverter.h"
22 #include "HwModule.h"
23 #include "PolicyAudioPort.h"
24 #include "IOProfile.h"
25 #include <AudioOutputDescriptor.h>
26
27 namespace android {
28
dump(String8 * dst,int spaces,int index) const29 void AudioPolicyMix::dump(String8 *dst, int spaces, int index) const
30 {
31 dst->appendFormat("%*sAudio Policy Mix %d:\n", spaces, "", index + 1);
32 std::string mixTypeLiteral;
33 if (!MixTypeConverter::toString(mMixType, mixTypeLiteral)) {
34 ALOGE("%s: failed to convert mix type %d", __FUNCTION__, mMixType);
35 return;
36 }
37 dst->appendFormat("%*s- mix type: %s\n", spaces, "", mixTypeLiteral.c_str());
38
39 std::string routeFlagLiteral;
40 RouteFlagTypeConverter::maskToString(mRouteFlags, routeFlagLiteral);
41 dst->appendFormat("%*s- Route Flags: %s\n", spaces, "", routeFlagLiteral.c_str());
42
43 dst->appendFormat("%*s- device type: %s\n", spaces, "", toString(mDeviceType).c_str());
44
45 dst->appendFormat("%*s- device address: %s\n", spaces, "", mDeviceAddress.string());
46
47 int indexCriterion = 0;
48 for (const auto &criterion : mCriteria) {
49 dst->appendFormat("%*s- Criterion %d: ", spaces + 2, "", indexCriterion++);
50
51 std::string ruleType, ruleValue;
52 bool unknownRule = !RuleTypeConverter::toString(criterion.mRule, ruleType);
53 switch (criterion.mRule & ~RULE_EXCLUSION_MASK) { // no need to match RULE_EXCLUDE_...
54 case RULE_MATCH_ATTRIBUTE_USAGE:
55 UsageTypeConverter::toString(criterion.mValue.mUsage, ruleValue);
56 break;
57 case RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET:
58 SourceTypeConverter::toString(criterion.mValue.mSource, ruleValue);
59 break;
60 case RULE_MATCH_UID:
61 ruleValue = std::to_string(criterion.mValue.mUid);
62 break;
63 default:
64 unknownRule = true;
65 }
66
67 if (!unknownRule) {
68 dst->appendFormat("%s %s\n", ruleType.c_str(), ruleValue.c_str());
69 } else {
70 dst->appendFormat("Unknown rule type value 0x%x\n", criterion.mRule);
71 }
72 }
73 }
74
registerMix(AudioMix mix,sp<SwAudioOutputDescriptor> desc)75 status_t AudioPolicyMixCollection::registerMix(AudioMix mix, sp<SwAudioOutputDescriptor> desc)
76 {
77 for (size_t i = 0; i < size(); i++) {
78 const sp<AudioPolicyMix>& registeredMix = itemAt(i);
79 if (mix.mDeviceType == registeredMix->mDeviceType
80 && mix.mDeviceAddress.compare(registeredMix->mDeviceAddress) == 0) {
81 ALOGE("registerMix(): mix already registered for dev=0x%x addr=%s",
82 mix.mDeviceType, mix.mDeviceAddress.string());
83 return BAD_VALUE;
84 }
85 }
86 sp<AudioPolicyMix> policyMix = new AudioPolicyMix(mix);
87 add(policyMix);
88 ALOGD("registerMix(): adding mix for dev=0x%x addr=%s",
89 policyMix->mDeviceType, policyMix->mDeviceAddress.string());
90
91 if (desc != 0) {
92 desc->mPolicyMix = policyMix;
93 policyMix->setOutput(desc);
94 }
95 return NO_ERROR;
96 }
97
unregisterMix(const AudioMix & mix)98 status_t AudioPolicyMixCollection::unregisterMix(const AudioMix& mix)
99 {
100 for (size_t i = 0; i < size(); i++) {
101 const sp<AudioPolicyMix>& registeredMix = itemAt(i);
102 if (mix.mDeviceType == registeredMix->mDeviceType
103 && mix.mDeviceAddress.compare(registeredMix->mDeviceAddress) == 0) {
104 ALOGD("unregisterMix(): removing mix for dev=0x%x addr=%s",
105 mix.mDeviceType, mix.mDeviceAddress.string());
106 removeAt(i);
107 return NO_ERROR;
108 }
109 }
110
111 ALOGE("unregisterMix(): mix not registered for dev=0x%x addr=%s",
112 mix.mDeviceType, mix.mDeviceAddress.string());
113 return BAD_VALUE;
114 }
115
getAudioPolicyMix(audio_devices_t deviceType,const String8 & address,sp<AudioPolicyMix> & policyMix) const116 status_t AudioPolicyMixCollection::getAudioPolicyMix(audio_devices_t deviceType,
117 const String8& address, sp<AudioPolicyMix> &policyMix) const
118 {
119
120 ALOGV("getAudioPolicyMix() for dev=0x%x addr=%s", deviceType, address.string());
121 for (ssize_t i = 0; i < size(); i++) {
122 // Workaround: when an in audio policy is registered, it opens an output
123 // that tries to find the audio policy, thus the device must be ignored.
124 if (itemAt(i)->mDeviceAddress.compare(address) == 0) {
125 policyMix = itemAt(i);
126 ALOGV("getAudioPolicyMix: found mix %zu match (devType=0x%x addr=%s)",
127 i, deviceType, address.string());
128 return NO_ERROR;
129 }
130 }
131
132 ALOGE("getAudioPolicyMix(): mix not registered for dev=0x%x addr=%s",
133 deviceType, address.string());
134 return BAD_VALUE;
135 }
136
closeOutput(sp<SwAudioOutputDescriptor> & desc)137 void AudioPolicyMixCollection::closeOutput(sp<SwAudioOutputDescriptor> &desc)
138 {
139 for (size_t i = 0; i < size(); i++) {
140 sp<AudioPolicyMix> policyMix = itemAt(i);
141 if (policyMix->getOutput() == desc) {
142 policyMix->clearOutput();
143 }
144 }
145 }
146
getOutputForAttr(const audio_attributes_t & attributes,uid_t uid,audio_output_flags_t flags,sp<SwAudioOutputDescriptor> & primaryDesc,std::vector<sp<SwAudioOutputDescriptor>> * secondaryDescs)147 status_t AudioPolicyMixCollection::getOutputForAttr(
148 const audio_attributes_t& attributes, uid_t uid,
149 audio_output_flags_t flags,
150 sp<SwAudioOutputDescriptor> &primaryDesc,
151 std::vector<sp<SwAudioOutputDescriptor>> *secondaryDescs)
152 {
153 ALOGV("getOutputForAttr() querying %zu mixes:", size());
154 primaryDesc = 0;
155 for (size_t i = 0; i < size(); i++) {
156 sp<AudioPolicyMix> policyMix = itemAt(i);
157 const bool primaryOutputMix = !is_mix_loopback_render(policyMix->mRouteFlags);
158 if (!primaryOutputMix && (flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ)) {
159 // AAudio does not support MMAP_NO_IRQ loopback render, and there is no way with
160 // the current MmapStreamInterface::start to reject a specific client added to a shared
161 // mmap stream.
162 // As a result all MMAP_NOIRQ requests have to be rejected when an loopback render
163 // policy is present. That ensures no shared mmap stream is used when an loopback
164 // render policy is registered.
165 ALOGD("%s: Rejecting MMAP_NOIRQ request due to LOOPBACK|RENDER mix present.", __func__);
166 return INVALID_OPERATION;
167 }
168
169 sp<SwAudioOutputDescriptor> policyDesc = policyMix->getOutput();
170 if (!policyDesc) {
171 ALOGV("%s: Skiping %zu: Mix has no output", __func__, i);
172 continue;
173 }
174
175 if (primaryOutputMix && primaryDesc != 0) {
176 ALOGV("%s: Skiping %zu: Primary output already found", __func__, i);
177 continue; // Primary output already found
178 }
179
180 switch (mixMatch(policyMix.get(), i, attributes, uid)) {
181 case MixMatchStatus::INVALID_MIX:
182 // The mix has contradictory rules, ignore it
183 // TODO: reject invalid mix at registration
184 continue;
185 case MixMatchStatus::NO_MATCH:
186 ALOGV("%s: Mix %zu: does not match", __func__, i);
187 continue; // skip the mix
188 case MixMatchStatus::MATCH:;
189 }
190
191 policyDesc->mPolicyMix = policyMix;
192 if (primaryOutputMix) {
193 primaryDesc = policyDesc;
194 ALOGV("%s: Mix %zu: set primary desc", __func__, i);
195 } else {
196 if (policyDesc->mIoHandle == AUDIO_IO_HANDLE_NONE) {
197 ALOGV("%s: Mix %zu ignored as secondaryOutput because not opened yet", __func__, i);
198 } else {
199 ALOGV("%s: Add a secondary desc %zu", __func__, i);
200 secondaryDescs->push_back(policyDesc);
201 }
202 }
203 }
204 return NO_ERROR;
205 }
206
mixMatch(const AudioMix * mix,size_t mixIndex,const audio_attributes_t & attributes,uid_t uid)207 AudioPolicyMixCollection::MixMatchStatus AudioPolicyMixCollection::mixMatch(
208 const AudioMix* mix, size_t mixIndex, const audio_attributes_t& attributes, uid_t uid) {
209
210 if (mix->mMixType == MIX_TYPE_PLAYERS) {
211 // Loopback render mixes are created from a public API and thus restricted
212 // to non sensible audio that have not opted out.
213 if (is_mix_loopback_render(mix->mRouteFlags)) {
214 auto hasFlag = [](auto flags, auto flag) { return (flags & flag) == flag; };
215 if (hasFlag(attributes.flags, AUDIO_FLAG_NO_SYSTEM_CAPTURE)) {
216 return MixMatchStatus::NO_MATCH;
217 }
218 if (!mix->mAllowPrivilegedPlaybackCapture &&
219 hasFlag(attributes.flags, AUDIO_FLAG_NO_MEDIA_PROJECTION)) {
220 return MixMatchStatus::NO_MATCH;
221 }
222 if (!(attributes.usage == AUDIO_USAGE_UNKNOWN ||
223 attributes.usage == AUDIO_USAGE_MEDIA ||
224 attributes.usage == AUDIO_USAGE_GAME)) {
225 return MixMatchStatus::NO_MATCH;
226 }
227 }
228 // TODO if adding more player rules (currently only 2), make rule handling "generic"
229 // as there is no difference in the treatment of usage- or uid-based rules
230 bool hasUsageMatchRules = false;
231 bool hasUsageExcludeRules = false;
232 bool usageMatchFound = false;
233 bool usageExclusionFound = false;
234
235 bool hasUidMatchRules = false;
236 bool hasUidExcludeRules = false;
237 bool uidMatchFound = false;
238 bool uidExclusionFound = false;
239
240 bool hasAddrMatch = false;
241
242 // iterate over all mix criteria to list what rules this mix contains
243 for (size_t j = 0; j < mix->mCriteria.size(); j++) {
244 ALOGV(" getOutputForAttr: mix %zu: inspecting mix criteria %zu of %zu",
245 mixIndex, j, mix->mCriteria.size());
246
247 // if there is an address match, prioritize that match
248 if (strncmp(attributes.tags, "addr=", strlen("addr=")) == 0 &&
249 strncmp(attributes.tags + strlen("addr="),
250 mix->mDeviceAddress.string(),
251 AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - strlen("addr=") - 1) == 0) {
252 hasAddrMatch = true;
253 break;
254 }
255
256 switch (mix->mCriteria[j].mRule) {
257 case RULE_MATCH_ATTRIBUTE_USAGE:
258 ALOGV("\tmix has RULE_MATCH_ATTRIBUTE_USAGE for usage %d",
259 mix->mCriteria[j].mValue.mUsage);
260 hasUsageMatchRules = true;
261 if (mix->mCriteria[j].mValue.mUsage == attributes.usage) {
262 // found one match against all allowed usages
263 usageMatchFound = true;
264 }
265 break;
266 case RULE_EXCLUDE_ATTRIBUTE_USAGE:
267 ALOGV("\tmix has RULE_EXCLUDE_ATTRIBUTE_USAGE for usage %d",
268 mix->mCriteria[j].mValue.mUsage);
269 hasUsageExcludeRules = true;
270 if (mix->mCriteria[j].mValue.mUsage == attributes.usage) {
271 // found this usage is to be excluded
272 usageExclusionFound = true;
273 }
274 break;
275 case RULE_MATCH_UID:
276 ALOGV("\tmix has RULE_MATCH_UID for uid %d", mix->mCriteria[j].mValue.mUid);
277 hasUidMatchRules = true;
278 if (mix->mCriteria[j].mValue.mUid == uid) {
279 // found one UID match against all allowed UIDs
280 uidMatchFound = true;
281 }
282 break;
283 case RULE_EXCLUDE_UID:
284 ALOGV("\tmix has RULE_EXCLUDE_UID for uid %d", mix->mCriteria[j].mValue.mUid);
285 hasUidExcludeRules = true;
286 if (mix->mCriteria[j].mValue.mUid == uid) {
287 // found this UID is to be excluded
288 uidExclusionFound = true;
289 }
290 break;
291 default:
292 break;
293 }
294
295 // consistency checks: for each "dimension" of rules (usage, uid...), we can
296 // only have MATCH rules, or EXCLUDE rules in each dimension, not a combination
297 if (hasUsageMatchRules && hasUsageExcludeRules) {
298 ALOGE("getOutputForAttr: invalid combination of RULE_MATCH_ATTRIBUTE_USAGE"
299 " and RULE_EXCLUDE_ATTRIBUTE_USAGE in mix %zu", mixIndex);
300 return MixMatchStatus::INVALID_MIX;
301 }
302 if (hasUidMatchRules && hasUidExcludeRules) {
303 ALOGE("getOutputForAttr: invalid combination of RULE_MATCH_UID"
304 " and RULE_EXCLUDE_UID in mix %zu", mixIndex);
305 return MixMatchStatus::INVALID_MIX;
306 }
307
308 if ((hasUsageExcludeRules && usageExclusionFound)
309 || (hasUidExcludeRules && uidExclusionFound)) {
310 break; // stop iterating on criteria because an exclusion was found (will fail)
311 }
312
313 }//iterate on mix criteria
314
315 // determine if exiting on success (or implicit failure as desc is 0)
316 if (hasAddrMatch ||
317 !((hasUsageExcludeRules && usageExclusionFound) ||
318 (hasUsageMatchRules && !usageMatchFound) ||
319 (hasUidExcludeRules && uidExclusionFound) ||
320 (hasUidMatchRules && !uidMatchFound))) {
321 ALOGV("\tgetOutputForAttr will use mix %zu", mixIndex);
322 return MixMatchStatus::MATCH;
323 }
324
325 } else if (mix->mMixType == MIX_TYPE_RECORDERS) {
326 if (attributes.usage == AUDIO_USAGE_VIRTUAL_SOURCE &&
327 strncmp(attributes.tags, "addr=", strlen("addr=")) == 0 &&
328 strncmp(attributes.tags + strlen("addr="),
329 mix->mDeviceAddress.string(),
330 AUDIO_ATTRIBUTES_TAGS_MAX_SIZE - strlen("addr=") - 1) == 0) {
331 return MixMatchStatus::MATCH;
332 }
333 }
334 return MixMatchStatus::NO_MATCH;
335 }
336
getDeviceAndMixForOutput(const sp<SwAudioOutputDescriptor> & output,const DeviceVector & availableOutputDevices)337 sp<DeviceDescriptor> AudioPolicyMixCollection::getDeviceAndMixForOutput(
338 const sp<SwAudioOutputDescriptor> &output,
339 const DeviceVector &availableOutputDevices)
340 {
341 for (size_t i = 0; i < size(); i++) {
342 if (itemAt(i)->getOutput() == output) {
343 // This Desc is involved in a Mix, which has the highest prio
344 audio_devices_t deviceType = itemAt(i)->mDeviceType;
345 String8 address = itemAt(i)->mDeviceAddress;
346 ALOGV("%s: device (0x%x, addr=%s) forced by mix",
347 __FUNCTION__, deviceType, address.c_str());
348 return availableOutputDevices.getDevice(deviceType, address, AUDIO_FORMAT_DEFAULT);
349 }
350 }
351 return nullptr;
352 }
353
getDeviceAndMixForInputSource(audio_source_t inputSource,const DeviceVector & availDevices,sp<AudioPolicyMix> * policyMix) const354 sp<DeviceDescriptor> AudioPolicyMixCollection::getDeviceAndMixForInputSource(
355 audio_source_t inputSource,
356 const DeviceVector &availDevices,
357 sp<AudioPolicyMix> *policyMix) const
358 {
359 for (size_t i = 0; i < size(); i++) {
360 AudioPolicyMix *mix = itemAt(i).get();
361 if (mix->mMixType != MIX_TYPE_RECORDERS) {
362 continue;
363 }
364 for (size_t j = 0; j < mix->mCriteria.size(); j++) {
365 if ((RULE_MATCH_ATTRIBUTE_CAPTURE_PRESET == mix->mCriteria[j].mRule &&
366 mix->mCriteria[j].mValue.mSource == inputSource) ||
367 (RULE_EXCLUDE_ATTRIBUTE_CAPTURE_PRESET == mix->mCriteria[j].mRule &&
368 mix->mCriteria[j].mValue.mSource != inputSource)) {
369 // assuming PolicyMix only for remote submix for input
370 // so mix->mDeviceType can only be AUDIO_DEVICE_OUT_REMOTE_SUBMIX
371 audio_devices_t device = AUDIO_DEVICE_IN_REMOTE_SUBMIX;
372 auto mixDevice =
373 availDevices.getDevice(device, mix->mDeviceAddress, AUDIO_FORMAT_DEFAULT);
374 if (mixDevice != nullptr) {
375 if (policyMix != nullptr) {
376 *policyMix = mix;
377 }
378 return mixDevice;
379 }
380 break;
381 }
382 }
383 }
384 return nullptr;
385 }
386
getInputMixForAttr(audio_attributes_t attr,sp<AudioPolicyMix> * policyMix)387 status_t AudioPolicyMixCollection::getInputMixForAttr(
388 audio_attributes_t attr, sp<AudioPolicyMix> *policyMix)
389 {
390 if (strncmp(attr.tags, "addr=", strlen("addr=")) != 0) {
391 return BAD_VALUE;
392 }
393 String8 address(attr.tags + strlen("addr="));
394
395 #ifdef LOG_NDEBUG
396 ALOGV("getInputMixForAttr looking for address %s for source %d\n mixes available:",
397 address.string(), attr.source);
398 for (size_t i = 0; i < size(); i++) {
399 const sp<AudioPolicyMix> audioPolicyMix = itemAt(i);
400 ALOGV("\tmix %zu address=%s", i, audioPolicyMix->mDeviceAddress.string());
401 }
402 #endif
403
404 size_t index;
405 for (index = 0; index < size(); index++) {
406 const sp<AudioPolicyMix>& registeredMix = itemAt(index);
407 if (registeredMix->mDeviceAddress.compare(address) == 0) {
408 ALOGD("getInputMixForAttr found addr=%s dev=0x%x",
409 registeredMix->mDeviceAddress.string(), registeredMix->mDeviceType);
410 break;
411 }
412 }
413 if (index == size()) {
414 ALOGW("getInputMixForAttr() no policy for address %s", address.string());
415 return BAD_VALUE;
416 }
417 const sp<AudioPolicyMix> audioPolicyMix = itemAt(index);
418
419 if (audioPolicyMix->mMixType != MIX_TYPE_PLAYERS) {
420 ALOGW("getInputMixForAttr() bad policy mix type for address %s", address.string());
421 return BAD_VALUE;
422 }
423 if (policyMix != nullptr) {
424 *policyMix = audioPolicyMix;
425 }
426 return NO_ERROR;
427 }
428
setUidDeviceAffinities(uid_t uid,const Vector<AudioDeviceTypeAddr> & devices)429 status_t AudioPolicyMixCollection::setUidDeviceAffinities(uid_t uid,
430 const Vector<AudioDeviceTypeAddr>& devices) {
431 // verify feasibility: for each player mix: if it already contains a
432 // "match uid" rule for this uid, return an error
433 // (adding a uid-device affinity would result in contradictory rules)
434 for (size_t i = 0; i < size(); i++) {
435 const AudioPolicyMix* mix = itemAt(i).get();
436 if (!mix->isDeviceAffinityCompatible()) {
437 continue;
438 }
439 if (mix->hasUidRule(true /*match*/, uid)) {
440 return INVALID_OPERATION;
441 }
442 }
443
444 // remove existing rules for this uid
445 removeUidDeviceAffinities(uid);
446
447 // for each player mix:
448 // IF device is not a target for the mix,
449 // AND it doesn't have a "match uid" rule
450 // THEN add a rule to exclude the uid
451 for (size_t i = 0; i < size(); i++) {
452 const AudioPolicyMix *mix = itemAt(i).get();
453 if (!mix->isDeviceAffinityCompatible()) {
454 continue;
455 }
456 // check if this mix goes to a device in the list of devices
457 bool deviceMatch = false;
458 const AudioDeviceTypeAddr mixDevice(mix->mDeviceType, mix->mDeviceAddress.string());
459 for (size_t j = 0; j < devices.size(); j++) {
460 if (mixDevice.equals(devices[j])) {
461 deviceMatch = true;
462 break;
463 }
464 }
465 if (!deviceMatch && !mix->hasMatchUidRule()) {
466 // this mix doesn't go to one of the listed devices for the given uid,
467 // and it's not already restricting the mix on a uid,
468 // modify its rules to exclude the uid
469 if (!mix->hasUidRule(false /*match*/, uid)) {
470 // no need to do it again if uid is already excluded
471 mix->setExcludeUid(uid);
472 }
473 }
474 }
475
476 return NO_ERROR;
477 }
478
removeUidDeviceAffinities(uid_t uid)479 status_t AudioPolicyMixCollection::removeUidDeviceAffinities(uid_t uid) {
480 // for each player mix: remove existing rules that match or exclude this uid
481 for (size_t i = 0; i < size(); i++) {
482 bool foundUidRule = false;
483 const AudioPolicyMix *mix = itemAt(i).get();
484 if (!mix->isDeviceAffinityCompatible()) {
485 continue;
486 }
487 std::vector<size_t> criteriaToRemove;
488 for (size_t j = 0; j < mix->mCriteria.size(); j++) {
489 const uint32_t rule = mix->mCriteria[j].mRule;
490 // is this rule excluding the uid? (not considering uid match rules
491 // as those are not used for uid-device affinity)
492 if (rule == RULE_EXCLUDE_UID
493 && uid == mix->mCriteria[j].mValue.mUid) {
494 foundUidRule = true;
495 criteriaToRemove.insert(criteriaToRemove.begin(), j);
496 }
497 }
498 if (foundUidRule) {
499 for (size_t j = 0; j < criteriaToRemove.size(); j++) {
500 mix->mCriteria.removeAt(criteriaToRemove[j]);
501 }
502 }
503 }
504 return NO_ERROR;
505 }
506
getDevicesForUid(uid_t uid,Vector<AudioDeviceTypeAddr> & devices) const507 status_t AudioPolicyMixCollection::getDevicesForUid(uid_t uid,
508 Vector<AudioDeviceTypeAddr>& devices) const {
509 // for each player mix: find rules that don't exclude this uid, and add the device to the list
510 for (size_t i = 0; i < size(); i++) {
511 bool ruleAllowsUid = true;
512 const AudioPolicyMix *mix = itemAt(i).get();
513 if (mix->mMixType != MIX_TYPE_PLAYERS) {
514 continue;
515 }
516 for (size_t j = 0; j < mix->mCriteria.size(); j++) {
517 const uint32_t rule = mix->mCriteria[j].mRule;
518 if (rule == RULE_EXCLUDE_UID
519 && uid == mix->mCriteria[j].mValue.mUid) {
520 ruleAllowsUid = false;
521 break;
522 }
523 }
524 if (ruleAllowsUid) {
525 devices.add(AudioDeviceTypeAddr(mix->mDeviceType, mix->mDeviceAddress.string()));
526 }
527 }
528 return NO_ERROR;
529 }
530
dump(String8 * dst) const531 void AudioPolicyMixCollection::dump(String8 *dst) const
532 {
533 dst->append("\nAudio Policy Mix:\n");
534 for (size_t i = 0; i < size(); i++) {
535 itemAt(i)->dump(dst, 2, i);
536 }
537 }
538
539 }; //namespace android
540