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.NonNull; 19 import android.annotation.Nullable; 20 import android.annotation.RequiresPermission; 21 import android.annotation.SystemApi; 22 import android.annotation.TestApi; 23 import android.car.Car; 24 import android.car.CarLibLog; 25 import android.car.CarManagerBase; 26 import android.media.AudioAttributes; 27 import android.media.AudioDeviceAttributes; 28 import android.media.AudioDeviceInfo; 29 import android.media.AudioManager; 30 import android.media.AudioManager.AudioDeviceRole; 31 import android.os.Bundle; 32 import android.os.IBinder; 33 import android.os.RemoteException; 34 import android.util.Log; 35 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Objects; 41 import java.util.Set; 42 import java.util.concurrent.CopyOnWriteArrayList; 43 44 /** 45 * APIs for handling audio in a car. 46 * 47 * In a car environment, we introduced the support to turn audio dynamic routing on /off by 48 * setting the "audioUseDynamicRouting" attribute in config.xml 49 * 50 * When audio dynamic routing is enabled: 51 * - Audio devices are grouped into zones 52 * - There is at least one primary zone, and extra secondary zones such as RSE 53 * (Reat Seat Entertainment) 54 * - Within each zone, audio devices are grouped into volume groups for volume control 55 * - Audio is assigned to an audio device based on its AudioAttributes usage 56 * 57 * When audio dynamic routing is disabled: 58 * - There is exactly one audio zone, which is the primary zone 59 * - Each volume group represents a controllable STREAM_TYPE, same as AudioManager 60 */ 61 public final class CarAudioManager extends CarManagerBase { 62 63 /** 64 * Zone id of the primary audio zone. 65 * @hide 66 */ 67 @SystemApi 68 public static final int PRIMARY_AUDIO_ZONE = 0x0; 69 70 /** 71 * Zone id of the invalid audio zone. 72 * @hide 73 */ 74 @SystemApi 75 public static final int INVALID_AUDIO_ZONE = 0xffffffff; 76 77 /** 78 * Volume Group ID when volume group not found. 79 * @hide 80 */ 81 public static final int INVALID_VOLUME_GROUP_ID = -1; 82 83 /** 84 * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an 85 * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus events, 86 * including {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}. 87 * The requester must hold {@link Car#PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS}; otherwise, 88 * this extra is ignored. 89 * 90 * @hide 91 */ 92 @SystemApi 93 public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS = 94 "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS"; 95 96 /** 97 * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an 98 * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus for the 99 * the zone. If the zone id is not defined: the audio focus request will default to the 100 * currently mapped zone for the requesting uid or {@link CarAudioManager.PRIMARY_AUDIO_ZONE} 101 * if no uid mapping currently exist. 102 * 103 * @hide 104 */ 105 public static final String AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID = 106 "android.car.media.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID"; 107 108 private final ICarAudio mService; 109 private final CopyOnWriteArrayList<CarVolumeCallback> mCarVolumeCallbacks; 110 private final AudioManager mAudioManager; 111 112 private final ICarVolumeCallback mCarVolumeCallbackImpl = new ICarVolumeCallback.Stub() { 113 @Override 114 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { 115 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 116 callback.onGroupVolumeChanged(zoneId, groupId, flags); 117 } 118 } 119 120 @Override 121 public void onMasterMuteChanged(int zoneId, int flags) { 122 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 123 callback.onMasterMuteChanged(zoneId, flags); 124 } 125 } 126 }; 127 128 /** 129 * @return Whether dynamic routing is enabled or not. 130 * @hide 131 */ 132 @TestApi isDynamicRoutingEnabled()133 public boolean isDynamicRoutingEnabled() { 134 try { 135 return mService.isDynamicRoutingEnabled(); 136 } catch (RemoteException e) { 137 return handleRemoteExceptionFromCarService(e, false); 138 } 139 } 140 141 /** 142 * Sets the volume index for a volume group in primary zone. 143 * 144 * @see {@link #setGroupVolume(int, int, int, int)} 145 * @hide 146 */ 147 @SystemApi 148 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setGroupVolume(int groupId, int index, int flags)149 public void setGroupVolume(int groupId, int index, int flags) { 150 setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, index, flags); 151 } 152 153 /** 154 * Sets the volume index for a volume group. 155 * 156 * @param zoneId The zone id whose volume group is affected. 157 * @param groupId The volume group id whose volume index should be set. 158 * @param index The volume index to set. See 159 * {@link #getGroupMaxVolume(int, int)} for the largest valid value. 160 * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI}, 161 * {@link android.media.AudioManager#FLAG_PLAY_SOUND}) 162 * @hide 163 */ 164 @SystemApi 165 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setGroupVolume(int zoneId, int groupId, int index, int flags)166 public void setGroupVolume(int zoneId, int groupId, int index, int flags) { 167 try { 168 mService.setGroupVolume(zoneId, groupId, index, flags); 169 } catch (RemoteException e) { 170 handleRemoteExceptionFromCarService(e); 171 } 172 } 173 174 /** 175 * Returns the maximum volume index for a volume group in primary zone. 176 * 177 * @see {@link #getGroupMaxVolume(int, int)} 178 * @hide 179 */ 180 @SystemApi 181 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMaxVolume(int groupId)182 public int getGroupMaxVolume(int groupId) { 183 return getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId); 184 } 185 186 /** 187 * Returns the maximum volume index for a volume group. 188 * 189 * @param zoneId The zone id whose volume group is queried. 190 * @param groupId The volume group id whose maximum volume index is returned. 191 * @return The maximum valid volume index for the given group. 192 * @hide 193 */ 194 @SystemApi 195 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMaxVolume(int zoneId, int groupId)196 public int getGroupMaxVolume(int zoneId, int groupId) { 197 try { 198 return mService.getGroupMaxVolume(zoneId, groupId); 199 } catch (RemoteException e) { 200 return handleRemoteExceptionFromCarService(e, 0); 201 } 202 } 203 204 /** 205 * Returns the minimum volume index for a volume group in primary zone. 206 * 207 * @see {@link #getGroupMinVolume(int, int)} 208 * @hide 209 */ 210 @SystemApi 211 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMinVolume(int groupId)212 public int getGroupMinVolume(int groupId) { 213 return getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId); 214 } 215 216 /** 217 * Returns the minimum volume index for a volume group. 218 * 219 * @param zoneId The zone id whose volume group is queried. 220 * @param groupId The volume group id whose minimum volume index is returned. 221 * @return The minimum valid volume index for the given group, non-negative 222 * @hide 223 */ 224 @SystemApi 225 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMinVolume(int zoneId, int groupId)226 public int getGroupMinVolume(int zoneId, int groupId) { 227 try { 228 return mService.getGroupMinVolume(zoneId, groupId); 229 } catch (RemoteException e) { 230 return handleRemoteExceptionFromCarService(e, 0); 231 } 232 } 233 234 /** 235 * Returns the current volume index for a volume group in primary zone. 236 * 237 * @see {@link #getGroupVolume(int, int)} 238 * @hide 239 */ 240 @SystemApi 241 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupVolume(int groupId)242 public int getGroupVolume(int groupId) { 243 return getGroupVolume(PRIMARY_AUDIO_ZONE, groupId); 244 } 245 246 /** 247 * Returns the current volume index for a volume group. 248 * 249 * @param zoneId The zone id whose volume groups is queried. 250 * @param groupId The volume group id whose volume index is returned. 251 * @return The current volume index for the given group. 252 * 253 * @see #getGroupMaxVolume(int, int) 254 * @see #setGroupVolume(int, int, int, int) 255 * @hide 256 */ 257 @SystemApi 258 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupVolume(int zoneId, int groupId)259 public int getGroupVolume(int zoneId, int groupId) { 260 try { 261 return mService.getGroupVolume(zoneId, groupId); 262 } catch (RemoteException e) { 263 return handleRemoteExceptionFromCarService(e, 0); 264 } 265 } 266 267 /** 268 * Adjust the relative volume in the front vs back of the vehicle cabin. 269 * 270 * @param value in the range -1.0 to 1.0 for fully toward the back through 271 * fully toward the front. 0.0 means evenly balanced. 272 * 273 * @see #setBalanceTowardRight(float) 274 * @hide 275 */ 276 @SystemApi 277 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setFadeTowardFront(float value)278 public void setFadeTowardFront(float value) { 279 try { 280 mService.setFadeTowardFront(value); 281 } catch (RemoteException e) { 282 handleRemoteExceptionFromCarService(e); 283 } 284 } 285 286 /** 287 * Adjust the relative volume on the left vs right side of the vehicle cabin. 288 * 289 * @param value in the range -1.0 to 1.0 for fully toward the left through 290 * fully toward the right. 0.0 means evenly balanced. 291 * 292 * @see #setFadeTowardFront(float) 293 * @hide 294 */ 295 @SystemApi 296 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setBalanceTowardRight(float value)297 public void setBalanceTowardRight(float value) { 298 try { 299 mService.setBalanceTowardRight(value); 300 } catch (RemoteException e) { 301 handleRemoteExceptionFromCarService(e); 302 } 303 } 304 305 /** 306 * Queries the system configuration in order to report the available, non-microphone audio 307 * input devices. 308 * 309 * @return An array of strings representing the available input ports. 310 * Each port is identified by it's "address" tag in the audioPolicyConfiguration xml file. 311 * Empty array if we find nothing. 312 * 313 * @see #createAudioPatch(String, int, int) 314 * @see #releaseAudioPatch(CarAudioPatchHandle) 315 * @hide 316 */ 317 @SystemApi 318 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getExternalSources()319 public @NonNull String[] getExternalSources() { 320 try { 321 return mService.getExternalSources(); 322 } catch (RemoteException e) { 323 handleRemoteExceptionFromCarService(e); 324 return new String[0]; 325 } 326 } 327 328 /** 329 * Given an input port identified by getExternalSources(), request that it's audio signal 330 * be routed below the HAL to the output port associated with the given usage. For example, 331 * The output of a tuner might be routed directly to the output buss associated with 332 * AudioAttributes.USAGE_MEDIA while the tuner is playing. 333 * 334 * @param sourceAddress the input port name obtained from getExternalSources(). 335 * @param usage the type of audio represented by this source (usually USAGE_MEDIA). 336 * @param gainInMillibels How many steps above the minimum value defined for the source port to 337 * set the gain when creating the patch. 338 * This may be used for source balancing without affecting the user 339 * controlled volumes applied to the destination ports. A value of 340 * 0 indicates no gain change is requested. 341 * @return A handle for the created patch which can be used to later remove it. 342 * 343 * @see #getExternalSources() 344 * @see #releaseAudioPatch(CarAudioPatchHandle) 345 * @hide 346 */ 347 @SystemApi 348 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) createAudioPatch(String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels)349 public CarAudioPatchHandle createAudioPatch(String sourceAddress, 350 @AudioAttributes.AttributeUsage int usage, int gainInMillibels) { 351 try { 352 return mService.createAudioPatch(sourceAddress, usage, gainInMillibels); 353 } catch (RemoteException e) { 354 return handleRemoteExceptionFromCarService(e, null); 355 } 356 } 357 358 /** 359 * Removes the association between an input port and an output port identified by the provided 360 * handle. 361 * 362 * @param patch CarAudioPatchHandle returned from createAudioPatch(). 363 * 364 * @see #getExternalSources() 365 * @see #createAudioPatch(String, int, int) 366 * @hide 367 */ 368 @SystemApi 369 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) releaseAudioPatch(CarAudioPatchHandle patch)370 public void releaseAudioPatch(CarAudioPatchHandle patch) { 371 try { 372 mService.releaseAudioPatch(patch); 373 } catch (RemoteException e) { 374 handleRemoteExceptionFromCarService(e); 375 } 376 } 377 378 /** 379 * Gets the count of available volume groups in primary zone. 380 * 381 * @see {@link #getVolumeGroupCount(int)} 382 * @hide 383 */ 384 @SystemApi 385 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupCount()386 public int getVolumeGroupCount() { 387 return getVolumeGroupCount(PRIMARY_AUDIO_ZONE); 388 } 389 390 /** 391 * Gets the count of available volume groups in the system. 392 * 393 * @param zoneId The zone id whois count of volume groups is queried. 394 * @return Count of volume groups 395 * @hide 396 */ 397 @SystemApi 398 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupCount(int zoneId)399 public int getVolumeGroupCount(int zoneId) { 400 try { 401 return mService.getVolumeGroupCount(zoneId); 402 } catch (RemoteException e) { 403 return handleRemoteExceptionFromCarService(e, 0); 404 } 405 } 406 407 /** 408 * Gets the volume group id for a given {@link AudioAttributes} usage in primary zone. 409 * 410 * @see {@link #getVolumeGroupIdForUsage(int, int)} 411 * @hide 412 */ 413 @SystemApi 414 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupIdForUsage(@udioAttributes.AttributeUsage int usage)415 public int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage) { 416 return getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE, usage); 417 } 418 419 /** 420 * Gets the volume group id for a given {@link AudioAttributes} usage. 421 * 422 * @param zoneId The zone id whose volume group is queried. 423 * @param usage The {@link AudioAttributes} usage to get a volume group from. 424 * @return The volume group id where the usage belongs to 425 * @hide 426 */ 427 @SystemApi 428 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)429 public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) { 430 try { 431 return mService.getVolumeGroupIdForUsage(zoneId, usage); 432 } catch (RemoteException e) { 433 return handleRemoteExceptionFromCarService(e, 0); 434 } 435 } 436 437 /** 438 * Gets array of {@link AudioAttributes} usages for a volume group in primary zone. 439 * 440 * @see {@link #getUsagesForVolumeGroupId(int, int)} 441 * @hide 442 */ 443 @SystemApi 444 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getUsagesForVolumeGroupId(int groupId)445 public @NonNull int[] getUsagesForVolumeGroupId(int groupId) { 446 return getUsagesForVolumeGroupId(PRIMARY_AUDIO_ZONE, groupId); 447 } 448 449 /** 450 * Gets array of {@link AudioAttributes} usages for a volume group in a zone. 451 * 452 * @param zoneId The zone id whose volume group is queried. 453 * @param groupId The volume group id whose associated audio usages is returned. 454 * @return Array of {@link AudioAttributes} usages for a given volume group id 455 * @hide 456 */ 457 @SystemApi 458 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getUsagesForVolumeGroupId(int zoneId, int groupId)459 public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) { 460 try { 461 return mService.getUsagesForVolumeGroupId(zoneId, groupId); 462 } catch (RemoteException e) { 463 return handleRemoteExceptionFromCarService(e, new int[0]); 464 } 465 } 466 467 /** 468 * Gets the audio zones currently available 469 * 470 * @return audio zone ids 471 * @hide 472 */ 473 @SystemApi 474 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getAudioZoneIds()475 public @NonNull List<Integer> getAudioZoneIds() { 476 try { 477 int[] zoneIdArray = mService.getAudioZoneIds(); 478 List<Integer> zoneIdList = new ArrayList<Integer>(zoneIdArray.length); 479 for (int zoneIdValue : zoneIdArray) { 480 zoneIdList.add(zoneIdValue); 481 } 482 return zoneIdList; 483 } catch (RemoteException e) { 484 return handleRemoteExceptionFromCarService(e, Collections.emptyList()); 485 } 486 } 487 488 /** 489 * Gets the audio zone id currently mapped to uId, 490 * defaults to PRIMARY_AUDIO_ZONE if no mapping exist 491 * 492 * @param uid The uid to map 493 * @return zone id mapped to uid 494 * @hide 495 */ 496 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getZoneIdForUid(int uid)497 public int getZoneIdForUid(int uid) { 498 try { 499 return mService.getZoneIdForUid(uid); 500 } catch (RemoteException e) { 501 return handleRemoteExceptionFromCarService(e, 0); 502 } 503 } 504 505 /** 506 * Maps the audio zone id to uid 507 * 508 * @param zoneId The audio zone id 509 * @param uid The uid to map 510 * @return true if the uid is successfully mapped 511 * @hide 512 */ 513 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) setZoneIdForUid(int zoneId, int uid)514 public boolean setZoneIdForUid(int zoneId, int uid) { 515 try { 516 return mService.setZoneIdForUid(zoneId, uid); 517 } catch (RemoteException e) { 518 return handleRemoteExceptionFromCarService(e, false); 519 } 520 } 521 522 /** 523 * Clears the current zone mapping of the uid 524 * 525 * @param uid The uid to clear 526 * @return true if the zone was successfully cleared 527 * @hide 528 */ 529 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) clearZoneIdForUid(int uid)530 public boolean clearZoneIdForUid(int uid) { 531 try { 532 return mService.clearZoneIdForUid(uid); 533 } catch (RemoteException e) { 534 return handleRemoteExceptionFromCarService(e, false); 535 } 536 } 537 538 /** 539 * Gets the output device for a given {@link AudioAttributes} usage in zoneId. 540 * 541 * <p><b>Note:</b> To be used for routing to a specific device. Most applications should 542 * use the regular routing mechanism, which is to set audio attribute usage to 543 * an audio track. 544 * 545 * @param zoneId zone id to query for device 546 * @param usage usage where audio is routed 547 * @return Audio device info, returns {@code null} if audio device usage fails to map to 548 * an active audio device. This is different from the using an invalid value for 549 * {@link AudioAttributes} usage. In the latter case the query will fail with a 550 * RuntimeException indicating the issue. 551 * 552 * @hide 553 */ 554 @SystemApi 555 @Nullable 556 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getOutputDeviceForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)557 public AudioDeviceInfo getOutputDeviceForUsage(int zoneId, 558 @AudioAttributes.AttributeUsage int usage) { 559 try { 560 String deviceAddress = mService.getOutputDeviceAddressForUsage(zoneId, usage); 561 if (deviceAddress == null) { 562 return null; 563 } 564 AudioDeviceInfo[] outputDevices = 565 mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 566 for (AudioDeviceInfo info : outputDevices) { 567 if (info.getAddress().equals(deviceAddress)) { 568 return info; 569 } 570 } 571 return null; 572 } catch (RemoteException e) { 573 return handleRemoteExceptionFromCarService(e, null); 574 } 575 } 576 577 /** 578 * Gets the input devices for an audio zone 579 * 580 * @return list of input devices 581 * @hide 582 */ 583 @SystemApi 584 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getInputDevicesForZoneId(int zoneId)585 public @NonNull List<AudioDeviceInfo> getInputDevicesForZoneId(int zoneId) { 586 try { 587 return convertInputDevicesToDeviceInfos( 588 mService.getInputDevicesForZoneId(zoneId), 589 AudioManager.GET_DEVICES_INPUTS); 590 } catch (RemoteException e) { 591 return handleRemoteExceptionFromCarService(e, new ArrayList<>()); 592 } 593 } 594 595 /** @hide */ 596 @Override onCarDisconnected()597 public void onCarDisconnected() { 598 if (mService != null) { 599 unregisterVolumeCallback(); 600 } 601 } 602 603 /** @hide */ CarAudioManager(Car car, IBinder service)604 public CarAudioManager(Car car, IBinder service) { 605 super(car); 606 mService = ICarAudio.Stub.asInterface(service); 607 mAudioManager = getContext().getSystemService(AudioManager.class); 608 mCarVolumeCallbacks = new CopyOnWriteArrayList<>(); 609 } 610 611 /** 612 * Registers a {@link CarVolumeCallback} to receive volume change callbacks 613 * @param callback {@link CarVolumeCallback} instance, can not be null 614 * <p> 615 * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME 616 */ registerCarVolumeCallback(@onNull CarVolumeCallback callback)617 public void registerCarVolumeCallback(@NonNull CarVolumeCallback callback) { 618 Objects.requireNonNull(callback); 619 620 if (mCarVolumeCallbacks.isEmpty()) { 621 registerVolumeCallback(); 622 } 623 624 mCarVolumeCallbacks.add(callback); 625 } 626 627 /** 628 * Unregisters a {@link CarVolumeCallback} from receiving volume change callbacks 629 * @param callback {@link CarVolumeCallback} instance previously registered, can not be null 630 * <p> 631 * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME 632 */ unregisterCarVolumeCallback(@onNull CarVolumeCallback callback)633 public void unregisterCarVolumeCallback(@NonNull CarVolumeCallback callback) { 634 Objects.requireNonNull(callback); 635 if (mCarVolumeCallbacks.remove(callback) && mCarVolumeCallbacks.isEmpty()) { 636 unregisterVolumeCallback(); 637 } 638 } 639 registerVolumeCallback()640 private void registerVolumeCallback() { 641 try { 642 mService.registerVolumeCallback(mCarVolumeCallbackImpl.asBinder()); 643 } catch (RemoteException e) { 644 Log.e(CarLibLog.TAG_CAR, "registerVolumeCallback failed", e); 645 } 646 } 647 unregisterVolumeCallback()648 private void unregisterVolumeCallback() { 649 try { 650 mService.unregisterVolumeCallback(mCarVolumeCallbackImpl.asBinder()); 651 } catch (RemoteException e) { 652 handleRemoteExceptionFromCarService(e); 653 } 654 } 655 convertInputDevicesToDeviceInfos( List<AudioDeviceAttributes> devices, @AudioDeviceRole int flag)656 private List<AudioDeviceInfo> convertInputDevicesToDeviceInfos( 657 List<AudioDeviceAttributes> devices, @AudioDeviceRole int flag) { 658 int addressesSize = devices.size(); 659 Set<String> deviceAddressMap = new HashSet<>(addressesSize); 660 for (int i = 0; i < addressesSize; ++i) { 661 AudioDeviceAttributes device = devices.get(i); 662 deviceAddressMap.add(device.getAddress()); 663 } 664 List<AudioDeviceInfo> deviceInfoList = new ArrayList<>(devices.size()); 665 AudioDeviceInfo[] inputDevices = mAudioManager.getDevices(flag); 666 for (int i = 0; i < inputDevices.length; ++i) { 667 AudioDeviceInfo info = inputDevices[i]; 668 if (info.isSource() && deviceAddressMap.contains(info.getAddress())) { 669 deviceInfoList.add(info); 670 } 671 } 672 return deviceInfoList; 673 } 674 675 /** 676 * Callback interface to receive volume change events in a car. 677 * Extend this class and register it with {@link #registerCarVolumeCallback(CarVolumeCallback)} 678 * and unregister it via {@link #unregisterCarVolumeCallback(CarVolumeCallback)} 679 */ 680 public abstract static class CarVolumeCallback { 681 /** 682 * This is called whenever a group volume is changed. 683 * The changed-to volume index is not included, the caller is encouraged to 684 * get the current group volume index via CarAudioManager. 685 * 686 * @param zoneId Id of the audio zone that volume change happens 687 * @param groupId Id of the volume group that volume is changed 688 * @param flags see {@link android.media.AudioManager} for flag definitions 689 */ onGroupVolumeChanged(int zoneId, int groupId, int flags)690 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {} 691 692 /** 693 * This is called whenever the master mute state is changed. 694 * The changed-to master mute state is not included, the caller is encouraged to 695 * get the current master mute state via AudioManager. 696 * 697 * @param zoneId Id of the audio zone that master mute state change happens 698 * @param flags see {@link android.media.AudioManager} for flag definitions 699 */ onMasterMuteChanged(int zoneId, int flags)700 public void onMasterMuteChanged(int zoneId, int flags) {} 701 } 702 } 703