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 android.car.media; 17 18 import android.annotation.IntDef; 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.RequiresPermission; 22 import android.annotation.SystemApi; 23 import android.annotation.TestApi; 24 import android.car.Car; 25 import android.car.CarLibLog; 26 import android.car.CarManagerBase; 27 import android.media.AudioAttributes; 28 import android.media.AudioDeviceAttributes; 29 import android.media.AudioDeviceInfo; 30 import android.media.AudioManager; 31 import android.media.AudioManager.AudioDeviceRole; 32 import android.os.Bundle; 33 import android.os.Handler; 34 import android.os.IBinder; 35 import android.os.Looper; 36 import android.os.Message; 37 import android.os.RemoteException; 38 import android.util.Log; 39 40 import java.lang.annotation.Retention; 41 import java.lang.annotation.RetentionPolicy; 42 import java.util.ArrayList; 43 import java.util.Collections; 44 import java.util.HashSet; 45 import java.util.List; 46 import java.util.Objects; 47 import java.util.Set; 48 import java.util.concurrent.CopyOnWriteArrayList; 49 50 /** 51 * APIs for handling audio in a car. 52 * 53 * In a car environment, we introduced the support to turn audio dynamic routing on /off by 54 * setting the "audioUseDynamicRouting" attribute in config.xml 55 * 56 * When audio dynamic routing is enabled: 57 * - Audio devices are grouped into zones 58 * - There is at least one primary zone, and extra secondary zones such as RSE 59 * (Reat Seat Entertainment) 60 * - Within each zone, audio devices are grouped into volume groups for volume control 61 * - Audio is assigned to an audio device based on its AudioAttributes usage 62 * 63 * When audio dynamic routing is disabled: 64 * - There is exactly one audio zone, which is the primary zone 65 * - Each volume group represents a controllable STREAM_TYPE, same as AudioManager 66 */ 67 public final class CarAudioManager extends CarManagerBase { 68 69 /** 70 * Zone id of the primary audio zone. 71 * @hide 72 */ 73 @SystemApi 74 public static final int PRIMARY_AUDIO_ZONE = 0x0; 75 76 /** 77 * Zone id of the invalid audio zone. 78 * @hide 79 */ 80 @SystemApi 81 public static final int INVALID_AUDIO_ZONE = 0xffffffff; 82 83 /** 84 * This is used to determine if dynamic routing is enabled via 85 * {@link #isAudioFeatureEnabled()} 86 */ 87 public static final int AUDIO_FEATURE_DYNAMIC_ROUTING = 0x1; 88 89 /** 90 * This is used to determine if volume group muting is enabled via 91 * {@link #isAudioFeatureEnabled()} 92 * 93 * <p> 94 * If enabled, car volume group muting APIs can be used to mute each volume group, 95 * also car volume group muting changed callback will be called upon group mute changes. If 96 * disabled, car volume will toggle master mute instead. 97 */ 98 public static final int AUDIO_FEATURE_VOLUME_GROUP_MUTING = 0x2; 99 100 /** @hide */ 101 @IntDef(flag = false, prefix = "AUDIO_FEATURE", value = { 102 AUDIO_FEATURE_DYNAMIC_ROUTING, 103 AUDIO_FEATURE_VOLUME_GROUP_MUTING 104 }) 105 @Retention(RetentionPolicy.SOURCE) 106 public @interface CarAudioFeature {} 107 108 /** 109 * Volume Group ID when volume group not found. 110 * @hide 111 */ 112 public static final int INVALID_VOLUME_GROUP_ID = -1; 113 114 /** 115 * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an 116 * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus events, 117 * including {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}. 118 * The requester must hold {@link Car#PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS}; otherwise, 119 * this extra is ignored. 120 * 121 * @hide 122 */ 123 @SystemApi 124 public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS = 125 "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS"; 126 127 /** 128 * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an 129 * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus for the 130 * the zone. If the zone id is not defined: the audio focus request will default to the 131 * currently mapped zone for the requesting uid or {@link CarAudioManager.PRIMARY_AUDIO_ZONE} 132 * if no uid mapping currently exist. 133 * 134 * @hide 135 */ 136 public static final String AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID = 137 "android.car.media.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID"; 138 139 private final ICarAudio mService; 140 private final CopyOnWriteArrayList<CarVolumeCallback> mCarVolumeCallbacks; 141 private final AudioManager mAudioManager; 142 143 private final EventHandler mEventHandler; 144 145 private final ICarVolumeCallback mCarVolumeCallbackImpl = 146 new android.car.media.ICarVolumeCallback.Stub() { 147 @Override 148 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { 149 mEventHandler.dispatchOnGroupVolumeChanged(zoneId, groupId, flags); 150 } 151 152 @Override 153 public void onGroupMuteChanged(int zoneId, int groupId, int flags) { 154 mEventHandler.dispatchOnGroupMuteChanged(zoneId, groupId, flags); 155 } 156 157 @Override 158 public void onMasterMuteChanged(int zoneId, int flags) { 159 mEventHandler.dispatchOnMasterMuteChanged(zoneId, flags); 160 } 161 }; 162 163 /** 164 * @return Whether dynamic routing is enabled or not. 165 * 166 * @deprecated use {@link #isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)} instead. 167 * 168 * @hide 169 */ 170 @TestApi 171 @Deprecated isDynamicRoutingEnabled()172 public boolean isDynamicRoutingEnabled() { 173 return isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING); 174 } 175 176 /** 177 * Determines if an audio feature is enabled. 178 * 179 * @param audioFeature audio feature to query, can be {@link #AUDIO_FEATURE_DYNAMIC_ROUTING} or 180 * {@link #AUDIO_FEATURE_VOLUME_GROUP_MUTING} 181 * @return Returns {@code true} if the feature is enabled, {@code false} otherwise. 182 */ isAudioFeatureEnabled(@arAudioFeature int audioFeature)183 public boolean isAudioFeatureEnabled(@CarAudioFeature int audioFeature) { 184 try { 185 return mService.isAudioFeatureEnabled(audioFeature); 186 } catch (RemoteException e) { 187 return handleRemoteExceptionFromCarService(e, false); 188 } 189 } 190 191 /** 192 * Sets the volume index for a volume group in primary zone. 193 * 194 * @see {@link #setGroupVolume(int, int, int, int)} 195 * @hide 196 */ 197 @SystemApi 198 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setGroupVolume(int groupId, int index, int flags)199 public void setGroupVolume(int groupId, int index, int flags) { 200 setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, index, flags); 201 } 202 203 /** 204 * Sets the volume index for a volume group. 205 * 206 * @param zoneId The zone id whose volume group is affected. 207 * @param groupId The volume group id whose volume index should be set. 208 * @param index The volume index to set. See 209 * {@link #getGroupMaxVolume(int, int)} for the largest valid value. 210 * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI}, 211 * {@link android.media.AudioManager#FLAG_PLAY_SOUND}) 212 * @hide 213 */ 214 @SystemApi 215 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setGroupVolume(int zoneId, int groupId, int index, int flags)216 public void setGroupVolume(int zoneId, int groupId, int index, int flags) { 217 try { 218 mService.setGroupVolume(zoneId, groupId, index, flags); 219 } catch (RemoteException e) { 220 handleRemoteExceptionFromCarService(e); 221 } 222 } 223 224 /** 225 * Returns the maximum volume index for a volume group in primary zone. 226 * 227 * @see {@link #getGroupMaxVolume(int, int)} 228 * @hide 229 */ 230 @SystemApi 231 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMaxVolume(int groupId)232 public int getGroupMaxVolume(int groupId) { 233 return getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId); 234 } 235 236 /** 237 * Returns the maximum volume index for a volume group. 238 * 239 * @param zoneId The zone id whose volume group is queried. 240 * @param groupId The volume group id whose maximum volume index is returned. 241 * @return The maximum valid volume index for the given group. 242 * @hide 243 */ 244 @SystemApi 245 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMaxVolume(int zoneId, int groupId)246 public int getGroupMaxVolume(int zoneId, int groupId) { 247 try { 248 return mService.getGroupMaxVolume(zoneId, groupId); 249 } catch (RemoteException e) { 250 return handleRemoteExceptionFromCarService(e, 0); 251 } 252 } 253 254 /** 255 * Returns the minimum volume index for a volume group in primary zone. 256 * 257 * @see {@link #getGroupMinVolume(int, int)} 258 * @hide 259 */ 260 @SystemApi 261 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMinVolume(int groupId)262 public int getGroupMinVolume(int groupId) { 263 return getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId); 264 } 265 266 /** 267 * Returns the minimum volume index for a volume group. 268 * 269 * @param zoneId The zone id whose volume group is queried. 270 * @param groupId The volume group id whose minimum volume index is returned. 271 * @return The minimum valid volume index for the given group, non-negative 272 * @hide 273 */ 274 @SystemApi 275 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMinVolume(int zoneId, int groupId)276 public int getGroupMinVolume(int zoneId, int groupId) { 277 try { 278 return mService.getGroupMinVolume(zoneId, groupId); 279 } catch (RemoteException e) { 280 return handleRemoteExceptionFromCarService(e, 0); 281 } 282 } 283 284 /** 285 * Returns the current volume index for a volume group in primary zone. 286 * 287 * @see {@link #getGroupVolume(int, int)} 288 * @hide 289 */ 290 @SystemApi 291 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupVolume(int groupId)292 public int getGroupVolume(int groupId) { 293 return getGroupVolume(PRIMARY_AUDIO_ZONE, groupId); 294 } 295 296 /** 297 * Returns the current volume index for a volume group. 298 * 299 * @param zoneId The zone id whose volume groups is queried. 300 * @param groupId The volume group id whose volume index is returned. 301 * @return The current volume index for the given group. 302 * 303 * @see #getGroupMaxVolume(int, int) 304 * @see #setGroupVolume(int, int, int, int) 305 * @hide 306 */ 307 @SystemApi 308 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupVolume(int zoneId, int groupId)309 public int getGroupVolume(int zoneId, int groupId) { 310 try { 311 return mService.getGroupVolume(zoneId, groupId); 312 } catch (RemoteException e) { 313 return handleRemoteExceptionFromCarService(e, 0); 314 } 315 } 316 317 /** 318 * Adjust the relative volume in the front vs back of the vehicle cabin. 319 * 320 * @param value in the range -1.0 to 1.0 for fully toward the back through 321 * fully toward the front. 0.0 means evenly balanced. 322 * 323 * @see #setBalanceTowardRight(float) 324 * @hide 325 */ 326 @SystemApi 327 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setFadeTowardFront(float value)328 public void setFadeTowardFront(float value) { 329 try { 330 mService.setFadeTowardFront(value); 331 } catch (RemoteException e) { 332 handleRemoteExceptionFromCarService(e); 333 } 334 } 335 336 /** 337 * Adjust the relative volume on the left vs right side of the vehicle cabin. 338 * 339 * @param value in the range -1.0 to 1.0 for fully toward the left through 340 * fully toward the right. 0.0 means evenly balanced. 341 * 342 * @see #setFadeTowardFront(float) 343 * @hide 344 */ 345 @SystemApi 346 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setBalanceTowardRight(float value)347 public void setBalanceTowardRight(float value) { 348 try { 349 mService.setBalanceTowardRight(value); 350 } catch (RemoteException e) { 351 handleRemoteExceptionFromCarService(e); 352 } 353 } 354 355 /** 356 * Queries the system configuration in order to report the available, non-microphone audio 357 * input devices. 358 * 359 * @return An array of strings representing the available input ports. 360 * Each port is identified by it's "address" tag in the audioPolicyConfiguration xml file. 361 * Empty array if we find nothing. 362 * 363 * @see #createAudioPatch(String, int, int) 364 * @see #releaseAudioPatch(CarAudioPatchHandle) 365 * @hide 366 */ 367 @SystemApi 368 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getExternalSources()369 public @NonNull String[] getExternalSources() { 370 try { 371 return mService.getExternalSources(); 372 } catch (RemoteException e) { 373 handleRemoteExceptionFromCarService(e); 374 return new String[0]; 375 } 376 } 377 378 /** 379 * Given an input port identified by getExternalSources(), request that it's audio signal 380 * be routed below the HAL to the output port associated with the given usage. For example, 381 * The output of a tuner might be routed directly to the output buss associated with 382 * AudioAttributes.USAGE_MEDIA while the tuner is playing. 383 * 384 * @param sourceAddress the input port name obtained from getExternalSources(). 385 * @param usage the type of audio represented by this source (usually USAGE_MEDIA). 386 * @param gainInMillibels How many steps above the minimum value defined for the source port to 387 * set the gain when creating the patch. 388 * This may be used for source balancing without affecting the user 389 * controlled volumes applied to the destination ports. A value of 390 * 0 indicates no gain change is requested. 391 * @return A handle for the created patch which can be used to later remove it. 392 * 393 * @see #getExternalSources() 394 * @see #releaseAudioPatch(CarAudioPatchHandle) 395 * @hide 396 */ 397 @SystemApi 398 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) createAudioPatch(String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels)399 public CarAudioPatchHandle createAudioPatch(String sourceAddress, 400 @AudioAttributes.AttributeUsage int usage, int gainInMillibels) { 401 try { 402 return mService.createAudioPatch(sourceAddress, usage, gainInMillibels); 403 } catch (RemoteException e) { 404 return handleRemoteExceptionFromCarService(e, null); 405 } 406 } 407 408 /** 409 * Removes the association between an input port and an output port identified by the provided 410 * handle. 411 * 412 * @param patch CarAudioPatchHandle returned from createAudioPatch(). 413 * 414 * @see #getExternalSources() 415 * @see #createAudioPatch(String, int, int) 416 * @hide 417 */ 418 @SystemApi 419 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) releaseAudioPatch(CarAudioPatchHandle patch)420 public void releaseAudioPatch(CarAudioPatchHandle patch) { 421 try { 422 mService.releaseAudioPatch(patch); 423 } catch (RemoteException e) { 424 handleRemoteExceptionFromCarService(e); 425 } 426 } 427 428 /** 429 * Gets the count of available volume groups in primary zone. 430 * 431 * @see {@link #getVolumeGroupCount(int)} 432 * @hide 433 */ 434 @SystemApi 435 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupCount()436 public int getVolumeGroupCount() { 437 return getVolumeGroupCount(PRIMARY_AUDIO_ZONE); 438 } 439 440 /** 441 * Gets the count of available volume groups in the system. 442 * 443 * @param zoneId The zone id whois count of volume groups is queried. 444 * @return Count of volume groups 445 * @hide 446 */ 447 @SystemApi 448 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupCount(int zoneId)449 public int getVolumeGroupCount(int zoneId) { 450 try { 451 return mService.getVolumeGroupCount(zoneId); 452 } catch (RemoteException e) { 453 return handleRemoteExceptionFromCarService(e, 0); 454 } 455 } 456 457 /** 458 * Gets the volume group id for a given {@link AudioAttributes} usage in primary zone. 459 * 460 * @see {@link #getVolumeGroupIdForUsage(int, int)} 461 * @hide 462 */ 463 @SystemApi 464 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupIdForUsage(@udioAttributes.AttributeUsage int usage)465 public int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage) { 466 return getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE, usage); 467 } 468 469 /** 470 * Gets the volume group id for a given {@link AudioAttributes} usage. 471 * 472 * @param zoneId The zone id whose volume group is queried. 473 * @param usage The {@link AudioAttributes} usage to get a volume group from. 474 * @return The volume group id where the usage belongs to 475 * @hide 476 */ 477 @SystemApi 478 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)479 public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) { 480 try { 481 return mService.getVolumeGroupIdForUsage(zoneId, usage); 482 } catch (RemoteException e) { 483 return handleRemoteExceptionFromCarService(e, 0); 484 } 485 } 486 487 /** 488 * Gets array of {@link AudioAttributes} usages for a volume group in primary zone. 489 * 490 * @see {@link #getUsagesForVolumeGroupId(int, int)} 491 * @hide 492 */ 493 @SystemApi 494 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getUsagesForVolumeGroupId(int groupId)495 public @NonNull int[] getUsagesForVolumeGroupId(int groupId) { 496 return getUsagesForVolumeGroupId(PRIMARY_AUDIO_ZONE, groupId); 497 } 498 499 /** 500 * Gets array of {@link AudioAttributes} usages for a volume group in a zone. 501 * 502 * @param zoneId The zone id whose volume group is queried. 503 * @param groupId The volume group id whose associated audio usages is returned. 504 * @return Array of {@link AudioAttributes} usages for a given volume group id 505 * @hide 506 */ 507 @SystemApi 508 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getUsagesForVolumeGroupId(int zoneId, int groupId)509 public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) { 510 try { 511 return mService.getUsagesForVolumeGroupId(zoneId, groupId); 512 } catch (RemoteException e) { 513 return handleRemoteExceptionFromCarService(e, new int[0]); 514 } 515 } 516 517 /** 518 * Determines if a particular volume group has any audio playback in a zone 519 * 520 * @param zoneId The zone id whose volume group is queried. 521 * @param groupId The volume group id whose associated audio usages is returned. 522 * @return {@code true} if the group has active playback, {@code false} otherwise 523 * 524 * @hide 525 */ 526 @SystemApi 527 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) isPlaybackOnVolumeGroupActive(int zoneId, int groupId)528 public boolean isPlaybackOnVolumeGroupActive(int zoneId, int groupId) { 529 try { 530 return mService.isPlaybackOnVolumeGroupActive(zoneId, groupId); 531 } catch (RemoteException e) { 532 return handleRemoteExceptionFromCarService(e, false); 533 } 534 } 535 536 /** 537 * Gets the audio zones currently available 538 * 539 * @return audio zone ids 540 * @hide 541 */ 542 @SystemApi 543 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getAudioZoneIds()544 public @NonNull List<Integer> getAudioZoneIds() { 545 try { 546 int[] zoneIdArray = mService.getAudioZoneIds(); 547 List<Integer> zoneIdList = new ArrayList<Integer>(zoneIdArray.length); 548 for (int zoneIdValue : zoneIdArray) { 549 zoneIdList.add(zoneIdValue); 550 } 551 return zoneIdList; 552 } catch (RemoteException e) { 553 return handleRemoteExceptionFromCarService(e, Collections.emptyList()); 554 } 555 } 556 557 /** 558 * Gets the audio zone id currently mapped to uId, 559 * defaults to PRIMARY_AUDIO_ZONE if no mapping exist 560 * 561 * @param uid The uid to map 562 * @return zone id mapped to uid 563 * @hide 564 */ 565 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getZoneIdForUid(int uid)566 public int getZoneIdForUid(int uid) { 567 try { 568 return mService.getZoneIdForUid(uid); 569 } catch (RemoteException e) { 570 return handleRemoteExceptionFromCarService(e, 0); 571 } 572 } 573 574 /** 575 * Maps the audio zone id to uid 576 * 577 * @param zoneId The audio zone id 578 * @param uid The uid to map 579 * @return true if the uid is successfully mapped 580 * @hide 581 */ 582 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) setZoneIdForUid(int zoneId, int uid)583 public boolean setZoneIdForUid(int zoneId, int uid) { 584 try { 585 return mService.setZoneIdForUid(zoneId, uid); 586 } catch (RemoteException e) { 587 return handleRemoteExceptionFromCarService(e, false); 588 } 589 } 590 591 /** 592 * Clears the current zone mapping of the uid 593 * 594 * @param uid The uid to clear 595 * @return true if the zone was successfully cleared 596 * @hide 597 */ 598 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) clearZoneIdForUid(int uid)599 public boolean clearZoneIdForUid(int uid) { 600 try { 601 return mService.clearZoneIdForUid(uid); 602 } catch (RemoteException e) { 603 return handleRemoteExceptionFromCarService(e, false); 604 } 605 } 606 607 /** 608 * Gets the output device for a given {@link AudioAttributes} usage in zoneId. 609 * 610 * <p><b>Note:</b> To be used for routing to a specific device. Most applications should 611 * use the regular routing mechanism, which is to set audio attribute usage to 612 * an audio track. 613 * 614 * @param zoneId zone id to query for device 615 * @param usage usage where audio is routed 616 * @return Audio device info, returns {@code null} if audio device usage fails to map to 617 * an active audio device. This is different from the using an invalid value for 618 * {@link AudioAttributes} usage. In the latter case the query will fail with a 619 * RuntimeException indicating the issue. 620 * 621 * @hide 622 */ 623 @SystemApi 624 @Nullable 625 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getOutputDeviceForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)626 public AudioDeviceInfo getOutputDeviceForUsage(int zoneId, 627 @AudioAttributes.AttributeUsage int usage) { 628 try { 629 String deviceAddress = mService.getOutputDeviceAddressForUsage(zoneId, usage); 630 if (deviceAddress == null) { 631 return null; 632 } 633 AudioDeviceInfo[] outputDevices = 634 mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 635 for (AudioDeviceInfo info : outputDevices) { 636 if (info.getAddress().equals(deviceAddress)) { 637 return info; 638 } 639 } 640 return null; 641 } catch (RemoteException e) { 642 return handleRemoteExceptionFromCarService(e, null); 643 } 644 } 645 646 /** 647 * Gets the input devices for an audio zone 648 * 649 * @return list of input devices 650 * @hide 651 */ 652 @SystemApi 653 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getInputDevicesForZoneId(int zoneId)654 public @NonNull List<AudioDeviceInfo> getInputDevicesForZoneId(int zoneId) { 655 try { 656 return convertInputDevicesToDeviceInfos( 657 mService.getInputDevicesForZoneId(zoneId), 658 AudioManager.GET_DEVICES_INPUTS); 659 } catch (RemoteException e) { 660 return handleRemoteExceptionFromCarService(e, new ArrayList<>()); 661 } 662 } 663 664 /** @hide */ 665 @Override onCarDisconnected()666 public void onCarDisconnected() { 667 if (mService != null && !mCarVolumeCallbacks.isEmpty()) { 668 unregisterVolumeCallback(); 669 } 670 } 671 672 /** @hide */ CarAudioManager(Car car, IBinder service)673 public CarAudioManager(Car car, IBinder service) { 674 super(car); 675 mService = ICarAudio.Stub.asInterface(service); 676 mAudioManager = getContext().getSystemService(AudioManager.class); 677 mCarVolumeCallbacks = new CopyOnWriteArrayList<>(); 678 mEventHandler = new EventHandler(getEventHandler().getLooper()); 679 } 680 681 /** 682 * Registers a {@link CarVolumeCallback} to receive volume change callbacks 683 * @param callback {@link CarVolumeCallback} instance, can not be null 684 * <p> 685 * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME 686 */ registerCarVolumeCallback(@onNull CarVolumeCallback callback)687 public void registerCarVolumeCallback(@NonNull CarVolumeCallback callback) { 688 Objects.requireNonNull(callback); 689 690 if (mCarVolumeCallbacks.isEmpty()) { 691 registerVolumeCallback(); 692 } 693 694 mCarVolumeCallbacks.add(callback); 695 } 696 697 /** 698 * Unregisters a {@link CarVolumeCallback} from receiving volume change callbacks 699 * @param callback {@link CarVolumeCallback} instance previously registered, can not be null 700 * <p> 701 * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME 702 */ unregisterCarVolumeCallback(@onNull CarVolumeCallback callback)703 public void unregisterCarVolumeCallback(@NonNull CarVolumeCallback callback) { 704 Objects.requireNonNull(callback); 705 if (mCarVolumeCallbacks.remove(callback) && mCarVolumeCallbacks.isEmpty()) { 706 unregisterVolumeCallback(); 707 } 708 } 709 registerVolumeCallback()710 private void registerVolumeCallback() { 711 try { 712 mService.registerVolumeCallback(mCarVolumeCallbackImpl.asBinder()); 713 } catch (RemoteException e) { 714 Log.e(CarLibLog.TAG_CAR, "registerVolumeCallback failed", e); 715 } 716 } 717 unregisterVolumeCallback()718 private void unregisterVolumeCallback() { 719 try { 720 mService.unregisterVolumeCallback(mCarVolumeCallbackImpl.asBinder()); 721 } catch (RemoteException e) { 722 handleRemoteExceptionFromCarService(e); 723 } 724 } 725 726 /** 727 * Returns the whether a volume group is muted 728 * 729 * <p><b>Note:<b/> If {@link #AUDIO_FEATURE_VOLUME_GROUP_MUTING} is disabled this will always 730 * return {@code false} as group mute is disabled. 731 * 732 * @param zoneId The zone id whose volume groups is queried. 733 * @param groupId The volume group id whose mute state is returned. 734 * @return {@code true} if the volume group is muted, {@code false} 735 * otherwise 736 * 737 * @hide 738 */ 739 @SystemApi 740 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) isVolumeGroupMuted(int zoneId, int groupId)741 public boolean isVolumeGroupMuted(int zoneId, int groupId) { 742 try { 743 return mService.isVolumeGroupMuted(zoneId, groupId); 744 } catch (RemoteException e) { 745 return handleRemoteExceptionFromCarService(e, false); 746 } 747 } 748 749 /** 750 * Sets a volume group mute 751 * 752 * <p><b>Note:<b/> If {@link #AUDIO_FEATURE_VOLUME_GROUP_MUTING} is disabled this will throw an 753 * error indicating the issue. 754 * 755 * @param zoneId The zone id whose volume groups will be changed. 756 * @param groupId The volume group id whose mute state will be changed. 757 * @param mute {@code true} to mute volume group, {@code false} otherwise 758 * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI}, 759 * {@link android.media.AudioManager#FLAG_PLAY_SOUND}) 760 * 761 * @hide 762 */ 763 @SystemApi 764 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags)765 public void setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags) { 766 try { 767 mService.setVolumeGroupMute(zoneId, groupId, mute, flags); 768 } catch (RemoteException e) { 769 handleRemoteExceptionFromCarService(e); 770 } 771 } 772 convertInputDevicesToDeviceInfos( List<AudioDeviceAttributes> devices, @AudioDeviceRole int flag)773 private List<AudioDeviceInfo> convertInputDevicesToDeviceInfos( 774 List<AudioDeviceAttributes> devices, @AudioDeviceRole int flag) { 775 int addressesSize = devices.size(); 776 Set<String> deviceAddressMap = new HashSet<>(addressesSize); 777 for (int i = 0; i < addressesSize; ++i) { 778 AudioDeviceAttributes device = devices.get(i); 779 deviceAddressMap.add(device.getAddress()); 780 } 781 List<AudioDeviceInfo> deviceInfoList = new ArrayList<>(devices.size()); 782 AudioDeviceInfo[] inputDevices = mAudioManager.getDevices(flag); 783 for (int i = 0; i < inputDevices.length; ++i) { 784 AudioDeviceInfo info = inputDevices[i]; 785 if (info.isSource() && deviceAddressMap.contains(info.getAddress())) { 786 deviceInfoList.add(info); 787 } 788 } 789 return deviceInfoList; 790 } 791 792 private final class EventHandler extends Handler { 793 private static final int MSG_GROUP_VOLUME_CHANGE = 1; 794 private static final int MSG_GROUP_MUTE_CHANGE = 2; 795 private static final int MSG_MASTER_MUTE_CHANGE = 3; 796 EventHandler(Looper looper)797 private EventHandler(Looper looper) { 798 super(looper); 799 } 800 801 @Override handleMessage(Message msg)802 public void handleMessage(Message msg) { 803 switch (msg.what) { 804 case MSG_GROUP_VOLUME_CHANGE: 805 VolumeGroupChangeInfo volumeInfo = (VolumeGroupChangeInfo) msg.obj; 806 handleOnGroupVolumeChanged(volumeInfo.mZoneId, volumeInfo.mGroupId, 807 volumeInfo.mFlags); 808 break; 809 case MSG_GROUP_MUTE_CHANGE: 810 VolumeGroupChangeInfo muteInfo = (VolumeGroupChangeInfo) msg.obj; 811 handleOnGroupMuteChanged(muteInfo.mZoneId, muteInfo.mGroupId, muteInfo.mFlags); 812 break; 813 case MSG_MASTER_MUTE_CHANGE: 814 handleOnMasterMuteChanged(msg.arg1, msg.arg2); 815 break; 816 default: 817 Log.e(CarLibLog.TAG_CAR, "Unknown nessage not handled:" + msg.what); 818 break; 819 } 820 } 821 dispatchOnGroupVolumeChanged(int zoneId, int groupId, int flags)822 private void dispatchOnGroupVolumeChanged(int zoneId, int groupId, int flags) { 823 VolumeGroupChangeInfo volumeInfo = new VolumeGroupChangeInfo(zoneId, groupId, flags); 824 sendMessage(obtainMessage(MSG_GROUP_VOLUME_CHANGE, volumeInfo)); 825 } 826 dispatchOnMasterMuteChanged(int zoneId, int flags)827 private void dispatchOnMasterMuteChanged(int zoneId, int flags) { 828 sendMessage(obtainMessage(MSG_MASTER_MUTE_CHANGE, zoneId, flags)); 829 } 830 dispatchOnGroupMuteChanged(int zoneId, int groupId, int flags)831 private void dispatchOnGroupMuteChanged(int zoneId, int groupId, int flags) { 832 VolumeGroupChangeInfo volumeInfo = new VolumeGroupChangeInfo(zoneId, groupId, flags); 833 sendMessage(obtainMessage(MSG_GROUP_MUTE_CHANGE, volumeInfo)); 834 } 835 836 private class VolumeGroupChangeInfo { 837 public int mZoneId; 838 public int mGroupId; 839 public int mFlags; 840 VolumeGroupChangeInfo(int zoneId, int groupId, int flags)841 VolumeGroupChangeInfo(int zoneId, int groupId, int flags) { 842 mZoneId = zoneId; 843 mGroupId = groupId; 844 mFlags = flags; 845 } 846 } 847 } 848 handleOnGroupVolumeChanged(int zoneId, int groupId, int flags)849 private void handleOnGroupVolumeChanged(int zoneId, int groupId, int flags) { 850 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 851 callback.onGroupVolumeChanged(zoneId, groupId, flags); 852 } 853 } 854 handleOnMasterMuteChanged(int zoneId, int flags)855 private void handleOnMasterMuteChanged(int zoneId, int flags) { 856 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 857 callback.onMasterMuteChanged(zoneId, flags); 858 } 859 } 860 handleOnGroupMuteChanged(int zoneId, int groupId, int flags)861 private void handleOnGroupMuteChanged(int zoneId, int groupId, int flags) { 862 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 863 callback.onGroupMuteChanged(zoneId, groupId, flags); 864 } 865 } 866 867 /** 868 * Callback interface to receive volume change events in a car. 869 * Extend this class and register it with {@link #registerCarVolumeCallback(CarVolumeCallback)} 870 * and unregister it via {@link #unregisterCarVolumeCallback(CarVolumeCallback)} 871 */ 872 public abstract static class CarVolumeCallback { 873 /** 874 * This is called whenever a group volume is changed. 875 * The changed-to volume index is not included, the caller is encouraged to 876 * get the current group volume index via CarAudioManager. 877 * 878 * @param zoneId Id of the audio zone that volume change happens 879 * @param groupId Id of the volume group that volume is changed 880 * @param flags see {@link android.media.AudioManager} for flag definitions 881 */ onGroupVolumeChanged(int zoneId, int groupId, int flags)882 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {} 883 884 /** 885 * This is called whenever the global mute state is changed. 886 * The changed-to global mute state is not included, the caller is encouraged to 887 * get the current global mute state via AudioManager. 888 * 889 * <p><b>Note:<b/> If {@link CarAudioManager#AUDIO_FEATURE_VOLUME_GROUP_MUTING} is disabled 890 * this will be triggered on mute changes. Otherwise, car audio mute changes will trigger 891 * {@link #onGroupMuteChanged(int, int, int)} 892 * 893 * @param zoneId Id of the audio zone that global mute state change happens 894 * @param flags see {@link android.media.AudioManager} for flag definitions 895 */ onMasterMuteChanged(int zoneId, int flags)896 public void onMasterMuteChanged(int zoneId, int flags) {} 897 898 /** 899 * This is called whenever a group mute state is changed. 900 * The changed-to mute state is not included, the caller is encouraged to 901 * get the current group mute state via CarAudioManager. 902 * 903 * <p><b>Note:<b/> If {@link CarAudioManager#AUDIO_FEATURE_VOLUME_GROUP_MUTING} is enabled 904 * this will be triggered on mute changes. Otherwise, car audio mute changes will trigger 905 * {@link #onMasterMuteChanged(int, int)} 906 * 907 * @param zoneId Id of the audio zone that volume change happens 908 * @param groupId Id of the volume group that volume is changed 909 * @param flags see {@link android.media.AudioManager} for flag definitions 910 */ onGroupMuteChanged(int zoneId, int groupId, int flags)911 public void onGroupMuteChanged(int zoneId, int groupId, int flags) {} 912 } 913 } 914