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