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