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 package com.android.car; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.car.Car; 21 import android.car.media.CarAudioPatchHandle; 22 import android.car.media.ICarAudio; 23 import android.car.media.ICarVolumeCallback; 24 import android.content.BroadcastReceiver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.IntentFilter; 28 import android.content.pm.PackageManager; 29 import android.hardware.automotive.audiocontrol.V1_0.ContextNumber; 30 import android.hardware.automotive.audiocontrol.V1_0.IAudioControl; 31 import android.media.AudioAttributes; 32 import android.media.AudioDeviceInfo; 33 import android.media.AudioDevicePort; 34 import android.media.AudioFormat; 35 import android.media.AudioGain; 36 import android.media.AudioGainConfig; 37 import android.media.AudioManager; 38 import android.media.AudioPatch; 39 import android.media.AudioPlaybackConfiguration; 40 import android.media.AudioPortConfig; 41 import android.media.AudioSystem; 42 import android.media.audiopolicy.AudioMix; 43 import android.media.audiopolicy.AudioMixingRule; 44 import android.media.audiopolicy.AudioPolicy; 45 import android.os.IBinder; 46 import android.os.Looper; 47 import android.os.RemoteException; 48 import android.telephony.TelephonyManager; 49 import android.text.TextUtils; 50 import android.util.Log; 51 import android.util.SparseArray; 52 import android.util.SparseIntArray; 53 54 import com.android.internal.util.Preconditions; 55 56 import java.io.PrintWriter; 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.HashSet; 60 import java.util.List; 61 import java.util.NoSuchElementException; 62 import java.util.Set; 63 import java.util.stream.Collectors; 64 65 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase { 66 67 private static final int DEFAULT_AUDIO_USAGE = AudioAttributes.USAGE_MEDIA; 68 69 private static final int[] CONTEXT_NUMBERS = new int[] { 70 ContextNumber.MUSIC, 71 ContextNumber.NAVIGATION, 72 ContextNumber.VOICE_COMMAND, 73 ContextNumber.CALL_RING, 74 ContextNumber.CALL, 75 ContextNumber.ALARM, 76 ContextNumber.NOTIFICATION, 77 ContextNumber.SYSTEM_SOUND 78 }; 79 80 private static final SparseIntArray USAGE_TO_CONTEXT = new SparseIntArray(); 81 82 // For legacy stream type based volume control. 83 // Values in STREAM_TYPES and STREAM_TYPE_USAGES should be aligned. 84 private static final int[] STREAM_TYPES = new int[] { 85 AudioManager.STREAM_MUSIC, 86 AudioManager.STREAM_ALARM, 87 AudioManager.STREAM_RING 88 }; 89 private static final int[] STREAM_TYPE_USAGES = new int[] { 90 AudioAttributes.USAGE_MEDIA, 91 AudioAttributes.USAGE_ALARM, 92 AudioAttributes.USAGE_NOTIFICATION_RINGTONE 93 }; 94 95 static { 96 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_UNKNOWN, ContextNumber.MUSIC); 97 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_MEDIA, ContextNumber.MUSIC); 98 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION, ContextNumber.CALL); 99 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING, 100 ContextNumber.CALL); 101 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ALARM, ContextNumber.ALARM); 102 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION, ContextNumber.NOTIFICATION); 103 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_RINGTONE, ContextNumber.CALL_RING); 104 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST, 105 ContextNumber.NOTIFICATION); 106 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT, 107 ContextNumber.NOTIFICATION); 108 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED, 109 ContextNumber.NOTIFICATION); 110 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_NOTIFICATION_EVENT, ContextNumber.NOTIFICATION); 111 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY, 112 ContextNumber.VOICE_COMMAND); 113 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, 114 ContextNumber.NAVIGATION); 115 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION, 116 ContextNumber.SYSTEM_SOUND); 117 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_GAME, ContextNumber.MUSIC); 118 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_VIRTUAL_SOURCE, ContextNumber.INVALID); 119 USAGE_TO_CONTEXT.put(AudioAttributes.USAGE_ASSISTANT, ContextNumber.VOICE_COMMAND); 120 } 121 122 private final Object mImplLock = new Object(); 123 124 private final Context mContext; 125 private final TelephonyManager mTelephonyManager; 126 private final AudioManager mAudioManager; 127 private final boolean mUseDynamicRouting; 128 private final SparseIntArray mContextToBus = new SparseIntArray(); 129 private final SparseArray<CarAudioDeviceInfo> mCarAudioDeviceInfos = new SparseArray<>(); 130 131 private final AudioPolicy.AudioPolicyVolumeCallback mAudioPolicyVolumeCallback = 132 new AudioPolicy.AudioPolicyVolumeCallback() { 133 @Override 134 public void onVolumeAdjustment(int adjustment) { 135 final int usage = getSuggestedAudioUsage(); 136 Log.v(CarLog.TAG_AUDIO, 137 "onVolumeAdjustment: " + AudioManager.adjustToString(adjustment) 138 + " suggested usage: " + AudioAttributes.usageToString(usage)); 139 final int groupId = getVolumeGroupIdForUsage(usage); 140 final int currentVolume = getGroupVolume(groupId); 141 final int flags = AudioManager.FLAG_FROM_KEY | AudioManager.FLAG_SHOW_UI; 142 switch (adjustment) { 143 case AudioManager.ADJUST_LOWER: 144 if (currentVolume > getGroupMinVolume(groupId)) { 145 setGroupVolume(groupId, currentVolume - 1, flags); 146 } 147 break; 148 case AudioManager.ADJUST_RAISE: 149 if (currentVolume < getGroupMaxVolume(groupId)) { 150 setGroupVolume(groupId, currentVolume + 1, flags); 151 } 152 break; 153 case AudioManager.ADJUST_MUTE: 154 mAudioManager.setMasterMute(true, flags); 155 callbackMasterMuteChange(flags); 156 break; 157 case AudioManager.ADJUST_UNMUTE: 158 mAudioManager.setMasterMute(false, flags); 159 callbackMasterMuteChange(flags); 160 break; 161 case AudioManager.ADJUST_TOGGLE_MUTE: 162 mAudioManager.setMasterMute(!mAudioManager.isMasterMute(), flags); 163 callbackMasterMuteChange(flags); 164 break; 165 case AudioManager.ADJUST_SAME: 166 default: 167 break; 168 } 169 } 170 }; 171 172 private final BinderInterfaceContainer<ICarVolumeCallback> mVolumeCallbackContainer = 173 new BinderInterfaceContainer<>(); 174 175 /** 176 * Simulates {@link ICarVolumeCallback} when it's running in legacy mode. 177 */ 178 private final BroadcastReceiver mLegacyVolumeChangedReceiver = new BroadcastReceiver() { 179 @Override 180 public void onReceive(Context context, Intent intent) { 181 switch (intent.getAction()) { 182 case AudioManager.VOLUME_CHANGED_ACTION: 183 int streamType = intent.getIntExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, -1); 184 int groupId = getVolumeGroupIdForStreamType(streamType); 185 if (groupId == -1) { 186 Log.w(CarLog.TAG_AUDIO, "Unknown stream type: " + streamType); 187 } else { 188 callbackGroupVolumeChange(groupId, 0); 189 } 190 break; 191 case AudioManager.MASTER_MUTE_CHANGED_ACTION: 192 callbackMasterMuteChange(0); 193 break; 194 } 195 } 196 }; 197 198 private AudioPolicy mAudioPolicy; 199 private CarVolumeGroup[] mCarVolumeGroups; 200 201 public CarAudioService(Context context) { 202 mContext = context; 203 mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); 204 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 205 mUseDynamicRouting = mContext.getResources().getBoolean(R.bool.audioUseDynamicRouting); 206 } 207 208 /** 209 * Dynamic routing and volume groups are set only if 210 * {@link #mUseDynamicRouting} is {@code true}. Otherwise, this service runs in legacy mode. 211 */ 212 @Override 213 public void init() { 214 synchronized (mImplLock) { 215 if (!mUseDynamicRouting) { 216 Log.i(CarLog.TAG_AUDIO, "Audio dynamic routing not configured, run in legacy mode"); 217 setupLegacyVolumeChangedListener(); 218 } else { 219 setupDynamicRouting(); 220 setupVolumeGroups(); 221 } 222 } 223 } 224 225 @Override 226 public void release() { 227 synchronized (mImplLock) { 228 if (mUseDynamicRouting) { 229 if (mAudioPolicy != null) { 230 mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy); 231 mAudioPolicy = null; 232 } 233 } else { 234 mContext.unregisterReceiver(mLegacyVolumeChangedReceiver); 235 } 236 237 mVolumeCallbackContainer.clear(); 238 } 239 } 240 241 @Override 242 public void dump(PrintWriter writer) { 243 writer.println("*CarAudioService*"); 244 writer.println("\tRun in legacy mode? " + (!mUseDynamicRouting)); 245 writer.println("\tMaster mute? " + mAudioManager.isMasterMute()); 246 // Empty line for comfortable reading 247 writer.println(); 248 if (mUseDynamicRouting) { 249 for (CarVolumeGroup group : mCarVolumeGroups) { 250 group.dump(writer); 251 } 252 } 253 } 254 255 /** 256 * @see {@link android.car.media.CarAudioManager#setGroupVolume(int, int, int)} 257 */ 258 @Override 259 public void setGroupVolume(int groupId, int index, int flags) { 260 synchronized (mImplLock) { 261 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 262 263 callbackGroupVolumeChange(groupId, flags); 264 // For legacy stream type based volume control 265 if (!mUseDynamicRouting) { 266 mAudioManager.setStreamVolume(STREAM_TYPES[groupId], index, flags); 267 return; 268 } 269 270 CarVolumeGroup group = getCarVolumeGroup(groupId); 271 group.setCurrentGainIndex(index); 272 } 273 } 274 275 private void callbackGroupVolumeChange(int groupId, int flags) { 276 for (BinderInterfaceContainer.BinderInterface<ICarVolumeCallback> callback : 277 mVolumeCallbackContainer.getInterfaces()) { 278 try { 279 callback.binderInterface.onGroupVolumeChanged(groupId, flags); 280 } catch (RemoteException e) { 281 Log.e(CarLog.TAG_AUDIO, "Failed to callback onGroupVolumeChanged", e); 282 } 283 } 284 } 285 286 private void callbackMasterMuteChange(int flags) { 287 for (BinderInterfaceContainer.BinderInterface<ICarVolumeCallback> callback : 288 mVolumeCallbackContainer.getInterfaces()) { 289 try { 290 callback.binderInterface.onMasterMuteChanged(flags); 291 } catch (RemoteException e) { 292 Log.e(CarLog.TAG_AUDIO, "Failed to callback onMasterMuteChanged", e); 293 } 294 } 295 } 296 297 /** 298 * @see {@link android.car.media.CarAudioManager#getGroupMaxVolume(int)} 299 */ 300 @Override 301 public int getGroupMaxVolume(int groupId) { 302 synchronized (mImplLock) { 303 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 304 305 // For legacy stream type based volume control 306 if (!mUseDynamicRouting) { 307 return mAudioManager.getStreamMaxVolume(STREAM_TYPES[groupId]); 308 } 309 310 CarVolumeGroup group = getCarVolumeGroup(groupId); 311 return group.getMaxGainIndex(); 312 } 313 } 314 315 /** 316 * @see {@link android.car.media.CarAudioManager#getGroupMinVolume(int)} 317 */ 318 @Override 319 public int getGroupMinVolume(int groupId) { 320 synchronized (mImplLock) { 321 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 322 323 // For legacy stream type based volume control 324 if (!mUseDynamicRouting) { 325 return mAudioManager.getStreamMinVolume(STREAM_TYPES[groupId]); 326 } 327 328 CarVolumeGroup group = getCarVolumeGroup(groupId); 329 return group.getMinGainIndex(); 330 } 331 } 332 333 /** 334 * @see {@link android.car.media.CarAudioManager#getGroupVolume(int)} 335 */ 336 @Override 337 public int getGroupVolume(int groupId) { 338 synchronized (mImplLock) { 339 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 340 341 // For legacy stream type based volume control 342 if (!mUseDynamicRouting) { 343 return mAudioManager.getStreamVolume(STREAM_TYPES[groupId]); 344 } 345 346 CarVolumeGroup group = getCarVolumeGroup(groupId); 347 return group.getCurrentGainIndex(); 348 } 349 } 350 351 private CarVolumeGroup getCarVolumeGroup(int groupId) { 352 Preconditions.checkNotNull(mCarVolumeGroups); 353 Preconditions.checkArgument(groupId >= 0 && groupId < mCarVolumeGroups.length, 354 "groupId out of range: " + groupId); 355 return mCarVolumeGroups[groupId]; 356 } 357 358 private void setupLegacyVolumeChangedListener() { 359 IntentFilter intentFilter = new IntentFilter(); 360 intentFilter.addAction(AudioManager.VOLUME_CHANGED_ACTION); 361 intentFilter.addAction(AudioManager.MASTER_MUTE_CHANGED_ACTION); 362 mContext.registerReceiver(mLegacyVolumeChangedReceiver, intentFilter); 363 } 364 365 private void setupDynamicRouting() { 366 final IAudioControl audioControl = getAudioControl(); 367 if (audioControl == null) { 368 return; 369 } 370 AudioPolicy audioPolicy = getDynamicAudioPolicy(audioControl); 371 int r = mAudioManager.registerAudioPolicy(audioPolicy); 372 if (r != AudioManager.SUCCESS) { 373 throw new RuntimeException("registerAudioPolicy failed " + r); 374 } 375 mAudioPolicy = audioPolicy; 376 } 377 378 private void setupVolumeGroups() { 379 Preconditions.checkArgument(mCarAudioDeviceInfos.size() > 0, 380 "No bus device is configured to setup volume groups"); 381 final CarVolumeGroupsHelper helper = new CarVolumeGroupsHelper( 382 mContext, R.xml.car_volume_groups); 383 mCarVolumeGroups = helper.loadVolumeGroups(); 384 for (CarVolumeGroup group : mCarVolumeGroups) { 385 for (int contextNumber : group.getContexts()) { 386 int busNumber = mContextToBus.get(contextNumber); 387 group.bind(contextNumber, busNumber, mCarAudioDeviceInfos.get(busNumber)); 388 } 389 390 // Now that we have all our contexts, ensure the HAL gets our intial value 391 group.setCurrentGainIndex(group.getCurrentGainIndex()); 392 393 Log.v(CarLog.TAG_AUDIO, "Processed volume group: " + group); 394 } 395 // Perform validation after all volume groups are processed 396 if (!validateVolumeGroups()) { 397 throw new RuntimeException("Invalid volume groups configuration"); 398 } 399 } 400 401 /** 402 * Constraints applied here: 403 * 404 * - One context should not appear in two groups 405 * - All contexts are assigned 406 * - One bus should not appear in two groups 407 * - All gain controllers in the same group have same step value 408 * 409 * Note that it is fine that there are buses not appear in any group, those buses may be 410 * reserved for other usages. 411 * Step value validation is done in {@link CarVolumeGroup#bind(int, int, CarAudioDeviceInfo)} 412 * 413 * See also the car_volume_groups.xml configuration 414 */ 415 private boolean validateVolumeGroups() { 416 Set<Integer> contextSet = new HashSet<>(); 417 Set<Integer> busNumberSet = new HashSet<>(); 418 for (CarVolumeGroup group : mCarVolumeGroups) { 419 // One context should not appear in two groups 420 for (int context : group.getContexts()) { 421 if (contextSet.contains(context)) { 422 Log.e(CarLog.TAG_AUDIO, "Context appears in two groups: " + context); 423 return false; 424 } 425 contextSet.add(context); 426 } 427 428 // One bus should not appear in two groups 429 for (int busNumber : group.getBusNumbers()) { 430 if (busNumberSet.contains(busNumber)) { 431 Log.e(CarLog.TAG_AUDIO, "Bus appears in two groups: " + busNumber); 432 return false; 433 } 434 busNumberSet.add(busNumber); 435 } 436 } 437 438 // All contexts are assigned 439 if (contextSet.size() != CONTEXT_NUMBERS.length) { 440 Log.e(CarLog.TAG_AUDIO, "Some contexts are not assigned to group"); 441 Log.e(CarLog.TAG_AUDIO, "Assigned contexts " 442 + Arrays.toString(contextSet.toArray(new Integer[contextSet.size()]))); 443 Log.e(CarLog.TAG_AUDIO, "All contexts " + Arrays.toString(CONTEXT_NUMBERS)); 444 return false; 445 } 446 447 return true; 448 } 449 450 @Nullable 451 private AudioPolicy getDynamicAudioPolicy(@NonNull IAudioControl audioControl) { 452 AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext); 453 builder.setLooper(Looper.getMainLooper()); 454 455 // 1st, enumerate all output bus device ports 456 AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 457 if (deviceInfos.length == 0) { 458 Log.e(CarLog.TAG_AUDIO, "getDynamicAudioPolicy, no output device available, ignore"); 459 return null; 460 } 461 for (AudioDeviceInfo info : deviceInfos) { 462 Log.v(CarLog.TAG_AUDIO, String.format("output id=%d address=%s type=%s", 463 info.getId(), info.getAddress(), info.getType())); 464 if (info.getType() == AudioDeviceInfo.TYPE_BUS) { 465 final CarAudioDeviceInfo carInfo = new CarAudioDeviceInfo(info); 466 // See also the audio_policy_configuration.xml and getBusForContext in 467 // audio control HAL, the bus number should be no less than zero. 468 if (carInfo.getBusNumber() >= 0) { 469 mCarAudioDeviceInfos.put(carInfo.getBusNumber(), carInfo); 470 Log.i(CarLog.TAG_AUDIO, "Valid bus found " + carInfo); 471 } 472 } 473 } 474 475 // 2nd, map context to physical bus 476 try { 477 for (int contextNumber : CONTEXT_NUMBERS) { 478 int busNumber = audioControl.getBusForContext(contextNumber); 479 mContextToBus.put(contextNumber, busNumber); 480 CarAudioDeviceInfo info = mCarAudioDeviceInfos.get(busNumber); 481 if (info == null) { 482 Log.w(CarLog.TAG_AUDIO, "No bus configured for context: " + contextNumber); 483 } 484 } 485 } catch (RemoteException e) { 486 Log.e(CarLog.TAG_AUDIO, "Error mapping context to physical bus", e); 487 } 488 489 // 3rd, enumerate all physical buses and build the routing policy. 490 // Note that one can not register audio mix for same bus more than once. 491 for (int i = 0; i < mCarAudioDeviceInfos.size(); i++) { 492 int busNumber = mCarAudioDeviceInfos.keyAt(i); 493 boolean hasContext = false; 494 CarAudioDeviceInfo info = mCarAudioDeviceInfos.valueAt(i); 495 AudioFormat mixFormat = new AudioFormat.Builder() 496 .setSampleRate(info.getSampleRate()) 497 .setEncoding(info.getEncodingFormat()) 498 .setChannelMask(info.getChannelCount()) 499 .build(); 500 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder(); 501 for (int j = 0; j < mContextToBus.size(); j++) { 502 if (mContextToBus.valueAt(j) == busNumber) { 503 hasContext = true; 504 int contextNumber = mContextToBus.keyAt(j); 505 int[] usages = getUsagesForContext(contextNumber); 506 for (int usage : usages) { 507 mixingRuleBuilder.addRule( 508 new AudioAttributes.Builder().setUsage(usage).build(), 509 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE); 510 } 511 Log.i(CarLog.TAG_AUDIO, "Bus number: " + busNumber 512 + " contextNumber: " + contextNumber 513 + " sampleRate: " + info.getSampleRate() 514 + " channels: " + info.getChannelCount() 515 + " usages: " + Arrays.toString(usages)); 516 } 517 } 518 if (hasContext) { 519 // It's a valid case that an audio output bus is defined in 520 // audio_policy_configuration and no context is assigned to it. 521 // In such case, do not build a policy mix with zero rules. 522 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build()) 523 .setFormat(mixFormat) 524 .setDevice(info.getAudioDeviceInfo()) 525 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 526 .build(); 527 builder.addMix(audioMix); 528 } 529 } 530 531 // 4th, attach the {@link AudioPolicyVolumeCallback} 532 builder.setAudioPolicyVolumeCallback(mAudioPolicyVolumeCallback); 533 534 return builder.build(); 535 } 536 537 private int[] getUsagesForContext(int contextNumber) { 538 final List<Integer> usages = new ArrayList<>(); 539 for (int i = 0; i < USAGE_TO_CONTEXT.size(); i++) { 540 if (USAGE_TO_CONTEXT.valueAt(i) == contextNumber) { 541 usages.add(USAGE_TO_CONTEXT.keyAt(i)); 542 } 543 } 544 return usages.stream().mapToInt(i -> i).toArray(); 545 } 546 547 @Override 548 public void setFadeTowardFront(float value) { 549 synchronized (mImplLock) { 550 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 551 final IAudioControl audioControlHal = getAudioControl(); 552 if (audioControlHal != null) { 553 try { 554 audioControlHal.setFadeTowardFront(value); 555 } catch (RemoteException e) { 556 Log.e(CarLog.TAG_AUDIO, "setFadeTowardFront failed", e); 557 } 558 } 559 } 560 } 561 562 @Override 563 public void setBalanceTowardRight(float value) { 564 synchronized (mImplLock) { 565 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 566 final IAudioControl audioControlHal = getAudioControl(); 567 if (audioControlHal != null) { 568 try { 569 audioControlHal.setBalanceTowardRight(value); 570 } catch (RemoteException e) { 571 Log.e(CarLog.TAG_AUDIO, "setBalanceTowardRight failed", e); 572 } 573 } 574 } 575 } 576 577 /** 578 * @return Array of accumulated device addresses, empty array if we found nothing 579 */ 580 @Override 581 public @NonNull String[] getExternalSources() { 582 synchronized (mImplLock) { 583 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS); 584 List<String> sourceAddresses = new ArrayList<>(); 585 586 AudioDeviceInfo[] devices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); 587 if (devices.length == 0) { 588 Log.w(CarLog.TAG_AUDIO, "getExternalSources, no input devices found."); 589 } 590 591 // Collect the list of non-microphone input ports 592 for (AudioDeviceInfo info : devices) { 593 switch (info.getType()) { 594 // TODO: Can we trim this set down? Especially duplicates like FM vs FM_TUNER? 595 case AudioDeviceInfo.TYPE_FM: 596 case AudioDeviceInfo.TYPE_FM_TUNER: 597 case AudioDeviceInfo.TYPE_TV_TUNER: 598 case AudioDeviceInfo.TYPE_HDMI: 599 case AudioDeviceInfo.TYPE_AUX_LINE: 600 case AudioDeviceInfo.TYPE_LINE_ANALOG: 601 case AudioDeviceInfo.TYPE_LINE_DIGITAL: 602 case AudioDeviceInfo.TYPE_USB_ACCESSORY: 603 case AudioDeviceInfo.TYPE_USB_DEVICE: 604 case AudioDeviceInfo.TYPE_USB_HEADSET: 605 case AudioDeviceInfo.TYPE_IP: 606 case AudioDeviceInfo.TYPE_BUS: 607 String address = info.getAddress(); 608 if (TextUtils.isEmpty(address)) { 609 Log.w(CarLog.TAG_AUDIO, 610 "Discarded device with empty address, type=" + info.getType()); 611 } else { 612 sourceAddresses.add(address); 613 } 614 } 615 } 616 617 return sourceAddresses.toArray(new String[sourceAddresses.size()]); 618 } 619 } 620 621 @Override 622 public CarAudioPatchHandle createAudioPatch(String sourceAddress, 623 @AudioAttributes.AttributeUsage int usage, int gainInMillibels) { 624 synchronized (mImplLock) { 625 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS); 626 return createAudioPatchLocked(sourceAddress, usage, gainInMillibels); 627 } 628 } 629 630 @Override 631 public void releaseAudioPatch(CarAudioPatchHandle carPatch) { 632 synchronized (mImplLock) { 633 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS); 634 releaseAudioPatchLocked(carPatch); 635 } 636 } 637 638 private CarAudioPatchHandle createAudioPatchLocked(String sourceAddress, 639 @AudioAttributes.AttributeUsage int usage, int gainInMillibels) { 640 // Find the named source port 641 AudioDeviceInfo sourcePortInfo = null; 642 AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS); 643 for (AudioDeviceInfo info : deviceInfos) { 644 if (sourceAddress.equals(info.getAddress())) { 645 // This is the one for which we're looking 646 sourcePortInfo = info; 647 break; 648 } 649 } 650 Preconditions.checkNotNull(sourcePortInfo, 651 "Specified source is not available: " + sourceAddress); 652 653 // Find the output port associated with the given carUsage 654 AudioDevicePort sinkPort = Preconditions.checkNotNull(getAudioPort(usage), 655 "Sink not available for usage: " + AudioAttributes.usageToString(usage)); 656 657 // {@link android.media.AudioPort#activeConfig()} is valid for mixer port only, 658 // since audio framework has no clue what's active on the device ports. 659 // Therefore we construct an empty / default configuration here, which the audio HAL 660 // implementation should ignore. 661 AudioPortConfig sinkConfig = sinkPort.buildConfig(0, 662 AudioFormat.CHANNEL_OUT_DEFAULT, AudioFormat.ENCODING_DEFAULT, null); 663 Log.d(CarLog.TAG_AUDIO, "createAudioPatch sinkConfig: " + sinkConfig); 664 665 // Configure the source port to match the output port except for a gain adjustment 666 final CarAudioDeviceInfo helper = new CarAudioDeviceInfo(sourcePortInfo); 667 AudioGain audioGain = Preconditions.checkNotNull(helper.getAudioGain(), 668 "Gain controller not available for source port"); 669 670 // size of gain values is 1 in MODE_JOINT 671 AudioGainConfig audioGainConfig = audioGain.buildConfig(AudioGain.MODE_JOINT, 672 audioGain.channelMask(), new int[] { gainInMillibels }, 0); 673 // Construct an empty / default configuration excepts gain config here and it's up to the 674 // audio HAL how to interpret this configuration, which the audio HAL 675 // implementation should ignore. 676 AudioPortConfig sourceConfig = sourcePortInfo.getPort().buildConfig(0, 677 AudioFormat.CHANNEL_IN_DEFAULT, AudioFormat.ENCODING_DEFAULT, audioGainConfig); 678 679 // Create an audioPatch to connect the two ports 680 AudioPatch[] patch = new AudioPatch[] { null }; 681 int result = AudioManager.createAudioPatch(patch, 682 new AudioPortConfig[] { sourceConfig }, 683 new AudioPortConfig[] { sinkConfig }); 684 if (result != AudioManager.SUCCESS) { 685 throw new RuntimeException("createAudioPatch failed with code " + result); 686 } 687 688 Preconditions.checkNotNull(patch[0], 689 "createAudioPatch didn't provide expected single handle"); 690 Log.d(CarLog.TAG_AUDIO, "Audio patch created: " + patch[0]); 691 return new CarAudioPatchHandle(patch[0]); 692 } 693 694 private void releaseAudioPatchLocked(CarAudioPatchHandle carPatch) { 695 // NOTE: AudioPolicyService::removeNotificationClient will take care of this automatically 696 // if the client that created a patch quits. 697 698 // FIXME {@link AudioManager#listAudioPatches(ArrayList)} returns old generation of 699 // audio patches after creation 700 ArrayList<AudioPatch> patches = new ArrayList<>(); 701 int result = AudioSystem.listAudioPatches(patches, new int[1]); 702 if (result != AudioManager.SUCCESS) { 703 throw new RuntimeException("listAudioPatches failed with code " + result); 704 } 705 706 // Look for a patch that matches the provided user side handle 707 for (AudioPatch patch : patches) { 708 if (carPatch.represents(patch)) { 709 // Found it! 710 result = AudioManager.releaseAudioPatch(patch); 711 if (result != AudioManager.SUCCESS) { 712 throw new RuntimeException("releaseAudioPatch failed with code " + result); 713 } 714 return; 715 } 716 } 717 718 // If we didn't find a match, then something went awry, but it's probably not fatal... 719 Log.e(CarLog.TAG_AUDIO, "releaseAudioPatch found no match for " + carPatch); 720 } 721 722 @Override 723 public int getVolumeGroupCount() { 724 synchronized (mImplLock) { 725 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 726 727 // For legacy stream type based volume control 728 if (!mUseDynamicRouting) return STREAM_TYPES.length; 729 730 return mCarVolumeGroups == null ? 0 : mCarVolumeGroups.length; 731 } 732 } 733 734 @Override 735 public int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage) { 736 synchronized (mImplLock) { 737 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 738 739 if (mCarVolumeGroups == null) { 740 return -1; 741 } 742 743 for (int i = 0; i < mCarVolumeGroups.length; i++) { 744 int[] contexts = mCarVolumeGroups[i].getContexts(); 745 for (int context : contexts) { 746 if (USAGE_TO_CONTEXT.get(usage) == context) { 747 return i; 748 } 749 } 750 } 751 return -1; 752 } 753 } 754 755 @Override 756 public @NonNull int[] getUsagesForVolumeGroupId(int groupId) { 757 synchronized (mImplLock) { 758 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 759 760 // For legacy stream type based volume control 761 if (!mUseDynamicRouting) { 762 return new int[] { STREAM_TYPE_USAGES[groupId] }; 763 } 764 765 CarVolumeGroup group = getCarVolumeGroup(groupId); 766 Set<Integer> contexts = 767 Arrays.stream(group.getContexts()).boxed().collect(Collectors.toSet()); 768 final List<Integer> usages = new ArrayList<>(); 769 for (int i = 0; i < USAGE_TO_CONTEXT.size(); i++) { 770 if (contexts.contains(USAGE_TO_CONTEXT.valueAt(i))) { 771 usages.add(USAGE_TO_CONTEXT.keyAt(i)); 772 } 773 } 774 return usages.stream().mapToInt(i -> i).toArray(); 775 } 776 } 777 778 /** 779 * See {@link android.car.media.CarAudioManager#registerVolumeCallback(IBinder)} 780 */ 781 @Override 782 public void registerVolumeCallback(@NonNull IBinder binder) { 783 synchronized (mImplLock) { 784 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 785 786 mVolumeCallbackContainer.addBinder(ICarVolumeCallback.Stub.asInterface(binder)); 787 } 788 } 789 790 /** 791 * See {@link android.car.media.CarAudioManager#unregisterVolumeCallback(IBinder)} 792 */ 793 @Override 794 public void unregisterVolumeCallback(@NonNull IBinder binder) { 795 synchronized (mImplLock) { 796 enforcePermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 797 798 mVolumeCallbackContainer.removeBinder(ICarVolumeCallback.Stub.asInterface(binder)); 799 } 800 } 801 802 private void enforcePermission(String permissionName) { 803 if (mContext.checkCallingOrSelfPermission(permissionName) 804 != PackageManager.PERMISSION_GRANTED) { 805 throw new SecurityException("requires permission " + permissionName); 806 } 807 } 808 809 /** 810 * @return {@link AudioDevicePort} that handles the given car audio usage. 811 * Multiple usages may share one {@link AudioDevicePort} 812 */ 813 private @Nullable AudioDevicePort getAudioPort(@AudioAttributes.AttributeUsage int usage) { 814 final int groupId = getVolumeGroupIdForUsage(usage); 815 final CarVolumeGroup group = Preconditions.checkNotNull(mCarVolumeGroups[groupId], 816 "Can not find CarVolumeGroup by usage: " 817 + AudioAttributes.usageToString(usage)); 818 return group.getAudioDevicePortForContext(USAGE_TO_CONTEXT.get(usage)); 819 } 820 821 /** 822 * @return The suggested {@link AudioAttributes} usage to which the volume key events apply 823 */ 824 private @AudioAttributes.AttributeUsage int getSuggestedAudioUsage() { 825 int callState = mTelephonyManager.getCallState(); 826 if (callState == TelephonyManager.CALL_STATE_RINGING) { 827 return AudioAttributes.USAGE_NOTIFICATION_RINGTONE; 828 } else if (callState == TelephonyManager.CALL_STATE_OFFHOOK) { 829 return AudioAttributes.USAGE_VOICE_COMMUNICATION; 830 } else { 831 List<AudioPlaybackConfiguration> playbacks = mAudioManager 832 .getActivePlaybackConfigurations() 833 .stream() 834 .filter(AudioPlaybackConfiguration::isActive) 835 .collect(Collectors.toList()); 836 if (!playbacks.isEmpty()) { 837 // Get audio usage from active playbacks if there is any, last one if multiple 838 return playbacks.get(playbacks.size() - 1).getAudioAttributes().getUsage(); 839 } else { 840 // TODO(b/72695246): Otherwise, get audio usage from foreground activity/window 841 return DEFAULT_AUDIO_USAGE; 842 } 843 } 844 } 845 846 /** 847 * Gets volume group by a given legacy stream type 848 * @param streamType Legacy stream type such as {@link AudioManager#STREAM_MUSIC} 849 * @return volume group id mapped from stream type 850 */ 851 private int getVolumeGroupIdForStreamType(int streamType) { 852 int groupId = -1; 853 for (int i = 0; i < STREAM_TYPES.length; i++) { 854 if (streamType == STREAM_TYPES[i]) { 855 groupId = i; 856 break; 857 } 858 } 859 return groupId; 860 } 861 862 @Nullable 863 private static IAudioControl getAudioControl() { 864 try { 865 return IAudioControl.getService(); 866 } catch (RemoteException e) { 867 Log.e(CarLog.TAG_AUDIO, "Failed to get IAudioControl service", e); 868 } catch (NoSuchElementException e) { 869 Log.e(CarLog.TAG_AUDIO, "IAudioControl service not registered yet"); 870 } 871 return null; 872 } 873 } 874