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