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 static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE; 20 import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY; 21 22 import android.annotation.CallbackExecutor; 23 import android.annotation.FlaggedApi; 24 import android.annotation.IntDef; 25 import android.annotation.NonNull; 26 import android.annotation.Nullable; 27 import android.annotation.RequiresPermission; 28 import android.annotation.SystemApi; 29 import android.annotation.TestApi; 30 import android.car.Car; 31 import android.car.CarLibLog; 32 import android.car.CarManagerBase; 33 import android.car.CarOccupantZoneManager; 34 import android.car.CarOccupantZoneManager.OccupantZoneInfo; 35 import android.car.feature.Flags; 36 import android.media.AudioAttributes; 37 import android.media.AudioDeviceAttributes; 38 import android.media.AudioDeviceInfo; 39 import android.media.AudioManager; 40 import android.os.Binder; 41 import android.os.Bundle; 42 import android.os.Handler; 43 import android.os.IBinder; 44 import android.os.Looper; 45 import android.os.Message; 46 import android.os.RemoteException; 47 import android.util.Slog; 48 49 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 50 import com.android.car.internal.ICarBase; 51 import com.android.car.internal.annotation.AttributeUsage; 52 import com.android.internal.annotations.GuardedBy; 53 54 import java.lang.annotation.Retention; 55 import java.lang.annotation.RetentionPolicy; 56 import java.util.ArrayList; 57 import java.util.Collections; 58 import java.util.HashSet; 59 import java.util.List; 60 import java.util.Objects; 61 import java.util.Set; 62 import java.util.concurrent.ConcurrentHashMap; 63 import java.util.concurrent.CopyOnWriteArrayList; 64 import java.util.concurrent.Executor; 65 66 /** 67 * APIs for handling audio in a car. 68 * 69 * <p>In a car environment, we introduced the support to turn audio dynamic routing on/off by 70 * setting the "audioUseDynamicRouting" attribute in config.xml</p> 71 * 72 * <p>When audio dynamic routing is enabled:</p> 73 * <ui> 74 * <li>Audio devices are grouped into zones</li> 75 * <li>There is at least one primary zone, and extra secondary zones such as RSE 76 * (Rear Seat Entertainment)</li> 77 * <li> Within each zone, audio devices are grouped into volume groups for volume control</li> 78 * <li> Audio is assigned to an audio device based on its AudioAttributes usage</li> 79 * </ui> 80 * 81 * 82 * <p>When audio dynamic routing is disabled:</p> 83 * <ui> 84 * <li>There is exactly one audio zone, which is the primary zone</li> 85 * <li>Each volume group represents a controllable STREAM_TYPE, same as AudioManager</li> 86 * </ui> 87 */ 88 public final class CarAudioManager extends CarManagerBase { 89 90 private static final String TAG = CarAudioManager.class.getSimpleName(); 91 92 /** 93 * Zone id of the primary audio zone. 94 * @hide 95 */ 96 @SystemApi 97 public static final int PRIMARY_AUDIO_ZONE = 0x0; 98 99 /** 100 * Zone id of the invalid audio zone. 101 * @hide 102 */ 103 @SystemApi 104 public static final int INVALID_AUDIO_ZONE = 0xffffffff; 105 106 /** 107 * This is used to determine if dynamic routing is enabled via 108 * {@link #isAudioFeatureEnabled(int)} 109 */ 110 public static final int AUDIO_FEATURE_DYNAMIC_ROUTING = 1; 111 112 /** 113 * This is used to determine if volume group muting is enabled via 114 * {@link #isAudioFeatureEnabled(int)} 115 * 116 * <p> 117 * If enabled, car volume group muting APIs can be used to mute each volume group, 118 * also car volume group muting changed callback will be called upon group mute changes. If 119 * disabled, car volume will toggle master mute instead. 120 */ 121 public static final int AUDIO_FEATURE_VOLUME_GROUP_MUTING = 2; 122 123 /** 124 * This is used to determine if the OEM audio service is enabled via 125 * {@link #isAudioFeatureEnabled(int)} 126 * 127 * <p>If enabled, car audio focus, car audio volume, and ducking control behaviour can change 128 * as it can be OEM dependent. 129 */ 130 public static final int AUDIO_FEATURE_OEM_AUDIO_SERVICE = 3; 131 132 /** 133 * This is used to determine if volume group events is supported via 134 * {@link #isAudioFeatureEnabled(int)} 135 * 136 * <p>If enabled, the car volume group event callback can be used to receive event changes 137 * to volume, mute, attenuation. 138 * If disabled, the register/unregister APIs will return {@code false}. 139 */ 140 public static final int AUDIO_FEATURE_VOLUME_GROUP_EVENTS = 4; 141 142 /** 143 * This is used to determine if audio mirroring is supported via 144 * {@link #isAudioFeatureEnabled(int)} 145 * 146 * <p>If enabled, audio mirroring can be managed by using the following APIs: 147 * {@link #setAudioZoneMirrorStatusCallback(Executor, AudioZonesMirrorStatusCallback)}, 148 * {@link #clearAudioZonesMirrorStatusCallback()}, {@link #canEnableAudioMirror()}, 149 * {@link #enableMirrorForAudioZones(List)}, {@link #extendAudioMirrorRequest(long, List)}, 150 * {@link #disableAudioMirrorForZone(int)}, {@link #disableAudioMirror(long)}, 151 * {@link #getMirrorAudioZonesForAudioZone(int)}, 152 * {@link #getMirrorAudioZonesForMirrorRequest(long)} 153 */ 154 public static final int AUDIO_FEATURE_AUDIO_MIRRORING = 5; 155 156 /** 157 * This is used to determine if min/max activation volume level is supported via 158 * {@link #isAudioFeatureEnabled(int)} 159 * 160 * <p>If enabled, the volume of the volume group with min/max activation volume setting 161 * will be set to min activation volume or max activation volume if volume during activation 162 * is lower than min activation volume or higher than max activation volume respectively. 163 */ 164 @FlaggedApi(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME) 165 public static final int AUDIO_FEATURE_MIN_MAX_ACTIVATION_VOLUME = 6; 166 167 /** @hide */ 168 @IntDef(flag = false, prefix = "AUDIO_FEATURE", value = { 169 AUDIO_FEATURE_DYNAMIC_ROUTING, 170 AUDIO_FEATURE_VOLUME_GROUP_MUTING, 171 AUDIO_FEATURE_OEM_AUDIO_SERVICE, 172 AUDIO_FEATURE_VOLUME_GROUP_EVENTS, 173 AUDIO_FEATURE_AUDIO_MIRRORING, 174 AUDIO_FEATURE_MIN_MAX_ACTIVATION_VOLUME 175 }) 176 @Retention(RetentionPolicy.SOURCE) 177 public @interface CarAudioFeature {} 178 179 /** 180 * Volume Group ID when volume group not found. 181 * @hide 182 */ 183 public static final int INVALID_VOLUME_GROUP_ID = -1; 184 185 /** 186 * Use to identify if the request from {@link #requestMediaAudioOnPrimaryZone} is invalid 187 * 188 * @hide 189 */ 190 @SystemApi 191 public static final long INVALID_REQUEST_ID = -1; 192 193 /** 194 * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an 195 * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus events, 196 * including {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}. 197 * The requester must hold {@link Car#PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS}; otherwise, 198 * this extra is ignored. 199 * 200 * @hide 201 */ 202 @SystemApi 203 public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS = 204 "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS"; 205 206 /** 207 * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an 208 * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus for the 209 * the zone. If the zone id is not defined: the audio focus request will default to the 210 * currently mapped zone for the requesting uid or {@link CarAudioManager#PRIMARY_AUDIO_ZONE} 211 * if no uid mapping currently exist. 212 * 213 * @hide 214 */ 215 public static final String AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID = 216 "android.car.media.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID"; 217 218 /** 219 * Use to inform media request callbacks about approval of a media request 220 * 221 * @hide 222 */ 223 @SystemApi 224 public static final int AUDIO_REQUEST_STATUS_APPROVED = 1; 225 226 /** 227 * Use to inform media request callbacks about rejection of a media request 228 * 229 * @hide 230 */ 231 @SystemApi 232 public static final int AUDIO_REQUEST_STATUS_REJECTED = 2; 233 234 /** 235 * Use to inform media request callbacks about cancellation of a pending request 236 * 237 * @hide 238 */ 239 @SystemApi 240 public static final int AUDIO_REQUEST_STATUS_CANCELLED = 3; 241 242 /** 243 * Use to inform media request callbacks about the stop of a media request 244 * 245 * @hide 246 */ 247 @SystemApi 248 public static final int AUDIO_REQUEST_STATUS_STOPPED = 4; 249 250 /** @hide */ 251 @IntDef(flag = false, prefix = "AUDIO_REQUEST_STATUS", value = { 252 AUDIO_REQUEST_STATUS_APPROVED, 253 AUDIO_REQUEST_STATUS_REJECTED, 254 AUDIO_REQUEST_STATUS_CANCELLED, 255 AUDIO_REQUEST_STATUS_STOPPED 256 }) 257 @Retention(RetentionPolicy.SOURCE) 258 public @interface MediaAudioRequestStatus {} 259 260 /** 261 * This will be returned by {@link #canEnableAudioMirror()} in case there is an error when 262 * calling the car audio service 263 * 264 * @hide 265 */ 266 @SystemApi 267 public static final int AUDIO_MIRROR_INTERNAL_ERROR = -1; 268 269 /** 270 * This will be returned by {@link #canEnableAudioMirror()} and determines that it is possible 271 * to enable audio mirroring using the {@link #enableMirrorForAudioZones(List)} 272 * 273 * @hide 274 */ 275 @SystemApi 276 public static final int AUDIO_MIRROR_CAN_ENABLE = 1; 277 278 /** 279 * This will be returned by {@link #canEnableAudioMirror()} and determines that it is not 280 * possible to enable audio mirroring using the {@link #enableMirrorForAudioZones(List)}. 281 * This informs that there are no more audio mirror output devices available to route audio. 282 * 283 * @hide 284 */ 285 @SystemApi 286 public static final int AUDIO_MIRROR_OUT_OF_OUTPUT_DEVICES = 2; 287 288 /** @hide */ 289 @IntDef(flag = false, prefix = "AUDIO_MIRROR_", value = { 290 AUDIO_MIRROR_INTERNAL_ERROR, 291 AUDIO_MIRROR_CAN_ENABLE, 292 AUDIO_MIRROR_OUT_OF_OUTPUT_DEVICES, 293 }) 294 @Retention(RetentionPolicy.SOURCE) 295 public @interface AudioMirrorStatus {} 296 297 /** 298 * Status indicating the dynamic audio configurations info have been updated. 299 * 300 * <p><b>Note</b> The list of devices on audio 301 * {@link AudioZoneConfigurationsChangeCallback#onAudioZoneConfigurationsChanged(List, int)}, 302 * will contain all the configuration and each configuration can be perused to find 303 * availability status. For an active configuration becoming disabled due to device 304 * availability, the {@link #CONFIG_STATUS_AUTO_SWITCHED} will be triggered instead. 305 * 306 * <p><b>Note</b> This API will only be triggered when a configuration's active status has 307 * changed due to a device connection state changing. 308 * 309 * @hide 310 */ 311 @SystemApi 312 @FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) 313 public static final int CONFIG_STATUS_CHANGED = 1; 314 315 /** 316 * Status indicating the dynamic audio config info has auto switched. 317 * 318 * <p><b>Note</b> The list of devices on audio 319 * {@link AudioZoneConfigurationsChangeCallback#onAudioZoneConfigurationsChanged(List, int)}, 320 * will contain the previously selected configuration and the newly selected configuration only. 321 * 322 * @hide 323 */ 324 @SystemApi 325 @FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) 326 public static final int CONFIG_STATUS_AUTO_SWITCHED = 2; 327 328 /** @hide */ 329 @IntDef(flag = false, prefix = "CONFIG_STATUS_", value = { 330 CONFIG_STATUS_CHANGED, 331 CONFIG_STATUS_AUTO_SWITCHED, 332 }) 333 @Retention(RetentionPolicy.SOURCE) 334 public @interface AudioConfigStatus {} 335 336 private final ICarAudio mService; 337 private final CopyOnWriteArrayList<CarVolumeCallback> mCarVolumeCallbacks; 338 private final CopyOnWriteArrayList<CarVolumeGroupEventCallbackWrapper> 339 mCarVolumeEventCallbacks = new CopyOnWriteArrayList<>(); 340 private final AudioManager mAudioManager; 341 342 private final EventHandler mEventHandler; 343 344 private final Object mLock = new Object(); 345 @GuardedBy("mLock") 346 private PrimaryZoneMediaAudioRequestCallback mPrimaryZoneMediaAudioRequestCallback; 347 @GuardedBy("mLock") 348 private Executor mPrimaryZoneMediaAudioRequestCallbackExecutor; 349 350 @GuardedBy("mLock") 351 private AudioZonesMirrorStatusCallbackWrapper mAudioZonesMirrorStatusCallbackWrapper; 352 353 @GuardedBy("mLock") 354 private AudioZoneConfigurationsChangeCallbackWrapper mZoneConfigurationsChangeCallbackWrapper; 355 356 private final ConcurrentHashMap<Long, MediaAudioRequestStatusCallbackWrapper> 357 mRequestIdToMediaAudioRequestStatusCallbacks = new ConcurrentHashMap<>(); 358 359 private final IPrimaryZoneMediaAudioRequestCallback mIPrimaryZoneMediaAudioRequestCallback = 360 new IPrimaryZoneMediaAudioRequestCallback.Stub() { 361 @Override 362 public void onRequestMediaOnPrimaryZone(OccupantZoneInfo info, 363 long requestId) { 364 runOnExecutor((callback) -> 365 callback.onRequestMediaOnPrimaryZone(info, requestId)); 366 } 367 368 @Override 369 public void onMediaAudioRequestStatusChanged( 370 @NonNull CarOccupantZoneManager.OccupantZoneInfo info, 371 long requestId, int status) throws RemoteException { 372 runOnExecutor((callback) -> 373 callback.onMediaAudioRequestStatusChanged(info, requestId, status)); 374 } 375 376 private void runOnExecutor(PrimaryZoneMediaAudioRequestCallbackRunner runner) { 377 PrimaryZoneMediaAudioRequestCallback callback; 378 Executor executor; 379 synchronized (mLock) { 380 if (mPrimaryZoneMediaAudioRequestCallbackExecutor == null 381 || mPrimaryZoneMediaAudioRequestCallback == null) { 382 Slog.w(TAG, "Media request removed before change dispatched"); 383 return; 384 } 385 callback = mPrimaryZoneMediaAudioRequestCallback; 386 executor = mPrimaryZoneMediaAudioRequestCallbackExecutor; 387 } 388 389 long identity = Binder.clearCallingIdentity(); 390 try { 391 executor.execute(() -> runner.runOnCallback(callback)); 392 } finally { 393 Binder.restoreCallingIdentity(identity); 394 } 395 } 396 }; 397 398 private interface PrimaryZoneMediaAudioRequestCallbackRunner { runOnCallback(PrimaryZoneMediaAudioRequestCallback callback)399 void runOnCallback(PrimaryZoneMediaAudioRequestCallback callback); 400 } 401 402 private final ICarVolumeCallback mCarVolumeCallbackImpl = 403 new android.car.media.ICarVolumeCallback.Stub() { 404 @Override 405 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) { 406 mEventHandler.dispatchOnGroupVolumeChanged(zoneId, groupId, flags); 407 } 408 409 @Override 410 public void onGroupMuteChanged(int zoneId, int groupId, int flags) { 411 mEventHandler.dispatchOnGroupMuteChanged(zoneId, groupId, flags); 412 } 413 414 @Override 415 public void onMasterMuteChanged(int zoneId, int flags) { 416 mEventHandler.dispatchOnMasterMuteChanged(zoneId, flags); 417 } 418 }; 419 420 private final ICarVolumeEventCallback mCarVolumeEventCallbackImpl = 421 new android.car.media.ICarVolumeEventCallback.Stub() { 422 @Override 423 public void onVolumeGroupEvent(@NonNull List<CarVolumeGroupEvent> events) { 424 mEventHandler.dispatchOnVolumeGroupEvent(events); 425 } 426 427 @Override 428 public void onMasterMuteChanged(int zoneId, int flags) { 429 mEventHandler.dispatchOnMasterMuteChanged(zoneId, flags); 430 } 431 }; 432 433 /** 434 * @return Whether dynamic routing is enabled or not. 435 * 436 * @deprecated use {@link #isAudioFeatureEnabled(int AUDIO_FEATURE_DYNAMIC_ROUTING)} instead. 437 * 438 * @hide 439 */ 440 @TestApi 441 @Deprecated 442 @ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE) isDynamicRoutingEnabled()443 public boolean isDynamicRoutingEnabled() { 444 return isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING); 445 } 446 447 /** 448 * Determines if an audio feature is enabled. 449 * 450 * @param audioFeature audio feature to query, can be any of: 451 * {@link #AUDIO_FEATURE_DYNAMIC_ROUTING}, 452 * {@link #AUDIO_FEATURE_VOLUME_GROUP_MUTING}, 453 * {@link #AUDIO_FEATURE_VOLUME_GROUP_EVENTS}, 454 * {@link #AUDIO_FEATURE_AUDIO_MIRRORING} or 455 * {@link #AUDIO_FEATURE_MIN_MAX_ACTIVATION_VOLUME} 456 * @return Returns {@code true} if the feature is enabled, {@code false} otherwise. 457 */ isAudioFeatureEnabled(@arAudioFeature int audioFeature)458 public boolean isAudioFeatureEnabled(@CarAudioFeature int audioFeature) { 459 try { 460 return mService.isAudioFeatureEnabled(audioFeature); 461 } catch (RemoteException e) { 462 return handleRemoteExceptionFromCarService(e, false); 463 } 464 } 465 466 /** 467 * Sets the volume index for a volume group in primary zone. 468 * 469 * @see #setGroupVolume(int, int, int, int) 470 * @hide 471 */ 472 @SystemApi 473 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setGroupVolume(int groupId, int index, int flags)474 public void setGroupVolume(int groupId, int index, int flags) { 475 setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, index, flags); 476 } 477 478 /** 479 * Sets the volume index for a volume group. 480 * 481 * @param zoneId The zone id whose volume group is affected. 482 * @param groupId The volume group id whose volume index should be set. 483 * @param index The volume index to set. See 484 * {@link #getGroupMaxVolume(int, int)} for the largest valid value. 485 * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI}, 486 * {@link android.media.AudioManager#FLAG_PLAY_SOUND}) 487 * @hide 488 */ 489 @SystemApi 490 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setGroupVolume(int zoneId, int groupId, int index, int flags)491 public void setGroupVolume(int zoneId, int groupId, int index, int flags) { 492 try { 493 mService.setGroupVolume(zoneId, groupId, index, flags); 494 } catch (RemoteException e) { 495 handleRemoteExceptionFromCarService(e); 496 } 497 } 498 499 /** 500 * Returns the maximum volume index for a volume group in primary zone. 501 * 502 * @see #getGroupMaxVolume(int, int) 503 * @hide 504 */ 505 @SystemApi 506 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMaxVolume(int groupId)507 public int getGroupMaxVolume(int groupId) { 508 return getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId); 509 } 510 511 /** 512 * Returns the maximum volume index for a volume group. 513 * 514 * @param zoneId The zone id whose volume group is queried. 515 * @param groupId The volume group id whose maximum volume index is returned. 516 * @return The maximum valid volume index for the given group. 517 * @hide 518 */ 519 @SystemApi 520 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMaxVolume(int zoneId, int groupId)521 public int getGroupMaxVolume(int zoneId, int groupId) { 522 try { 523 return mService.getGroupMaxVolume(zoneId, groupId); 524 } catch (RemoteException e) { 525 return handleRemoteExceptionFromCarService(e, 0); 526 } 527 } 528 529 /** 530 * Returns the minimum volume index for a volume group in primary zone. 531 * 532 * @see #getGroupMinVolume(int, int) 533 * @hide 534 */ 535 @SystemApi 536 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMinVolume(int groupId)537 public int getGroupMinVolume(int groupId) { 538 return getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId); 539 } 540 541 /** 542 * Returns the minimum volume index for a volume group. 543 * 544 * @param zoneId The zone id whose volume group is queried. 545 * @param groupId The volume group id whose minimum volume index is returned. 546 * @return The minimum valid volume index for the given group, non-negative 547 * @hide 548 */ 549 @SystemApi 550 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupMinVolume(int zoneId, int groupId)551 public int getGroupMinVolume(int zoneId, int groupId) { 552 try { 553 return mService.getGroupMinVolume(zoneId, groupId); 554 } catch (RemoteException e) { 555 return handleRemoteExceptionFromCarService(e, 0); 556 } 557 } 558 559 /** 560 * Returns the current volume index for a volume group in primary zone. 561 * 562 * @see #getGroupVolume(int, int) 563 * @hide 564 */ 565 @SystemApi 566 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupVolume(int groupId)567 public int getGroupVolume(int groupId) { 568 return getGroupVolume(PRIMARY_AUDIO_ZONE, groupId); 569 } 570 571 /** 572 * Returns the current volume index for a volume group. 573 * 574 * @param zoneId The zone id whose volume groups is queried. 575 * @param groupId The volume group id whose volume index is returned. 576 * @return The current volume index for the given group. 577 * 578 * @see #getGroupMaxVolume(int, int) 579 * @see #setGroupVolume(int, int, int, int) 580 * @hide 581 */ 582 @SystemApi 583 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getGroupVolume(int zoneId, int groupId)584 public int getGroupVolume(int zoneId, int groupId) { 585 try { 586 return mService.getGroupVolume(zoneId, groupId); 587 } catch (RemoteException e) { 588 return handleRemoteExceptionFromCarService(e, 0); 589 } 590 } 591 592 /** 593 * Adjust the relative volume in the front vs back of the vehicle cabin. 594 * 595 * @param value in the range -1.0 to 1.0 for fully toward the back through 596 * fully toward the front. 0.0 means evenly balanced. 597 * 598 * @throws IllegalArgumentException if {@code value} is less than -1.0 or 599 * greater than 1.0 600 * @see #setBalanceTowardRight(float) 601 * @hide 602 */ 603 @SystemApi 604 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setFadeTowardFront(float value)605 public void setFadeTowardFront(float value) { 606 try { 607 mService.setFadeTowardFront(value); 608 } catch (RemoteException e) { 609 handleRemoteExceptionFromCarService(e); 610 } 611 } 612 613 /** 614 * Adjust the relative volume on the left vs right side of the vehicle cabin. 615 * 616 * @param value in the range -1.0 to 1.0 for fully toward the left through 617 * fully toward the right. 0.0 means evenly balanced. 618 * 619 * @throws IllegalArgumentException if {@code value} is less than -1.0 or 620 * greater than 1.0 621 * @see #setFadeTowardFront(float) 622 * @hide 623 */ 624 @SystemApi 625 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setBalanceTowardRight(float value)626 public void setBalanceTowardRight(float value) { 627 try { 628 mService.setBalanceTowardRight(value); 629 } catch (RemoteException e) { 630 handleRemoteExceptionFromCarService(e); 631 } 632 } 633 634 /** 635 * Queries the system configuration in order to report the available, non-microphone audio 636 * input devices. 637 * 638 * @return An array of strings representing the available input ports. 639 * Each port is identified by it's "address" tag in the audioPolicyConfiguration xml file. 640 * Empty array if we find nothing. 641 * 642 * @see #createAudioPatch(String, int, int) 643 * @see #releaseAudioPatch(CarAudioPatchHandle) 644 * 645 * @deprecated use {@link AudioManager#getDevices(int)} with 646 * {@link AudioManager#GET_DEVICES_INPUTS} instead 647 * @hide 648 */ 649 @SystemApi 650 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 651 @Deprecated 652 @ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE) getExternalSources()653 public @NonNull String[] getExternalSources() { 654 try { 655 return mService.getExternalSources(); 656 } catch (RemoteException e) { 657 handleRemoteExceptionFromCarService(e); 658 return new String[0]; 659 } 660 } 661 662 /** 663 * Given an input port identified by getExternalSources(), request that it's audio signal 664 * be routed below the HAL to the output port associated with the given usage. For example, 665 * The output of a tuner might be routed directly to the output buss associated with 666 * AudioAttributes.USAGE_MEDIA while the tuner is playing. 667 * 668 * @param sourceAddress the input port name obtained from getExternalSources(). 669 * @param usage the type of audio represented by this source (usually USAGE_MEDIA). 670 * @param gainInMillibels How many steps above the minimum value defined for the source port to 671 * set the gain when creating the patch. 672 * This may be used for source balancing without affecting the user 673 * controlled volumes applied to the destination ports. A value of 674 * 0 indicates no gain change is requested. 675 * @return A handle for the created patch which can be used to later remove it. 676 * 677 * @see #getExternalSources() 678 * @see #releaseAudioPatch(CarAudioPatchHandle) 679 * 680 * @deprecated use {@link android.media.HwAudioSource} instead 681 * @hide 682 */ 683 @SystemApi 684 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 685 @Deprecated 686 @ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE) createAudioPatch(String sourceAddress, @AttributeUsage int usage, int gainInMillibels)687 public CarAudioPatchHandle createAudioPatch(String sourceAddress, @AttributeUsage int usage, 688 int gainInMillibels) { 689 try { 690 return mService.createAudioPatch(sourceAddress, usage, gainInMillibels); 691 } catch (RemoteException e) { 692 return handleRemoteExceptionFromCarService(e, null); 693 } 694 } 695 696 /** 697 * Removes the association between an input port and an output port identified by the provided 698 * handle. 699 * 700 * @param patch CarAudioPatchHandle returned from createAudioPatch(). 701 * 702 * @see #getExternalSources() 703 * @see #createAudioPatch(String, int, int) 704 * 705 * @deprecated use {@link android.media.HwAudioSource} instead 706 * @hide 707 */ 708 @SystemApi 709 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 710 @Deprecated 711 @ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE) releaseAudioPatch(CarAudioPatchHandle patch)712 public void releaseAudioPatch(CarAudioPatchHandle patch) { 713 try { 714 mService.releaseAudioPatch(patch); 715 } catch (RemoteException e) { 716 handleRemoteExceptionFromCarService(e); 717 } 718 } 719 720 /** 721 * Gets the count of available volume groups in primary zone. 722 * 723 * @see #getVolumeGroupCount(int) 724 * @hide 725 */ 726 @SystemApi 727 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupCount()728 public int getVolumeGroupCount() { 729 return getVolumeGroupCount(PRIMARY_AUDIO_ZONE); 730 } 731 732 /** 733 * Gets the count of available volume groups in the system. 734 * 735 * @param zoneId The zone id whois count of volume groups is queried. 736 * @return Count of volume groups 737 * @hide 738 */ 739 @SystemApi 740 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupCount(int zoneId)741 public int getVolumeGroupCount(int zoneId) { 742 try { 743 return mService.getVolumeGroupCount(zoneId); 744 } catch (RemoteException e) { 745 return handleRemoteExceptionFromCarService(e, 0); 746 } 747 } 748 749 /** 750 * Gets the volume group id for a given {@link AudioAttributes} usage in primary zone. 751 * 752 * @see #getVolumeGroupIdForUsage(int, int) 753 * @hide 754 */ 755 @SystemApi 756 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupIdForUsage(@ttributeUsage int usage)757 public int getVolumeGroupIdForUsage(@AttributeUsage int usage) { 758 return getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE, usage); 759 } 760 761 /** 762 * Gets the volume group id for a given {@link AudioAttributes} usage. 763 * 764 * @param zoneId The zone id whose volume group is queried. 765 * @param usage The {@link AudioAttributes} usage to get a volume group from. 766 * @return The volume group id where the usage belongs to 767 * @hide 768 */ 769 @SystemApi 770 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getVolumeGroupIdForUsage(int zoneId, @AttributeUsage int usage)771 public int getVolumeGroupIdForUsage(int zoneId, @AttributeUsage int usage) { 772 try { 773 return mService.getVolumeGroupIdForUsage(zoneId, usage); 774 } catch (RemoteException e) { 775 return handleRemoteExceptionFromCarService(e, 0); 776 } 777 } 778 779 /** 780 * Gets array of {@link AudioAttributes} usages for a volume group in primary zone. 781 * 782 * @see #getUsagesForVolumeGroupId(int, int) 783 * @hide 784 */ 785 @SystemApi 786 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getUsagesForVolumeGroupId(int groupId)787 public @NonNull int[] getUsagesForVolumeGroupId(int groupId) { 788 return getUsagesForVolumeGroupId(PRIMARY_AUDIO_ZONE, groupId); 789 } 790 791 /** 792 * Returns the volume group info associated with the zone id and group id. 793 * 794 * <p>The volume information, including mute, blocked, limited state will reflect the state 795 * of the volume group at the time of query. 796 * 797 * @param zoneId zone id for the group to query 798 * @param groupId group id for the group to query 799 * @throws IllegalArgumentException if the audio zone or group id are invalid 800 * 801 * @return the current volume group info 802 * 803 * @hide 804 */ 805 @SystemApi 806 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) 807 @Nullable getVolumeGroupInfo(int zoneId, int groupId)808 public CarVolumeGroupInfo getVolumeGroupInfo(int zoneId, int groupId) { 809 try { 810 return mService.getVolumeGroupInfo(zoneId, groupId); 811 } catch (RemoteException e) { 812 return handleRemoteExceptionFromCarService(e, null); 813 } 814 } 815 816 /** 817 * Returns a list of volume group info associated with the zone id. 818 * 819 * <p>The volume information, including mute, blocked, limited state will reflect the state 820 * of the volume group at the time of query. 821 * 822 * @param zoneId zone id for the group to query 823 * @throws IllegalArgumentException if the audio zone is invalid 824 * 825 * @return all the current volume group info's for the zone id 826 * 827 * @hide 828 */ 829 @SystemApi 830 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) 831 @NonNull getVolumeGroupInfosForZone(int zoneId)832 public List<CarVolumeGroupInfo> getVolumeGroupInfosForZone(int zoneId) { 833 try { 834 return mService.getVolumeGroupInfosForZone(zoneId); 835 } catch (RemoteException e) { 836 return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST); 837 } 838 } 839 840 /** 841 * Returns a list of audio attributes associated with the volume group info. 842 * 843 * @param groupInfo group info to query 844 * @throws NullPointerException if the volume group info is {@code null} 845 * 846 * @return list of audio attributes associated with the volume group info 847 * 848 * @hide 849 */ 850 @SystemApi 851 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) 852 @NonNull getAudioAttributesForVolumeGroup( @onNull CarVolumeGroupInfo groupInfo)853 public List<AudioAttributes> getAudioAttributesForVolumeGroup( 854 @NonNull CarVolumeGroupInfo groupInfo) { 855 try { 856 return mService.getAudioAttributesForVolumeGroup(groupInfo); 857 } catch (RemoteException e) { 858 return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST); 859 } 860 } 861 862 /** 863 * Gets array of {@link AudioAttributes} usages for a volume group in a zone. 864 * 865 * @param zoneId The zone id whose volume group is queried. 866 * @param groupId The volume group id whose associated audio usages is returned. 867 * @return Array of {@link AudioAttributes} usages for a given volume group id 868 * @hide 869 */ 870 @SystemApi 871 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) getUsagesForVolumeGroupId(int zoneId, int groupId)872 public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) { 873 try { 874 return mService.getUsagesForVolumeGroupId(zoneId, groupId); 875 } catch (RemoteException e) { 876 return handleRemoteExceptionFromCarService(e, EMPTY_INT_ARRAY); 877 } 878 } 879 880 /** 881 * Determines if a particular volume group has any audio playback in a zone 882 * 883 * @param zoneId The zone id whose volume group is queried. 884 * @param groupId The volume group id whose associated audio usages is returned. 885 * @return {@code true} if the group has active playback, {@code false} otherwise 886 * 887 * @hide 888 */ 889 @SystemApi 890 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) isPlaybackOnVolumeGroupActive(int zoneId, int groupId)891 public boolean isPlaybackOnVolumeGroupActive(int zoneId, int groupId) { 892 try { 893 return mService.isPlaybackOnVolumeGroupActive(zoneId, groupId); 894 } catch (RemoteException e) { 895 return handleRemoteExceptionFromCarService(e, false); 896 } 897 } 898 899 /** 900 * Returns the current car audio zone configuration info associated with the zone id 901 * 902 * <p>If the car audio configuration does not include zone configurations, a default 903 * configuration consisting current output devices for the zone is returned. 904 * 905 * @param zoneId Zone id for the configuration to query 906 * @return the current car audio zone configuration info, or {@code null} if car audio service 907 * throws {@link RemoteException} 908 * @throws IllegalStateException if dynamic audio routing is not enabled 909 * @throws IllegalArgumentException if the audio zone id is invalid 910 * 911 * @hide 912 */ 913 @SystemApi 914 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 915 @Nullable getCurrentAudioZoneConfigInfo(int zoneId)916 public CarAudioZoneConfigInfo getCurrentAudioZoneConfigInfo(int zoneId) { 917 try { 918 return mService.getCurrentAudioZoneConfigInfo(zoneId); 919 } catch (RemoteException e) { 920 return handleRemoteExceptionFromCarService(e, null); 921 } 922 } 923 924 /** 925 * Returns a list of car audio zone configuration info associated with the zone id 926 * 927 * <p>If the car audio configuration does not include zone configurations, a default 928 * configuration consisting current output devices for each zone is returned. 929 * 930 * <p>There exists exactly one zone configuration in primary zone. 931 * 932 * @param zoneId Zone id for the configuration to query 933 * @return all the car audio zone configuration info for the zone id 934 * @throws IllegalStateException if dynamic audio routing is not enabled 935 * @throws IllegalArgumentException if the audio zone id is invalid 936 * 937 * @hide 938 */ 939 @SystemApi 940 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 941 @NonNull getAudioZoneConfigInfos(int zoneId)942 public List<CarAudioZoneConfigInfo> getAudioZoneConfigInfos(int zoneId) { 943 try { 944 return mService.getAudioZoneConfigInfos(zoneId); 945 } catch (RemoteException e) { 946 return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST); 947 } 948 } 949 950 /** 951 * Switches the car audio zone configuration 952 * 953 * <p>To receive the volume group change after configuration is changed, a 954 * {@link CarVolumeGroupEventCallback} must be registered through 955 * {@link #registerCarVolumeGroupEventCallback(Executor, CarVolumeGroupEventCallback)} first. 956 * 957 * @param zoneConfig Audio zone configuration to switch to 958 * @param executor Executor on which callback will be invoked 959 * @param callback Callback that will report the result of switching car audio zone 960 * configuration 961 * @throws NullPointerException if either executor or callback are {@code null} 962 * @throws IllegalStateException if dynamic audio routing is not enabled 963 * @throws IllegalStateException if no user is assigned to the audio zone 964 * @throws IllegalStateException if the audio zone is currently in a mirroring configuration 965 * or sharing audio with primary audio zone 966 * @throws IllegalArgumentException if the audio zone configuration is invalid 967 * 968 * @hide 969 */ 970 @SystemApi 971 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) switchAudioZoneToConfig(@onNull CarAudioZoneConfigInfo zoneConfig, @NonNull @CallbackExecutor Executor executor, @NonNull SwitchAudioZoneConfigCallback callback)972 public void switchAudioZoneToConfig(@NonNull CarAudioZoneConfigInfo zoneConfig, 973 @NonNull @CallbackExecutor Executor executor, 974 @NonNull SwitchAudioZoneConfigCallback callback) { 975 Objects.requireNonNull(zoneConfig, "Audio zone configuration can not be null"); 976 Objects.requireNonNull(executor, "Executor can not be null"); 977 Objects.requireNonNull(callback, 978 "Switching audio zone configuration result callback can not be null"); 979 SwitchAudioZoneConfigCallbackWrapper wrapper = 980 new SwitchAudioZoneConfigCallbackWrapper(executor, callback); 981 try { 982 mService.switchZoneToConfig(zoneConfig, wrapper); 983 } catch (RemoteException e) { 984 handleRemoteExceptionFromCarService(e); 985 } 986 } 987 988 /** 989 * Sets the audio zone configurations change callback 990 * 991 * <p><b>Note:</b> There are two types on configuration changes. 992 * 993 * <p>Config active status changes, signaled by status {@link #CONFIG_STATUS_CHANGED}, 994 * represent changes to the configurations due to a configuration becoming active or inactive as 995 * a result of a dynamic device being connected or disconnected respectively. 996 * 997 * <p>Config auto switch changes, signaled by status {@link #CONFIG_STATUS_AUTO_SWITCHED}, 998 * represent changes to the configurations due a currently selected configuration becoming 999 * inactive as a result of a dynamic device being disconnected. 1000 * 1001 * @param executor Executor on which callback will be invoked 1002 * @param callback Callback that will be triggered on audio configuration changes 1003 * @return {@code true} if the callback is successfully registered, {@code false} otherwise 1004 * @throws NullPointerException if either executor or callback are {@code null} 1005 * @throws IllegalStateException if dynamic audio routing is not enabled 1006 * @throws IllegalStateException if there is a callback already set 1007 * 1008 * @hide 1009 */ 1010 @SystemApi 1011 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 1012 @FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) setAudioZoneConfigsChangeCallback(@onNull @allbackExecutor Executor executor, @NonNull AudioZoneConfigurationsChangeCallback callback)1013 public boolean setAudioZoneConfigsChangeCallback(@NonNull @CallbackExecutor Executor executor, 1014 @NonNull AudioZoneConfigurationsChangeCallback callback) { 1015 Objects.requireNonNull(executor, "Executor can not be null"); 1016 Objects.requireNonNull(callback, "Audio zone configs change callback can not be null"); 1017 1018 synchronized (mLock) { 1019 if (mZoneConfigurationsChangeCallbackWrapper != null) { 1020 throw new IllegalStateException("Audio zone configs change " 1021 + "callback is already set"); 1022 } 1023 } 1024 AudioZoneConfigurationsChangeCallbackWrapper wrapper = 1025 new AudioZoneConfigurationsChangeCallbackWrapper(executor, callback); 1026 1027 boolean succeeded; 1028 try { 1029 succeeded = mService.registerAudioZoneConfigsChangeCallback(wrapper); 1030 } catch (RemoteException e) { 1031 return handleRemoteExceptionFromCarService(e, false); 1032 } 1033 1034 if (!succeeded) { 1035 return false; 1036 } 1037 boolean error; 1038 synchronized (mLock) { 1039 error = mZoneConfigurationsChangeCallbackWrapper != null; 1040 if (!error) { 1041 mZoneConfigurationsChangeCallbackWrapper = wrapper; 1042 } 1043 } 1044 1045 // In case there was an error, unregister the listener and throw an exception 1046 if (error) { 1047 try { 1048 mService.unregisterAudioZoneConfigsChangeCallback(wrapper); 1049 } catch (RemoteException e) { 1050 handleRemoteExceptionFromCarService(e); 1051 } 1052 1053 throw new IllegalStateException("Audio zone config change callback is already set"); 1054 } 1055 return true; 1056 } 1057 1058 /** 1059 * Clears the currently set {@link AudioZoneConfigurationsChangeCallback} 1060 * 1061 * @throws IllegalStateException if dynamic audio routing is not enabled 1062 * 1063 * @hide 1064 */ 1065 @SystemApi 1066 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 1067 @FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) clearAudioZoneConfigsCallback()1068 public void clearAudioZoneConfigsCallback() { 1069 AudioZoneConfigurationsChangeCallbackWrapper wrapper; 1070 1071 synchronized (mLock) { 1072 if (mZoneConfigurationsChangeCallbackWrapper == null) { 1073 Slog.w(TAG, "Audio zone configs callback was already cleared"); 1074 return; 1075 } 1076 wrapper = mZoneConfigurationsChangeCallbackWrapper; 1077 mZoneConfigurationsChangeCallbackWrapper = null; 1078 } 1079 1080 try { 1081 mService.unregisterAudioZoneConfigsChangeCallback(wrapper); 1082 } catch (RemoteException e) { 1083 handleRemoteExceptionFromCarService(e); 1084 } 1085 } 1086 1087 /** 1088 * Gets the audio zones currently available 1089 * 1090 * @return audio zone ids 1091 * @hide 1092 */ 1093 @SystemApi 1094 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getAudioZoneIds()1095 public @NonNull List<Integer> getAudioZoneIds() { 1096 try { 1097 return asList(mService.getAudioZoneIds()); 1098 } catch (RemoteException e) { 1099 return handleRemoteExceptionFromCarService(e, Collections.emptyList()); 1100 } 1101 } 1102 1103 /** 1104 * Gets the audio zone id currently mapped to uId, 1105 * defaults to PRIMARY_AUDIO_ZONE if no mapping exist 1106 * 1107 * @param uid The uid to map 1108 * @return zone id mapped to uid 1109 * @hide 1110 */ 1111 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getZoneIdForUid(int uid)1112 public int getZoneIdForUid(int uid) { 1113 try { 1114 return mService.getZoneIdForUid(uid); 1115 } catch (RemoteException e) { 1116 return handleRemoteExceptionFromCarService(e, 0); 1117 } 1118 } 1119 1120 /** 1121 * Maps the audio zone id to uid 1122 * 1123 * @param zoneId The audio zone id 1124 * @param uid The uid to map 1125 * @return true if the uid is successfully mapped 1126 * @hide 1127 */ 1128 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) setZoneIdForUid(int zoneId, int uid)1129 public boolean setZoneIdForUid(int zoneId, int uid) { 1130 try { 1131 return mService.setZoneIdForUid(zoneId, uid); 1132 } catch (RemoteException e) { 1133 return handleRemoteExceptionFromCarService(e, false); 1134 } 1135 } 1136 1137 /** 1138 * Clears the current zone mapping of the uid 1139 * 1140 * @param uid The uid to clear 1141 * @return true if the zone was successfully cleared 1142 * 1143 * @hide 1144 */ 1145 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) clearZoneIdForUid(int uid)1146 public boolean clearZoneIdForUid(int uid) { 1147 try { 1148 return mService.clearZoneIdForUid(uid); 1149 } catch (RemoteException e) { 1150 return handleRemoteExceptionFromCarService(e, false); 1151 } 1152 } 1153 1154 /** 1155 * Sets a {@link PrimaryZoneMediaAudioRequestCallback} to listen for request to play 1156 * media audio in primary audio zone 1157 * 1158 * @param executor Executor on which callback will be invoked 1159 * @param callback Media audio request callback to monitor for audio requests 1160 * @return {@code true} if the callback is successfully registered, {@code false} otherwise 1161 * @throws NullPointerException if either executor or callback are {@code null} 1162 * @throws IllegalStateException if dynamic audio routing is not enabled 1163 * @throws IllegalStateException if there is a callback already set 1164 * 1165 * @hide 1166 */ 1167 @SystemApi 1168 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) setPrimaryZoneMediaAudioRequestCallback( @onNull @allbackExecutor Executor executor, @NonNull PrimaryZoneMediaAudioRequestCallback callback)1169 public boolean setPrimaryZoneMediaAudioRequestCallback( 1170 @NonNull @CallbackExecutor Executor executor, 1171 @NonNull PrimaryZoneMediaAudioRequestCallback callback) { 1172 Objects.requireNonNull(executor, "Executor can not be null"); 1173 Objects.requireNonNull(callback, "Audio media request callback can not be null"); 1174 synchronized (mLock) { 1175 if (mPrimaryZoneMediaAudioRequestCallback != null) { 1176 throw new IllegalStateException("Primary zone media audio request is already set"); 1177 } 1178 } 1179 1180 try { 1181 if (!mService.registerPrimaryZoneMediaAudioRequestCallback( 1182 mIPrimaryZoneMediaAudioRequestCallback)) { 1183 return false; 1184 } 1185 } catch (RemoteException e) { 1186 return handleRemoteExceptionFromCarService(e, /* returnValue= */ false); 1187 } 1188 1189 synchronized (mLock) { 1190 mPrimaryZoneMediaAudioRequestCallback = callback; 1191 mPrimaryZoneMediaAudioRequestCallbackExecutor = executor; 1192 } 1193 1194 return true; 1195 } 1196 1197 /** 1198 * Clears the currently set {@link PrimaryZoneMediaAudioRequestCallback} 1199 * 1200 * @throws IllegalStateException if dynamic audio routing is not enabled 1201 * 1202 * @hide 1203 */ 1204 @SystemApi 1205 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) clearPrimaryZoneMediaAudioRequestCallback()1206 public void clearPrimaryZoneMediaAudioRequestCallback() { 1207 synchronized (mLock) { 1208 if (mPrimaryZoneMediaAudioRequestCallback == null) { 1209 return; 1210 } 1211 } 1212 1213 try { 1214 mService.unregisterPrimaryZoneMediaAudioRequestCallback( 1215 mIPrimaryZoneMediaAudioRequestCallback); 1216 } catch (RemoteException e) { 1217 handleRemoteExceptionFromCarService(e); 1218 } 1219 1220 synchronized (mLock) { 1221 mPrimaryZoneMediaAudioRequestCallback = null; 1222 mPrimaryZoneMediaAudioRequestCallbackExecutor = null; 1223 } 1224 } 1225 1226 /** 1227 * Cancels a request set by {@link #requestMediaAudioOnPrimaryZone} 1228 * 1229 * @param requestId Request id to cancel 1230 * @return {@code true} if request is successfully cancelled 1231 * @throws IllegalStateException if dynamic audio routing is not enabled 1232 * 1233 * @hide 1234 */ 1235 @SystemApi 1236 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) cancelMediaAudioOnPrimaryZone(long requestId)1237 public boolean cancelMediaAudioOnPrimaryZone(long requestId) { 1238 try { 1239 if (removeMediaRequestCallback(requestId)) { 1240 return mService.cancelMediaAudioOnPrimaryZone(requestId); 1241 } 1242 } catch (RemoteException e) { 1243 return handleRemoteExceptionFromCarService(e, /* returnValue= */ false); 1244 } 1245 1246 return true; 1247 } 1248 removeMediaRequestCallback(long requestId)1249 private boolean removeMediaRequestCallback(long requestId) { 1250 return mRequestIdToMediaAudioRequestStatusCallbacks.remove(requestId) != null; 1251 } 1252 1253 /** 1254 * Requests to play audio in primary zone with information contained in {@code request} 1255 * 1256 * @param info Occupant zone info whose media audio should be shared to primary zone 1257 * @param executor Executor on which callback will be invoked 1258 * @param callback Callback that will report the status changes of the request 1259 * @return returns a valid request id if successful or {@code INVALID_REQUEST_ID} otherwise 1260 * @throws NullPointerException if any of info, executor, or callback parameters are 1261 * {@code null} 1262 * @throws IllegalStateException if dynamic audio routing is not enabled, or if audio mirroring 1263 * is currently enabled for the audio zone owned by the occupant as configured by 1264 * {@link #enableMirrorForAudioZones(List)} 1265 * 1266 * @hide 1267 */ 1268 @SystemApi 1269 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) requestMediaAudioOnPrimaryZone(@onNull OccupantZoneInfo info, @NonNull @CallbackExecutor Executor executor, @NonNull MediaAudioRequestStatusCallback callback)1270 public long requestMediaAudioOnPrimaryZone(@NonNull OccupantZoneInfo info, 1271 @NonNull @CallbackExecutor Executor executor, 1272 @NonNull MediaAudioRequestStatusCallback callback) { 1273 Objects.requireNonNull(info, "Occupant zone info can not be null"); 1274 Objects.requireNonNull(executor, "Executor can not be null"); 1275 Objects.requireNonNull(callback, "Media audio request status callback can not be null"); 1276 1277 MediaAudioRequestStatusCallbackWrapper wrapper = 1278 new MediaAudioRequestStatusCallbackWrapper(executor, callback); 1279 1280 long requestId; 1281 try { 1282 requestId = mService.requestMediaAudioOnPrimaryZone(wrapper, info); 1283 } catch (RemoteException e) { 1284 return handleRemoteExceptionFromCarService(e, INVALID_REQUEST_ID); 1285 } 1286 1287 if (requestId == INVALID_REQUEST_ID) { 1288 return requestId; 1289 } 1290 1291 mRequestIdToMediaAudioRequestStatusCallbacks.put(requestId, wrapper); 1292 return requestId; 1293 } 1294 1295 /** 1296 * Allow/rejects audio to play for a request 1297 * {@link #requestMediaAudioOnPrimaryZone(OccupantZoneInfo, Executor, 1298 * MediaAudioRequestStatusCallback)} 1299 * 1300 * @param requestId Request id to approve 1301 * @param allow Boolean indicating to allow or reject, {@code true} to allow audio 1302 * playback on primary zone, {@code false} otherwise 1303 * @return {@code false} if media is not successfully allowed/rejected for the request, 1304 * including the case when the request id is {@link #INVALID_REQUEST_ID} 1305 * @throws IllegalStateException if no {@link PrimaryZoneMediaAudioRequestCallback} is 1306 * registered prior to calling this method. 1307 * @throws IllegalStateException if dynamic audio routing is not enabled, or if audio mirroring 1308 * is currently enabled for the audio zone owned by the occupant as configured by 1309 * {@link #enableMirrorForAudioZones(List)} 1310 * 1311 * @hide 1312 */ 1313 @SystemApi 1314 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) allowMediaAudioOnPrimaryZone(long requestId, boolean allow)1315 public boolean allowMediaAudioOnPrimaryZone(long requestId, boolean allow) { 1316 synchronized (mLock) { 1317 if (mPrimaryZoneMediaAudioRequestCallback == null) { 1318 throw new IllegalStateException("Primary zone media audio request callback must be " 1319 + "registered to allow/reject playback"); 1320 } 1321 } 1322 1323 try { 1324 return mService.allowMediaAudioOnPrimaryZone( 1325 mIPrimaryZoneMediaAudioRequestCallback.asBinder(), requestId, allow); 1326 } catch (RemoteException e) { 1327 return handleRemoteExceptionFromCarService(e, /* returnValue= */ false); 1328 } 1329 } 1330 1331 /** 1332 * Resets the media audio playback in primary zone from occupant 1333 * 1334 * @param info Occupant's audio to reset in primary zone 1335 * @return {@code true} if audio is successfully reset, {@code false} otherwise including case 1336 * where audio is not currently assigned 1337 * @throws IllegalStateException if dynamic audio routing is not enabled 1338 * 1339 * @hide 1340 */ 1341 @SystemApi 1342 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) resetMediaAudioOnPrimaryZone(@onNull OccupantZoneInfo info)1343 public boolean resetMediaAudioOnPrimaryZone(@NonNull OccupantZoneInfo info) { 1344 try { 1345 return mService.resetMediaAudioOnPrimaryZone(info); 1346 } catch (RemoteException e) { 1347 return handleRemoteExceptionFromCarService(e, /* returnValue= */ false); 1348 } 1349 } 1350 1351 /** 1352 * Determines if audio from occupant is allowed in primary zone 1353 * 1354 * @param info Occupant zone info to query 1355 * @return {@code true} if audio playback from occupant is allowed in primary zone 1356 * @throws IllegalStateException if dynamic audio routing is not enabled 1357 * 1358 * @hide 1359 */ 1360 @SystemApi 1361 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) isMediaAudioAllowedInPrimaryZone(@onNull OccupantZoneInfo info)1362 public boolean isMediaAudioAllowedInPrimaryZone(@NonNull OccupantZoneInfo info) { 1363 try { 1364 return mService.isMediaAudioAllowedInPrimaryZone(info); 1365 } catch (RemoteException e) { 1366 return handleRemoteExceptionFromCarService(e, /* returnValue= */ false); 1367 } 1368 } 1369 1370 /** 1371 * Registers audio mirror status callback 1372 * 1373 * @param executor Executor on which the callback will be invoked 1374 * @param callback Callback to inform about audio mirror status changes 1375 * @return {@code true} if audio zones mirror status is set successfully, or {@code false} 1376 * otherwise 1377 * @throws NullPointerException if {@link AudioZonesMirrorStatusCallback} or {@link Executor} 1378 * passed in are {@code null} 1379 * @throws IllegalStateException if dynamic audio routing is not enabled, also if 1380 * there is a callback already set 1381 * @throws IllegalStateException if audio mirroring feature is disabled, which can be verified 1382 * using {@link #isAudioFeatureEnabled(int)} with the {@link #AUDIO_FEATURE_AUDIO_MIRRORING} 1383 * feature flag 1384 * 1385 * @hide 1386 */ 1387 @SystemApi 1388 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) setAudioZoneMirrorStatusCallback(@onNull @allbackExecutor Executor executor, @NonNull AudioZonesMirrorStatusCallback callback)1389 public boolean setAudioZoneMirrorStatusCallback(@NonNull @CallbackExecutor Executor executor, 1390 @NonNull AudioZonesMirrorStatusCallback callback) { 1391 Objects.requireNonNull(executor, "Executor can not be null"); 1392 Objects.requireNonNull(callback, "Audio zones mirror status callback can not be null"); 1393 1394 synchronized (mLock) { 1395 if (mAudioZonesMirrorStatusCallbackWrapper != null) { 1396 throw new IllegalStateException("Audio zones mirror status " 1397 + "callback is already set"); 1398 } 1399 } 1400 AudioZonesMirrorStatusCallbackWrapper wrapper = 1401 new AudioZonesMirrorStatusCallbackWrapper(executor, callback); 1402 1403 boolean succeeded; 1404 try { 1405 succeeded = mService.registerAudioZonesMirrorStatusCallback(wrapper); 1406 } catch (RemoteException e) { 1407 return handleRemoteExceptionFromCarService(e, false); 1408 } 1409 1410 if (!succeeded) { 1411 return false; 1412 } 1413 boolean error; 1414 synchronized (mLock) { 1415 // Unless there is a race condition mAudioZonesMirrorStatusCallbackWrapper 1416 // should not be set 1417 error = mAudioZonesMirrorStatusCallbackWrapper != null; 1418 if (!error) { 1419 mAudioZonesMirrorStatusCallbackWrapper = wrapper; 1420 } 1421 } 1422 1423 // In case there was an error, unregister the listener and throw an exception 1424 if (error) { 1425 try { 1426 mService.unregisterAudioZonesMirrorStatusCallback(wrapper); 1427 } catch (RemoteException e) { 1428 handleRemoteExceptionFromCarService(e); 1429 } 1430 1431 throw new IllegalStateException("Audio zones mirror status callback is already set"); 1432 } 1433 return true; 1434 } 1435 1436 /** 1437 * Clears the currently set {@link AudioZonesMirrorStatusCallback} 1438 * 1439 * @throws IllegalStateException if dynamic audio routing is not enabled 1440 * @throws IllegalStateException if audio mirroring feature is disabled, which can be verified 1441 * using {@link #isAudioFeatureEnabled(int)} with the {@link #AUDIO_FEATURE_AUDIO_MIRRORING} 1442 * feature flag 1443 * 1444 * @hide 1445 */ 1446 @SystemApi 1447 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) clearAudioZonesMirrorStatusCallback()1448 public void clearAudioZonesMirrorStatusCallback() { 1449 AudioZonesMirrorStatusCallbackWrapper wrapper; 1450 1451 synchronized (mLock) { 1452 if (mAudioZonesMirrorStatusCallbackWrapper == null) { 1453 return; 1454 } 1455 wrapper = mAudioZonesMirrorStatusCallbackWrapper; 1456 mAudioZonesMirrorStatusCallbackWrapper = null; 1457 } 1458 1459 try { 1460 mService.unregisterAudioZonesMirrorStatusCallback(wrapper); 1461 } catch (RemoteException e) { 1462 handleRemoteExceptionFromCarService(e); 1463 } 1464 } 1465 1466 /** 1467 * Determines if it is possible to enable audio mirror 1468 * 1469 * @return returns status to determine if it is possible to enable audio mirror using the 1470 * {@link #enableMirrorForAudioZones(List)} API, if audio mirror can be enabled this will 1471 * return {@link #AUDIO_MIRROR_CAN_ENABLE}, or {@link #AUDIO_MIRROR_OUT_OF_OUTPUT_DEVICES} if 1472 * there are no more output devices currently available to mirror. 1473 * {@link #AUDIO_MIRROR_INTERNAL_ERROR} can also be returned in case there is an error when 1474 * communicating with the car audio service 1475 * @throws IllegalStateException if audio mirroring feature is disabled, which can be verified 1476 * using {@link #isAudioFeatureEnabled(int)} with the {@link #AUDIO_FEATURE_AUDIO_MIRRORING} 1477 * feature flag 1478 * 1479 * @hide 1480 */ 1481 @SystemApi 1482 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) canEnableAudioMirror()1483 public @AudioMirrorStatus int canEnableAudioMirror() { 1484 try { 1485 return mService.canEnableAudioMirror(); 1486 } catch (RemoteException e) { 1487 return handleRemoteExceptionFromCarService(e, AUDIO_MIRROR_INTERNAL_ERROR); 1488 } 1489 } 1490 1491 /** 1492 * Enables audio mirror for a set of audio zones 1493 * 1494 * <p><b>Note:</b> The results will be notified in the {@link AudioZonesMirrorStatusCallback} 1495 * set via {@link #setAudioZoneMirrorStatusCallback(Executor, AudioZonesMirrorStatusCallback)} 1496 * 1497 * @param audioZonesToMirror List of audio zones that should have audio mirror enabled, 1498 * a minimum of two audio zones are needed to enable mirroring 1499 * @return returns a valid mirror request id if successful or {@code INVALID_REQUEST_ID} 1500 * otherwise 1501 * @throws NullPointerException if the audio mirror list is {@code null} 1502 * @throws IllegalArgumentException if the audio mirror list size is less than 2, if a zone id 1503 * repeats within the list, or if the list contains the {@link #PRIMARY_AUDIO_ZONE} 1504 * @throws IllegalStateException if dynamic audio routing is not enabled, or there is an 1505 * attempt to merge zones from two different mirroring request, or any of the zone ids 1506 * are currently sharing audio to primary zone as allowed via 1507 * {@link #allowMediaAudioOnPrimaryZone(long, boolean)} 1508 * @throws IllegalStateException if audio mirroring feature is disabled, which can be verified 1509 * using {@link #isAudioFeatureEnabled(int)} with the {@link #AUDIO_FEATURE_AUDIO_MIRRORING} 1510 * feature flag 1511 * 1512 * @hide 1513 */ 1514 @SystemApi 1515 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) enableMirrorForAudioZones(@onNull List<Integer> audioZonesToMirror)1516 public long enableMirrorForAudioZones(@NonNull List<Integer> audioZonesToMirror) { 1517 Objects.requireNonNull(audioZonesToMirror, "Audio zones to mirror should not be null"); 1518 1519 try { 1520 return mService.enableMirrorForAudioZones(toIntArray(audioZonesToMirror)); 1521 } catch (RemoteException e) { 1522 return handleRemoteExceptionFromCarService(e, INVALID_REQUEST_ID); 1523 } 1524 } 1525 1526 /** 1527 * Extends the audio zone mirroring request by appending new zones to the mirroring 1528 * configuration. The zones previously mirroring in the audio mirroring configuration, will 1529 * continue to mirror and the mirroring will be further extended to the new zones. 1530 * 1531 * <p><b>Note:</b> The results will be notified in the {@link AudioZonesMirrorStatusCallback} 1532 * set via {@link #setAudioZoneMirrorStatusCallback(Executor, AudioZonesMirrorStatusCallback)}. 1533 * For example, to further extend a mirroring request currently containing zones 1 and 2, with 1534 * a new zone (3) Simply call the API with zone 3 in the list, after the completion of audio 1535 * mirroring extension, zones 1, 2, and 3 will now have mirroring enabled. 1536 * 1537 * @param audioZonesToMirror List of audio zones that will be added to the mirroring request 1538 * @param mirrorId Audio mirroring request to expand with more audio zones 1539 * @throws NullPointerException if the audio mirror list is {@code null} 1540 * @throws IllegalArgumentException if a zone id repeats within the list, or if the list 1541 * contains the {@link #PRIMARY_AUDIO_ZONE}, or if the request id to expand is no longer valid 1542 * @throws IllegalStateException if dynamic audio routing is not enabled, or there is an 1543 * attempt to merge zones from two different mirroring request, or any of the zone ids 1544 * are currently sharing audio to primary zone as allowed via 1545 * {@link #allowMediaAudioOnPrimaryZone(long, boolean)} 1546 * @throws IllegalStateException if audio mirroring feature is disabled, which can be verified 1547 * using {@link #isAudioFeatureEnabled(int)} with the {@link #AUDIO_FEATURE_AUDIO_MIRRORING} 1548 * feature flag 1549 * 1550 * @hide 1551 */ 1552 @SystemApi 1553 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) extendAudioMirrorRequest(long mirrorId, @NonNull List<Integer> audioZonesToMirror)1554 public void extendAudioMirrorRequest(long mirrorId, @NonNull List<Integer> audioZonesToMirror) { 1555 Objects.requireNonNull(audioZonesToMirror, "Audio zones to mirror should not be null"); 1556 1557 try { 1558 mService.extendAudioMirrorRequest(mirrorId, toIntArray(audioZonesToMirror)); 1559 } catch (RemoteException e) { 1560 handleRemoteExceptionFromCarService(e); 1561 } 1562 } 1563 1564 /** 1565 * Disables audio mirror for a particular audio zone 1566 * 1567 * <p><b>Note:</b> The results will be notified in the {@link AudioZonesMirrorStatusCallback} 1568 * set via {@link #setAudioZoneMirrorStatusCallback(Executor, AudioZonesMirrorStatusCallback)}. 1569 * The results will contain the information for the audio zones whose mirror was cancelled. 1570 * For example, if the mirror configuration only has two zones, mirroring will be undone for 1571 * both zones and the callback will have both zones. On the other hand, if the mirroring 1572 * configuration contains three zones, then this API will only cancel mirroring for one zone 1573 * and the other two zone will continue mirroring. In this case, the callback will only have 1574 * information about the cancelled zone 1575 * 1576 * @param zoneId Zone id where audio mirror should be disabled 1577 * @throws IllegalArgumentException if the zoneId is invalid 1578 * @throws IllegalStateException if dynamic audio routing is not enabled 1579 * @throws IllegalStateException if audio mirroring feature is disabled, which can be verified 1580 * using {@link #isAudioFeatureEnabled(int)} with the {@link #AUDIO_FEATURE_AUDIO_MIRRORING} 1581 * feature flag 1582 * 1583 * @hide 1584 */ 1585 @SystemApi 1586 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) disableAudioMirrorForZone(int zoneId)1587 public void disableAudioMirrorForZone(int zoneId) { 1588 try { 1589 mService.disableAudioMirrorForZone(zoneId); 1590 } catch (RemoteException e) { 1591 handleRemoteExceptionFromCarService(e); 1592 } 1593 } 1594 1595 /** 1596 * Disables audio mirror for all the zones mirroring in a particular request 1597 * 1598 * <p><b>Note:</b> The results will be notified in the {@link AudioZonesMirrorStatusCallback} 1599 * set via {@link #setAudioZoneMirrorStatusCallback(Executor, AudioZonesMirrorStatusCallback)} 1600 * 1601 * @param mirrorId Whose audio mirroring should be disabled as obtained via 1602 * {@link #enableMirrorForAudioZones(List)} 1603 * @throws IllegalArgumentException if the request id is no longer valid 1604 * @throws IllegalStateException if dynamic audio routing is not enabled 1605 * @throws IllegalStateException if audio mirroring feature is disabled, which can be verified 1606 * using {@link #isAudioFeatureEnabled(int)} with the {@link #AUDIO_FEATURE_AUDIO_MIRRORING} 1607 * feature flag 1608 * 1609 * @hide 1610 */ 1611 @SystemApi 1612 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) disableAudioMirror(long mirrorId)1613 public void disableAudioMirror(long mirrorId) { 1614 try { 1615 mService.disableAudioMirror(mirrorId); 1616 } catch (RemoteException e) { 1617 handleRemoteExceptionFromCarService(e); 1618 } 1619 } 1620 1621 /** 1622 * Determines the current mirror configuration for an audio zone as set by 1623 * {@link #enableMirrorForAudioZones(List)} or extended via 1624 * {@link #extendAudioMirrorRequest(long, List)} 1625 * 1626 * @param zoneId The audio zone id where mirror audio should be queried 1627 * @return A list of audio zones where the queried audio zone is mirroring or empty if the 1628 * audio zone is not mirroring with any other audio zone. The list of zones will contain the 1629 * queried zone if audio mirroring is enabled for that zone. 1630 * @throws IllegalArgumentException if the audio zone id is invalid 1631 * @throws IllegalStateException if dynamic audio routing is not enabled 1632 * @throws IllegalStateException if audio mirroring feature is disabled, which can be verified 1633 * using {@link #isAudioFeatureEnabled(int)} with the {@link #AUDIO_FEATURE_AUDIO_MIRRORING} 1634 * feature flag 1635 * 1636 * @hide 1637 */ 1638 @SystemApi 1639 @NonNull 1640 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getMirrorAudioZonesForAudioZone(int zoneId)1641 public List<Integer> getMirrorAudioZonesForAudioZone(int zoneId) { 1642 try { 1643 return asList(mService.getMirrorAudioZonesForAudioZone(zoneId)); 1644 } catch (RemoteException e) { 1645 return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST); 1646 } 1647 } 1648 1649 /** 1650 * Determines the current mirror configuration for a mirror id 1651 * 1652 * @param mirrorId The request id that should be queried 1653 * @return A list of audio zones where the queried audio zone is mirroring or empty if the 1654 * request id is no longer valid. 1655 * @throws IllegalArgumentException if mirror request id is {@link #INVALID_REQUEST_ID} 1656 * @throws IllegalStateException if dynamic audio routing is not enabled 1657 * @throws IllegalStateException if audio mirroring feature is disabled, which can be verified 1658 * using {@link #isAudioFeatureEnabled(int)} with the {@link #AUDIO_FEATURE_AUDIO_MIRRORING} 1659 * feature flag 1660 * 1661 * @hide 1662 */ 1663 @SystemApi 1664 @NonNull 1665 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getMirrorAudioZonesForMirrorRequest(long mirrorId)1666 public List<Integer> getMirrorAudioZonesForMirrorRequest(long mirrorId) { 1667 try { 1668 return asList(mService.getMirrorAudioZonesForMirrorRequest(mirrorId)); 1669 } catch (RemoteException e) { 1670 return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST); 1671 } 1672 } 1673 1674 /** 1675 * Gets the output device for a given {@link AudioAttributes} usage in zoneId. 1676 * 1677 * <p><b>Note:</b> To be used for routing to a specific device. Most applications should 1678 * use the regular routing mechanism, which is to set audio attribute usage to 1679 * an audio track. 1680 * 1681 * @param zoneId zone id to query for device 1682 * @param usage usage where audio is routed 1683 * @return Audio device info, returns {@code null} if audio device usage fails to map to 1684 * an active audio device. This is different from the using an invalid value for 1685 * {@link AudioAttributes} usage. In the latter case the query will fail with a 1686 * RuntimeException indicating the issue. 1687 * 1688 * @hide 1689 */ 1690 @SystemApi 1691 @Nullable 1692 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getOutputDeviceForUsage(int zoneId, @AttributeUsage int usage)1693 public AudioDeviceInfo getOutputDeviceForUsage(int zoneId, @AttributeUsage int usage) { 1694 try { 1695 String deviceAddress = mService.getOutputDeviceAddressForUsage(zoneId, usage); 1696 if (deviceAddress == null) { 1697 return null; 1698 } 1699 AudioDeviceInfo[] outputDevices = 1700 mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 1701 for (AudioDeviceInfo info : outputDevices) { 1702 if (info.getAddress().equals(deviceAddress)) { 1703 return info; 1704 } 1705 } 1706 return null; 1707 } catch (RemoteException e) { 1708 return handleRemoteExceptionFromCarService(e, null); 1709 } 1710 } 1711 1712 /** 1713 * Gets the input devices for an audio zone 1714 * 1715 * @return list of input devices 1716 * @hide 1717 */ 1718 @SystemApi 1719 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) getInputDevicesForZoneId(int zoneId)1720 public @NonNull List<AudioDeviceInfo> getInputDevicesForZoneId(int zoneId) { 1721 try { 1722 return convertInputDevicesToDeviceInfos( 1723 mService.getInputDevicesForZoneId(zoneId), 1724 AudioManager.GET_DEVICES_INPUTS); 1725 } catch (RemoteException e) { 1726 return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST); 1727 } 1728 } 1729 1730 /** @hide */ 1731 @Override onCarDisconnected()1732 public void onCarDisconnected() { 1733 if (mService == null) { 1734 return; 1735 } 1736 1737 if (!mCarVolumeCallbacks.isEmpty()) { 1738 unregisterVolumeCallback(); 1739 } 1740 if (!mCarVolumeEventCallbacks.isEmpty()) { 1741 unregisterVolumeGroupEventCallback(); 1742 } 1743 } 1744 1745 /** @hide */ CarAudioManager(ICarBase car, IBinder service)1746 public CarAudioManager(ICarBase car, IBinder service) { 1747 super(car); 1748 mService = ICarAudio.Stub.asInterface(service); 1749 mAudioManager = getContext().getSystemService(AudioManager.class); 1750 mCarVolumeCallbacks = new CopyOnWriteArrayList<>(); 1751 mEventHandler = new EventHandler(getEventHandler().getLooper()); 1752 } 1753 1754 /** 1755 * Registers a {@link CarVolumeCallback} to receive volume change callbacks 1756 * @param callback {@link CarVolumeCallback} instance, can not be null 1757 * <p> 1758 * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME 1759 */ registerCarVolumeCallback(@onNull CarVolumeCallback callback)1760 public void registerCarVolumeCallback(@NonNull CarVolumeCallback callback) { 1761 Objects.requireNonNull(callback); 1762 1763 if (mCarVolumeCallbacks.isEmpty()) { 1764 registerVolumeCallback(); 1765 } 1766 1767 mCarVolumeCallbacks.add(callback); 1768 } 1769 1770 /** 1771 * Unregisters a {@link CarVolumeCallback} from receiving volume change callbacks 1772 * @param callback {@link CarVolumeCallback} instance previously registered, can not be null 1773 * <p> 1774 * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME 1775 */ unregisterCarVolumeCallback(@onNull CarVolumeCallback callback)1776 public void unregisterCarVolumeCallback(@NonNull CarVolumeCallback callback) { 1777 Objects.requireNonNull(callback); 1778 if (mCarVolumeCallbacks.contains(callback) && (mCarVolumeCallbacks.size() == 1)) { 1779 unregisterVolumeCallback(); 1780 } 1781 1782 mCarVolumeCallbacks.remove(callback); 1783 } 1784 registerVolumeCallback()1785 private void registerVolumeCallback() { 1786 try { 1787 mService.registerVolumeCallback(mCarVolumeCallbackImpl.asBinder()); 1788 } catch (RemoteException e) { 1789 Slog.e(CarLibLog.TAG_CAR, "registerVolumeCallback failed", e); 1790 } 1791 } 1792 unregisterVolumeCallback()1793 private void unregisterVolumeCallback() { 1794 try { 1795 mService.unregisterVolumeCallback(mCarVolumeCallbackImpl.asBinder()); 1796 } catch (RemoteException e) { 1797 handleRemoteExceptionFromCarService(e); 1798 } 1799 } 1800 1801 /** 1802 * Registers a {@link CarVolumeGroupEventCallback} to receive volume group event callbacks 1803 * 1804 * @param executor Executor on which callback will be invoked 1805 * @param callback Callback that will report volume group events 1806 * @return {@code true} if the callback is successfully registered, {@code false} otherwise 1807 * @throws NullPointerException if executor or callback parameters is {@code null} 1808 * @throws IllegalStateException if dynamic audio routing is not enabled 1809 * @throws IllegalStateException if volume group events are not enabled 1810 * 1811 * @hide 1812 */ 1813 @SystemApi 1814 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) registerCarVolumeGroupEventCallback( @onNull @allbackExecutor Executor executor, @NonNull CarVolumeGroupEventCallback callback)1815 public boolean registerCarVolumeGroupEventCallback( 1816 @NonNull @CallbackExecutor Executor executor, 1817 @NonNull CarVolumeGroupEventCallback callback) { 1818 Objects.requireNonNull(executor, "Executor can not be null"); 1819 Objects.requireNonNull(callback, "Car volume event callback can not be null"); 1820 1821 if (mCarVolumeEventCallbacks.isEmpty()) { 1822 if (!registerVolumeGroupEventCallback()) { 1823 return false; 1824 } 1825 } 1826 1827 return mCarVolumeEventCallbacks.addIfAbsent( 1828 new CarVolumeGroupEventCallbackWrapper(executor, callback)); 1829 } 1830 registerVolumeGroupEventCallback()1831 private boolean registerVolumeGroupEventCallback() { 1832 try { 1833 if (!mService.registerCarVolumeEventCallback(mCarVolumeEventCallbackImpl)) { 1834 return false; 1835 } 1836 } catch (RemoteException e) { 1837 Slog.e(CarLibLog.TAG_CAR, "registerCarVolumeEventCallback failed", e); 1838 return handleRemoteExceptionFromCarService(e, /* returnValue= */ false); 1839 } 1840 1841 return true; 1842 } 1843 1844 /** 1845 * Unregisters a {@link CarVolumeGroupEventCallback} registered via 1846 * {@link #registerCarVolumeGroupEventCallback} 1847 * 1848 * @param callback The callback to be removed 1849 * @throws NullPointerException if callback is {@code null} 1850 * @throws IllegalStateException if dynamic audio routing is not enabled 1851 * @throws IllegalStateException if volume group events are not enabled 1852 * 1853 * @hide 1854 */ 1855 @SystemApi 1856 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) unregisterCarVolumeGroupEventCallback( @onNull CarVolumeGroupEventCallback callback)1857 public void unregisterCarVolumeGroupEventCallback( 1858 @NonNull CarVolumeGroupEventCallback callback) { 1859 Objects.requireNonNull(callback, "Car volume event callback can not be null"); 1860 1861 CarVolumeGroupEventCallbackWrapper callbackWrapper = 1862 new CarVolumeGroupEventCallbackWrapper(/* executor= */ null, callback); 1863 if (mCarVolumeEventCallbacks.contains(callbackWrapper) 1864 && (mCarVolumeEventCallbacks.size() == 1)) { 1865 unregisterVolumeGroupEventCallback(); 1866 } 1867 1868 mCarVolumeEventCallbacks.remove(callbackWrapper); 1869 } 1870 unregisterVolumeGroupEventCallback()1871 private boolean unregisterVolumeGroupEventCallback() { 1872 try { 1873 if (!mService.unregisterCarVolumeEventCallback(mCarVolumeEventCallbackImpl)) { 1874 Slog.e(CarLibLog.TAG_CAR, 1875 "unregisterCarVolumeEventCallback failed with service"); 1876 return false; 1877 } 1878 } catch (RemoteException e) { 1879 Slog.e(CarLibLog.TAG_CAR, 1880 "unregisterCarVolumeEventCallback failed with exception", e); 1881 handleRemoteExceptionFromCarService(e); 1882 } 1883 1884 return true; 1885 } 1886 1887 /** 1888 * Returns the whether a volume group is muted 1889 * 1890 * <p><b>Note:</b> If {@link #AUDIO_FEATURE_VOLUME_GROUP_MUTING} is disabled this will always 1891 * return {@code false} as group mute is disabled. 1892 * 1893 * @param zoneId The zone id whose volume groups is queried. 1894 * @param groupId The volume group id whose mute state is returned. 1895 * @return {@code true} if the volume group is muted, {@code false} 1896 * otherwise 1897 * 1898 * @hide 1899 */ 1900 @SystemApi 1901 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) isVolumeGroupMuted(int zoneId, int groupId)1902 public boolean isVolumeGroupMuted(int zoneId, int groupId) { 1903 try { 1904 return mService.isVolumeGroupMuted(zoneId, groupId); 1905 } catch (RemoteException e) { 1906 return handleRemoteExceptionFromCarService(e, false); 1907 } 1908 } 1909 1910 /** 1911 * Sets a volume group mute 1912 * 1913 * <p><b>Note:</b> If {@link #AUDIO_FEATURE_VOLUME_GROUP_MUTING} is disabled this will throw an 1914 * error indicating the issue. 1915 * 1916 * @param zoneId The zone id whose volume groups will be changed. 1917 * @param groupId The volume group id whose mute state will be changed. 1918 * @param mute {@code true} to mute volume group, {@code false} otherwise 1919 * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI}, 1920 * {@link android.media.AudioManager#FLAG_PLAY_SOUND}) 1921 * 1922 * @hide 1923 */ 1924 @SystemApi 1925 @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags)1926 public void setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags) { 1927 try { 1928 mService.setVolumeGroupMute(zoneId, groupId, mute, flags); 1929 } catch (RemoteException e) { 1930 handleRemoteExceptionFromCarService(e); 1931 } 1932 } 1933 convertInputDevicesToDeviceInfos( List<AudioDeviceAttributes> devices, int flag)1934 private List<AudioDeviceInfo> convertInputDevicesToDeviceInfos( 1935 List<AudioDeviceAttributes> devices, int flag) { 1936 int addressesSize = devices.size(); 1937 Set<String> deviceAddressMap = new HashSet<>(addressesSize); 1938 for (int i = 0; i < addressesSize; ++i) { 1939 AudioDeviceAttributes device = devices.get(i); 1940 deviceAddressMap.add(device.getAddress()); 1941 } 1942 List<AudioDeviceInfo> deviceInfoList = new ArrayList<>(devices.size()); 1943 AudioDeviceInfo[] inputDevices = mAudioManager.getDevices(flag); 1944 for (int i = 0; i < inputDevices.length; ++i) { 1945 AudioDeviceInfo info = inputDevices[i]; 1946 if (info.isSource() && deviceAddressMap.contains(info.getAddress())) { 1947 deviceInfoList.add(info); 1948 } 1949 } 1950 return deviceInfoList; 1951 } 1952 1953 private final class EventHandler extends Handler { 1954 private static final int MSG_GROUP_VOLUME_CHANGE = 1; 1955 private static final int MSG_GROUP_MUTE_CHANGE = 2; 1956 private static final int MSG_MASTER_MUTE_CHANGE = 3; 1957 private static final int MSG_VOLUME_GROUP_EVENT = 4; 1958 EventHandler(Looper looper)1959 private EventHandler(Looper looper) { 1960 super(looper); 1961 } 1962 1963 @Override handleMessage(Message msg)1964 public void handleMessage(Message msg) { 1965 switch (msg.what) { 1966 case MSG_GROUP_VOLUME_CHANGE: 1967 VolumeGroupChangeInfo volumeInfo = (VolumeGroupChangeInfo) msg.obj; 1968 handleOnGroupVolumeChanged(volumeInfo.mZoneId, volumeInfo.mGroupId, 1969 volumeInfo.mFlags); 1970 break; 1971 case MSG_GROUP_MUTE_CHANGE: 1972 VolumeGroupChangeInfo muteInfo = (VolumeGroupChangeInfo) msg.obj; 1973 handleOnGroupMuteChanged(muteInfo.mZoneId, muteInfo.mGroupId, muteInfo.mFlags); 1974 break; 1975 case MSG_MASTER_MUTE_CHANGE: 1976 handleOnMasterMuteChanged(msg.arg1, msg.arg2); 1977 break; 1978 case MSG_VOLUME_GROUP_EVENT: 1979 List<CarVolumeGroupEvent> events = (List<CarVolumeGroupEvent>) msg.obj; 1980 handleOnVolumeGroupEvent(events); 1981 default: 1982 Slog.e(CarLibLog.TAG_CAR, "Unknown message not handled:" + msg.what); 1983 break; 1984 } 1985 } 1986 dispatchOnGroupVolumeChanged(int zoneId, int groupId, int flags)1987 private void dispatchOnGroupVolumeChanged(int zoneId, int groupId, int flags) { 1988 VolumeGroupChangeInfo volumeInfo = new VolumeGroupChangeInfo(zoneId, groupId, flags); 1989 sendMessage(obtainMessage(MSG_GROUP_VOLUME_CHANGE, volumeInfo)); 1990 } 1991 dispatchOnMasterMuteChanged(int zoneId, int flags)1992 private void dispatchOnMasterMuteChanged(int zoneId, int flags) { 1993 sendMessage(obtainMessage(MSG_MASTER_MUTE_CHANGE, zoneId, flags)); 1994 } 1995 dispatchOnGroupMuteChanged(int zoneId, int groupId, int flags)1996 private void dispatchOnGroupMuteChanged(int zoneId, int groupId, int flags) { 1997 VolumeGroupChangeInfo volumeInfo = new VolumeGroupChangeInfo(zoneId, groupId, flags); 1998 sendMessage(obtainMessage(MSG_GROUP_MUTE_CHANGE, volumeInfo)); 1999 } 2000 dispatchOnVolumeGroupEvent(List<CarVolumeGroupEvent> events)2001 private void dispatchOnVolumeGroupEvent(List<CarVolumeGroupEvent> events) { 2002 sendMessage(obtainMessage(MSG_VOLUME_GROUP_EVENT, events)); 2003 } 2004 2005 private class VolumeGroupChangeInfo { 2006 public int mZoneId; 2007 public int mGroupId; 2008 public int mFlags; 2009 VolumeGroupChangeInfo(int zoneId, int groupId, int flags)2010 VolumeGroupChangeInfo(int zoneId, int groupId, int flags) { 2011 mZoneId = zoneId; 2012 mGroupId = groupId; 2013 mFlags = flags; 2014 } 2015 } 2016 } 2017 handleOnGroupVolumeChanged(int zoneId, int groupId, int flags)2018 private void handleOnGroupVolumeChanged(int zoneId, int groupId, int flags) { 2019 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 2020 callback.onGroupVolumeChanged(zoneId, groupId, flags); 2021 } 2022 } 2023 handleOnMasterMuteChanged(int zoneId, int flags)2024 private void handleOnMasterMuteChanged(int zoneId, int flags) { 2025 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 2026 callback.onMasterMuteChanged(zoneId, flags); 2027 } 2028 } 2029 handleOnGroupMuteChanged(int zoneId, int groupId, int flags)2030 private void handleOnGroupMuteChanged(int zoneId, int groupId, int flags) { 2031 for (CarVolumeCallback callback : mCarVolumeCallbacks) { 2032 callback.onGroupMuteChanged(zoneId, groupId, flags); 2033 } 2034 } 2035 2036 handleOnVolumeGroupEvent(List<CarVolumeGroupEvent> events)2037 private void handleOnVolumeGroupEvent(List<CarVolumeGroupEvent> events) { 2038 for (CarVolumeGroupEventCallbackWrapper wr : mCarVolumeEventCallbacks) { 2039 wr.mExecutor.execute(() -> wr.mCallback.onVolumeGroupEvent(events)); 2040 } 2041 } 2042 toIntArray(List<Integer> list)2043 private static int[] toIntArray(List<Integer> list) { 2044 int size = list.size(); 2045 int[] array = new int[size]; 2046 for (int i = 0; i < size; ++i) { 2047 array[i] = list.get(i); 2048 } 2049 return array; 2050 } 2051 asList(int[] intArray)2052 private static List<Integer> asList(int[] intArray) { 2053 List<Integer> zoneIdList = new ArrayList<Integer>(intArray.length); 2054 for (int index = 0; index < intArray.length; index++) { 2055 zoneIdList.add(intArray[index]); 2056 } 2057 return zoneIdList; 2058 } 2059 2060 /** 2061 * Callback interface to receive volume change events in a car. 2062 * Extend this class and register it with {@link #registerCarVolumeCallback(CarVolumeCallback)} 2063 * and unregister it via {@link #unregisterCarVolumeCallback(CarVolumeCallback)} 2064 */ 2065 public abstract static class CarVolumeCallback { 2066 /** 2067 * This is called whenever a group volume is changed. 2068 * 2069 * The changed-to volume index is not included, the caller is encouraged to 2070 * get the current group volume index via CarAudioManager. 2071 * 2072 * <p><b>Notes:</b> 2073 * <ul> 2074 * <li>If both {@link CarVolumeCallback} and {@code CarVolumeGroupEventCallback} 2075 * are registered by the same app, then volume group index changes are <b>only</b> 2076 * propagated through {@code CarVolumeGroupEventCallback} 2077 * (until it is unregistered)</li> 2078 * <li>Apps are encouraged to migrate to the new callback 2079 * {@code CarVolumeGroupEventCallback}</li> 2080 * </ul> 2081 * 2082 * @param zoneId Id of the audio zone that volume change happens 2083 * @param groupId Id of the volume group that volume is changed 2084 * @param flags see {@link android.media.AudioManager} for flag definitions 2085 */ 2086 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) onGroupVolumeChanged(int zoneId, int groupId, int flags)2087 public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {} 2088 2089 /** 2090 * This is called whenever the global mute state is changed. 2091 * The changed-to global mute state is not included, the caller is encouraged to 2092 * get the current global mute state via AudioManager. 2093 * 2094 * <p><b>Note:</b> If {@link CarAudioManager#AUDIO_FEATURE_VOLUME_GROUP_MUTING} is disabled 2095 * this will be triggered on mute changes. Otherwise, car audio mute changes will trigger 2096 * {@link #onGroupMuteChanged(int, int, int)} 2097 * 2098 * @param zoneId Id of the audio zone that global mute state change happens 2099 * @param flags see {@link android.media.AudioManager} for flag definitions 2100 */ 2101 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) onMasterMuteChanged(int zoneId, int flags)2102 public void onMasterMuteChanged(int zoneId, int flags) {} 2103 2104 /** 2105 * This is called whenever a group mute state is changed. 2106 * 2107 * The changed-to mute state is not included, the caller is encouraged to 2108 * get the current group mute state via CarAudioManager. 2109 * 2110 * <p><b>Notes:</b> 2111 * <ul> 2112 * <li>If {@link CarAudioManager#AUDIO_FEATURE_VOLUME_GROUP_MUTING} is enabled 2113 * this will be triggered on mute changes. Otherwise, car audio mute changes will 2114 * trigger {@link #onMasterMuteChanged(int, int)}</li> 2115 * <li>If both {@link CarVolumeCallback} and {@code CarVolumeGroupEventCallback} 2116 * are registered by the same app, then volume group mute changes are <b>only</b> 2117 * propagated through {@code CarVolumeGroupEventCallback} 2118 * (until it is unregistered)</li> 2119 * <li>Apps are encouraged to migrate to the new callback 2120 * {@code CarVolumeGroupEventCallback}</li> 2121 * </ul> 2122 * 2123 * @param zoneId Id of the audio zone that volume change happens 2124 * @param groupId Id of the volume group that volume is changed 2125 * @param flags see {@link android.media.AudioManager} for flag definitions 2126 */ 2127 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) onGroupMuteChanged(int zoneId, int groupId, int flags)2128 public void onGroupMuteChanged(int zoneId, int groupId, int flags) {} 2129 } 2130 2131 private static final class MediaAudioRequestStatusCallbackWrapper 2132 extends IMediaAudioRequestStatusCallback.Stub { 2133 2134 private final Executor mExecutor; 2135 private final MediaAudioRequestStatusCallback mCallback; 2136 MediaAudioRequestStatusCallbackWrapper(Executor executor, MediaAudioRequestStatusCallback callback)2137 MediaAudioRequestStatusCallbackWrapper(Executor executor, 2138 MediaAudioRequestStatusCallback callback) { 2139 mExecutor = executor; 2140 mCallback = callback; 2141 } 2142 2143 @Override onMediaAudioRequestStatusChanged(CarOccupantZoneManager.OccupantZoneInfo info, long requestId, @CarAudioManager.MediaAudioRequestStatus int status)2144 public void onMediaAudioRequestStatusChanged(CarOccupantZoneManager.OccupantZoneInfo info, 2145 long requestId, 2146 @CarAudioManager.MediaAudioRequestStatus int status) throws RemoteException { 2147 long identity = Binder.clearCallingIdentity(); 2148 try { 2149 mExecutor.execute(() -> 2150 mCallback.onMediaAudioRequestStatusChanged(info, requestId, status)); 2151 } finally { 2152 Binder.restoreCallingIdentity(identity); 2153 } 2154 } 2155 } 2156 2157 private static final class SwitchAudioZoneConfigCallbackWrapper 2158 extends ISwitchAudioZoneConfigCallback.Stub { 2159 private final Executor mExecutor; 2160 private final SwitchAudioZoneConfigCallback mCallback; 2161 SwitchAudioZoneConfigCallbackWrapper(Executor executor, SwitchAudioZoneConfigCallback callback)2162 SwitchAudioZoneConfigCallbackWrapper(Executor executor, 2163 SwitchAudioZoneConfigCallback callback) { 2164 mExecutor = executor; 2165 mCallback = callback; 2166 } 2167 2168 @Override onAudioZoneConfigSwitched(CarAudioZoneConfigInfo zoneConfig, boolean isSuccessful)2169 public void onAudioZoneConfigSwitched(CarAudioZoneConfigInfo zoneConfig, 2170 boolean isSuccessful) { 2171 long identity = Binder.clearCallingIdentity(); 2172 try { 2173 mExecutor.execute(() -> 2174 mCallback.onAudioZoneConfigSwitched(zoneConfig, isSuccessful)); 2175 } finally { 2176 Binder.restoreCallingIdentity(identity); 2177 } 2178 } 2179 } 2180 2181 private static final class CarVolumeGroupEventCallbackWrapper { 2182 private final Executor mExecutor; 2183 private final CarVolumeGroupEventCallback mCallback; 2184 CarVolumeGroupEventCallbackWrapper(Executor executor, CarVolumeGroupEventCallback callback)2185 CarVolumeGroupEventCallbackWrapper(Executor executor, 2186 CarVolumeGroupEventCallback callback) { 2187 mExecutor = executor; 2188 mCallback = callback; 2189 } 2190 2191 @Override equals(Object o)2192 public boolean equals(Object o) { 2193 if (this == o) { 2194 return true; 2195 } 2196 2197 if (!(o instanceof CarVolumeGroupEventCallbackWrapper)) { 2198 return false; 2199 } 2200 2201 CarVolumeGroupEventCallbackWrapper rhs = (CarVolumeGroupEventCallbackWrapper) o; 2202 return mCallback == rhs.mCallback; 2203 } 2204 2205 @Override hashCode()2206 public int hashCode() { 2207 return mCallback.hashCode(); 2208 } 2209 } 2210 2211 private static final class AudioZonesMirrorStatusCallbackWrapper 2212 extends IAudioZonesMirrorStatusCallback.Stub { 2213 2214 private final Executor mExecutor; 2215 private final AudioZonesMirrorStatusCallback mCallback; 2216 AudioZonesMirrorStatusCallbackWrapper(Executor executor, AudioZonesMirrorStatusCallback callback)2217 AudioZonesMirrorStatusCallbackWrapper(Executor executor, 2218 AudioZonesMirrorStatusCallback callback) { 2219 mExecutor = executor; 2220 mCallback = callback; 2221 } 2222 onAudioZonesMirrorStatusChanged(int[] mirroredAudioZones, int status)2223 public void onAudioZonesMirrorStatusChanged(int[] mirroredAudioZones, 2224 int status) { 2225 long identity = Binder.clearCallingIdentity(); 2226 try { 2227 mExecutor.execute(() -> mCallback.onAudioZonesMirrorStatusChanged( 2228 asList(mirroredAudioZones), status)); 2229 } finally { 2230 Binder.restoreCallingIdentity(identity); 2231 } 2232 } 2233 } 2234 2235 private static final class AudioZoneConfigurationsChangeCallbackWrapper extends 2236 IAudioZoneConfigurationsChangeCallback.Stub { 2237 2238 private final Executor mExecutor; 2239 private final AudioZoneConfigurationsChangeCallback mCallback; 2240 AudioZoneConfigurationsChangeCallbackWrapper(Executor executor, AudioZoneConfigurationsChangeCallback callback)2241 private AudioZoneConfigurationsChangeCallbackWrapper(Executor executor, 2242 AudioZoneConfigurationsChangeCallback callback) { 2243 mExecutor = executor; 2244 mCallback = callback; 2245 } 2246 2247 @Override onAudioZoneConfigurationsChanged(List<CarAudioZoneConfigInfo> configs, int status)2248 public void onAudioZoneConfigurationsChanged(List<CarAudioZoneConfigInfo> configs, 2249 int status) { 2250 long identity = Binder.clearCallingIdentity(); 2251 try { 2252 mExecutor.execute(() -> mCallback.onAudioZoneConfigurationsChanged(configs, 2253 status)); 2254 } finally { 2255 Binder.restoreCallingIdentity(identity); 2256 } 2257 } 2258 } 2259 } 2260