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