• 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 android.annotation.IntDef;
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.annotation.RequiresPermission;
22 import android.annotation.SystemApi;
23 import android.annotation.TestApi;
24 import android.car.Car;
25 import android.car.CarLibLog;
26 import android.car.CarManagerBase;
27 import android.media.AudioAttributes;
28 import android.media.AudioDeviceAttributes;
29 import android.media.AudioDeviceInfo;
30 import android.media.AudioManager;
31 import android.media.AudioManager.AudioDeviceRole;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.IBinder;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.os.RemoteException;
38 import android.util.Log;
39 
40 import java.lang.annotation.Retention;
41 import java.lang.annotation.RetentionPolicy;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.HashSet;
45 import java.util.List;
46 import java.util.Objects;
47 import java.util.Set;
48 import java.util.concurrent.CopyOnWriteArrayList;
49 
50 /**
51  * APIs for handling audio in a car.
52  *
53  * In a car environment, we introduced the support to turn audio dynamic routing on /off by
54  * setting the "audioUseDynamicRouting" attribute in config.xml
55  *
56  * When audio dynamic routing is enabled:
57  * - Audio devices are grouped into zones
58  * - There is at least one primary zone, and extra secondary zones such as RSE
59  *   (Reat Seat Entertainment)
60  * - Within each zone, audio devices are grouped into volume groups for volume control
61  * - Audio is assigned to an audio device based on its AudioAttributes usage
62  *
63  * When audio dynamic routing is disabled:
64  * - There is exactly one audio zone, which is the primary zone
65  * - Each volume group represents a controllable STREAM_TYPE, same as AudioManager
66  */
67 public final class CarAudioManager extends CarManagerBase {
68 
69     /**
70      * Zone id of the primary audio zone.
71      * @hide
72      */
73     @SystemApi
74     public static final int PRIMARY_AUDIO_ZONE = 0x0;
75 
76     /**
77      * Zone id of the invalid audio zone.
78      * @hide
79      */
80     @SystemApi
81     public static final int INVALID_AUDIO_ZONE = 0xffffffff;
82 
83     /**
84      * This is used to determine if dynamic routing is enabled via
85      * {@link #isAudioFeatureEnabled()}
86      */
87     public static final int AUDIO_FEATURE_DYNAMIC_ROUTING = 0x1;
88 
89     /**
90      * This is used to determine if volume group muting is enabled via
91      * {@link #isAudioFeatureEnabled()}
92      *
93      * <p>
94      * If enabled, car volume group muting APIs can be used to mute each volume group,
95      * also car volume group muting changed callback will be called upon group mute changes. If
96      * disabled, car volume will toggle master mute instead.
97      */
98     public static final int AUDIO_FEATURE_VOLUME_GROUP_MUTING = 0x2;
99 
100     /** @hide */
101     @IntDef(flag = false, prefix = "AUDIO_FEATURE", value = {
102             AUDIO_FEATURE_DYNAMIC_ROUTING,
103             AUDIO_FEATURE_VOLUME_GROUP_MUTING
104     })
105     @Retention(RetentionPolicy.SOURCE)
106     public @interface CarAudioFeature {}
107 
108     /**
109      * Volume Group ID when volume group not found.
110      * @hide
111      */
112     public static final int INVALID_VOLUME_GROUP_ID = -1;
113 
114     /**
115      * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an
116      * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus events,
117      * including {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
118      * The requester must hold {@link Car#PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS}; otherwise,
119      * this extra is ignored.
120      *
121      * @hide
122      */
123     @SystemApi
124     public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS =
125             "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS";
126 
127     /**
128      * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an
129      * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus for the
130      * the zone. If the zone id is not defined: the audio focus request will default to the
131      * currently mapped zone for the requesting uid or {@link CarAudioManager.PRIMARY_AUDIO_ZONE}
132      * if no uid mapping currently exist.
133      *
134      * @hide
135      */
136     public static final String AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID =
137             "android.car.media.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID";
138 
139     private final ICarAudio mService;
140     private final CopyOnWriteArrayList<CarVolumeCallback> mCarVolumeCallbacks;
141     private final AudioManager mAudioManager;
142 
143     private final EventHandler mEventHandler;
144 
145     private final ICarVolumeCallback mCarVolumeCallbackImpl =
146             new android.car.media.ICarVolumeCallback.Stub() {
147         @Override
148         public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
149             mEventHandler.dispatchOnGroupVolumeChanged(zoneId, groupId, flags);
150         }
151 
152         @Override
153         public void onGroupMuteChanged(int zoneId, int groupId, int flags) {
154             mEventHandler.dispatchOnGroupMuteChanged(zoneId, groupId, flags);
155         }
156 
157         @Override
158         public void onMasterMuteChanged(int zoneId, int flags) {
159             mEventHandler.dispatchOnMasterMuteChanged(zoneId, flags);
160         }
161     };
162 
163     /**
164      * @return Whether dynamic routing is enabled or not.
165      *
166      * @deprecated use {@link #isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING)} instead.
167      *
168      * @hide
169      */
170     @TestApi
171     @Deprecated
isDynamicRoutingEnabled()172     public boolean isDynamicRoutingEnabled() {
173         return isAudioFeatureEnabled(AUDIO_FEATURE_DYNAMIC_ROUTING);
174     }
175 
176     /**
177      * Determines if an audio feature is enabled.
178      *
179      * @param audioFeature audio feature to query, can be {@link #AUDIO_FEATURE_DYNAMIC_ROUTING} or
180      * {@link #AUDIO_FEATURE_VOLUME_GROUP_MUTING}
181      * @return Returns {@code true} if the feature is enabled, {@code false} otherwise.
182      */
isAudioFeatureEnabled(@arAudioFeature int audioFeature)183     public boolean isAudioFeatureEnabled(@CarAudioFeature int audioFeature) {
184         try {
185             return mService.isAudioFeatureEnabled(audioFeature);
186         } catch (RemoteException e) {
187             return handleRemoteExceptionFromCarService(e, false);
188         }
189     }
190 
191     /**
192      * Sets the volume index for a volume group in primary zone.
193      *
194      * @see {@link #setGroupVolume(int, int, int, int)}
195      * @hide
196      */
197     @SystemApi
198     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
setGroupVolume(int groupId, int index, int flags)199     public void setGroupVolume(int groupId, int index, int flags) {
200         setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, index, flags);
201     }
202 
203     /**
204      * Sets the volume index for a volume group.
205      *
206      * @param zoneId The zone id whose volume group is affected.
207      * @param groupId The volume group id whose volume index should be set.
208      * @param index The volume index to set. See
209      *            {@link #getGroupMaxVolume(int, int)} for the largest valid value.
210      * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI},
211      *              {@link android.media.AudioManager#FLAG_PLAY_SOUND})
212      * @hide
213      */
214     @SystemApi
215     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
setGroupVolume(int zoneId, int groupId, int index, int flags)216     public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
217         try {
218             mService.setGroupVolume(zoneId, groupId, index, flags);
219         } catch (RemoteException e) {
220             handleRemoteExceptionFromCarService(e);
221         }
222     }
223 
224     /**
225      * Returns the maximum volume index for a volume group in primary zone.
226      *
227      * @see {@link #getGroupMaxVolume(int, int)}
228      * @hide
229      */
230     @SystemApi
231     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupMaxVolume(int groupId)232     public int getGroupMaxVolume(int groupId) {
233         return getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId);
234     }
235 
236     /**
237      * Returns the maximum volume index for a volume group.
238      *
239      * @param zoneId The zone id whose volume group is queried.
240      * @param groupId The volume group id whose maximum volume index is returned.
241      * @return The maximum valid volume index for the given group.
242      * @hide
243      */
244     @SystemApi
245     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupMaxVolume(int zoneId, int groupId)246     public int getGroupMaxVolume(int zoneId, int groupId) {
247         try {
248             return mService.getGroupMaxVolume(zoneId, groupId);
249         } catch (RemoteException e) {
250             return handleRemoteExceptionFromCarService(e, 0);
251         }
252     }
253 
254     /**
255      * Returns the minimum volume index for a volume group in primary zone.
256      *
257      * @see {@link #getGroupMinVolume(int, int)}
258      * @hide
259      */
260     @SystemApi
261     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupMinVolume(int groupId)262     public int getGroupMinVolume(int groupId) {
263         return getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
264     }
265 
266     /**
267      * Returns the minimum volume index for a volume group.
268      *
269      * @param zoneId The zone id whose volume group is queried.
270      * @param groupId The volume group id whose minimum volume index is returned.
271      * @return The minimum valid volume index for the given group, non-negative
272      * @hide
273      */
274     @SystemApi
275     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupMinVolume(int zoneId, int groupId)276     public int getGroupMinVolume(int zoneId, int groupId) {
277         try {
278             return mService.getGroupMinVolume(zoneId, groupId);
279         } catch (RemoteException e) {
280             return handleRemoteExceptionFromCarService(e, 0);
281         }
282     }
283 
284     /**
285      * Returns the current volume index for a volume group in primary zone.
286      *
287      * @see {@link #getGroupVolume(int, int)}
288      * @hide
289      */
290     @SystemApi
291     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupVolume(int groupId)292     public int getGroupVolume(int groupId) {
293         return getGroupVolume(PRIMARY_AUDIO_ZONE, groupId);
294     }
295 
296     /**
297      * Returns the current volume index for a volume group.
298      *
299      * @param zoneId The zone id whose volume groups is queried.
300      * @param groupId The volume group id whose volume index is returned.
301      * @return The current volume index for the given group.
302      *
303      * @see #getGroupMaxVolume(int, int)
304      * @see #setGroupVolume(int, int, int, int)
305      * @hide
306      */
307     @SystemApi
308     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupVolume(int zoneId, int groupId)309     public int getGroupVolume(int zoneId, int groupId) {
310         try {
311             return mService.getGroupVolume(zoneId, groupId);
312         } catch (RemoteException e) {
313             return handleRemoteExceptionFromCarService(e, 0);
314         }
315     }
316 
317     /**
318      * Adjust the relative volume in the front vs back of the vehicle cabin.
319      *
320      * @param value in the range -1.0 to 1.0 for fully toward the back through
321      *              fully toward the front.  0.0 means evenly balanced.
322      *
323      * @see #setBalanceTowardRight(float)
324      * @hide
325      */
326     @SystemApi
327     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
setFadeTowardFront(float value)328     public void setFadeTowardFront(float value) {
329         try {
330             mService.setFadeTowardFront(value);
331         } catch (RemoteException e) {
332             handleRemoteExceptionFromCarService(e);
333         }
334     }
335 
336     /**
337      * Adjust the relative volume on the left vs right side of the vehicle cabin.
338      *
339      * @param value in the range -1.0 to 1.0 for fully toward the left through
340      *              fully toward the right.  0.0 means evenly balanced.
341      *
342      * @see #setFadeTowardFront(float)
343      * @hide
344      */
345     @SystemApi
346     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
setBalanceTowardRight(float value)347     public void setBalanceTowardRight(float value) {
348         try {
349             mService.setBalanceTowardRight(value);
350         } catch (RemoteException e) {
351             handleRemoteExceptionFromCarService(e);
352         }
353     }
354 
355     /**
356      * Queries the system configuration in order to report the available, non-microphone audio
357      * input devices.
358      *
359      * @return An array of strings representing the available input ports.
360      * Each port is identified by it's "address" tag in the audioPolicyConfiguration xml file.
361      * Empty array if we find nothing.
362      *
363      * @see #createAudioPatch(String, int, int)
364      * @see #releaseAudioPatch(CarAudioPatchHandle)
365      * @hide
366      */
367     @SystemApi
368     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getExternalSources()369     public @NonNull String[] getExternalSources() {
370         try {
371             return mService.getExternalSources();
372         } catch (RemoteException e) {
373             handleRemoteExceptionFromCarService(e);
374             return new String[0];
375         }
376     }
377 
378     /**
379      * Given an input port identified by getExternalSources(), request that it's audio signal
380      * be routed below the HAL to the output port associated with the given usage.  For example,
381      * The output of a tuner might be routed directly to the output buss associated with
382      * AudioAttributes.USAGE_MEDIA while the tuner is playing.
383      *
384      * @param sourceAddress the input port name obtained from getExternalSources().
385      * @param usage the type of audio represented by this source (usually USAGE_MEDIA).
386      * @param gainInMillibels How many steps above the minimum value defined for the source port to
387      *                       set the gain when creating the patch.
388      *                       This may be used for source balancing without affecting the user
389      *                       controlled volumes applied to the destination ports.  A value of
390      *                       0 indicates no gain change is requested.
391      * @return A handle for the created patch which can be used to later remove it.
392      *
393      * @see #getExternalSources()
394      * @see #releaseAudioPatch(CarAudioPatchHandle)
395      * @hide
396      */
397     @SystemApi
398     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
createAudioPatch(String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels)399     public CarAudioPatchHandle createAudioPatch(String sourceAddress,
400             @AudioAttributes.AttributeUsage int usage, int gainInMillibels) {
401         try {
402             return mService.createAudioPatch(sourceAddress, usage, gainInMillibels);
403         } catch (RemoteException e) {
404             return handleRemoteExceptionFromCarService(e, null);
405         }
406     }
407 
408     /**
409      * Removes the association between an input port and an output port identified by the provided
410      * handle.
411      *
412      * @param patch CarAudioPatchHandle returned from createAudioPatch().
413      *
414      * @see #getExternalSources()
415      * @see #createAudioPatch(String, int, int)
416      * @hide
417      */
418     @SystemApi
419     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
releaseAudioPatch(CarAudioPatchHandle patch)420     public void releaseAudioPatch(CarAudioPatchHandle patch) {
421         try {
422             mService.releaseAudioPatch(patch);
423         } catch (RemoteException e) {
424             handleRemoteExceptionFromCarService(e);
425         }
426     }
427 
428     /**
429      * Gets the count of available volume groups in primary zone.
430      *
431      * @see {@link #getVolumeGroupCount(int)}
432      * @hide
433      */
434     @SystemApi
435     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getVolumeGroupCount()436     public int getVolumeGroupCount() {
437         return getVolumeGroupCount(PRIMARY_AUDIO_ZONE);
438     }
439 
440     /**
441      * Gets the count of available volume groups in the system.
442      *
443      * @param zoneId The zone id whois count of volume groups is queried.
444      * @return Count of volume groups
445      * @hide
446      */
447     @SystemApi
448     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getVolumeGroupCount(int zoneId)449     public int getVolumeGroupCount(int zoneId) {
450         try {
451             return mService.getVolumeGroupCount(zoneId);
452         } catch (RemoteException e) {
453             return handleRemoteExceptionFromCarService(e, 0);
454         }
455     }
456 
457     /**
458      * Gets the volume group id for a given {@link AudioAttributes} usage in primary zone.
459      *
460      * @see {@link #getVolumeGroupIdForUsage(int, int)}
461      * @hide
462      */
463     @SystemApi
464     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getVolumeGroupIdForUsage(@udioAttributes.AttributeUsage int usage)465     public int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage) {
466         return getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE, usage);
467     }
468 
469     /**
470      * Gets the volume group id for a given {@link AudioAttributes} usage.
471      *
472      * @param zoneId The zone id whose volume group is queried.
473      * @param usage The {@link AudioAttributes} usage to get a volume group from.
474      * @return The volume group id where the usage belongs to
475      * @hide
476      */
477     @SystemApi
478     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)479     public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) {
480         try {
481             return mService.getVolumeGroupIdForUsage(zoneId, usage);
482         } catch (RemoteException e) {
483             return handleRemoteExceptionFromCarService(e, 0);
484         }
485     }
486 
487     /**
488      * Gets array of {@link AudioAttributes} usages for a volume group in primary zone.
489      *
490      * @see {@link #getUsagesForVolumeGroupId(int, int)}
491      * @hide
492      */
493     @SystemApi
494     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getUsagesForVolumeGroupId(int groupId)495     public @NonNull int[] getUsagesForVolumeGroupId(int groupId) {
496         return getUsagesForVolumeGroupId(PRIMARY_AUDIO_ZONE, groupId);
497     }
498 
499     /**
500      * Gets array of {@link AudioAttributes} usages for a volume group in a zone.
501      *
502      * @param zoneId The zone id whose volume group is queried.
503      * @param groupId The volume group id whose associated audio usages is returned.
504      * @return Array of {@link AudioAttributes} usages for a given volume group id
505      * @hide
506      */
507     @SystemApi
508     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getUsagesForVolumeGroupId(int zoneId, int groupId)509     public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
510         try {
511             return mService.getUsagesForVolumeGroupId(zoneId, groupId);
512         } catch (RemoteException e) {
513             return handleRemoteExceptionFromCarService(e, new int[0]);
514         }
515     }
516 
517     /**
518      * Determines if a particular volume group has any audio playback in a zone
519      *
520      * @param zoneId The zone id whose volume group is queried.
521      * @param groupId The volume group id whose associated audio usages is returned.
522      * @return {@code true} if the group has active playback, {@code false} otherwise
523      *
524      * @hide
525      */
526     @SystemApi
527     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
isPlaybackOnVolumeGroupActive(int zoneId, int groupId)528     public boolean isPlaybackOnVolumeGroupActive(int zoneId, int groupId) {
529         try {
530             return mService.isPlaybackOnVolumeGroupActive(zoneId, groupId);
531         } catch (RemoteException e) {
532             return handleRemoteExceptionFromCarService(e, false);
533         }
534     }
535 
536     /**
537      * Gets the audio zones currently available
538      *
539      * @return audio zone ids
540      * @hide
541      */
542     @SystemApi
543     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getAudioZoneIds()544     public @NonNull List<Integer> getAudioZoneIds() {
545         try {
546             int[] zoneIdArray = mService.getAudioZoneIds();
547             List<Integer> zoneIdList = new ArrayList<Integer>(zoneIdArray.length);
548             for (int zoneIdValue : zoneIdArray) {
549                 zoneIdList.add(zoneIdValue);
550             }
551             return zoneIdList;
552         } catch (RemoteException e) {
553             return handleRemoteExceptionFromCarService(e, Collections.emptyList());
554         }
555     }
556 
557     /**
558      * Gets the audio zone id currently mapped to uId,
559      * defaults to PRIMARY_AUDIO_ZONE if no mapping exist
560      *
561      * @param uid The uid to map
562      * @return zone id mapped to uid
563      * @hide
564      */
565     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getZoneIdForUid(int uid)566     public int getZoneIdForUid(int uid) {
567         try {
568             return mService.getZoneIdForUid(uid);
569         } catch (RemoteException e) {
570             return handleRemoteExceptionFromCarService(e, 0);
571         }
572     }
573 
574     /**
575      * Maps the audio zone id to uid
576      *
577      * @param zoneId The audio zone id
578      * @param uid The uid to map
579      * @return true if the uid is successfully mapped
580      * @hide
581      */
582     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
setZoneIdForUid(int zoneId, int uid)583     public boolean setZoneIdForUid(int zoneId, int uid) {
584         try {
585             return mService.setZoneIdForUid(zoneId, uid);
586         } catch (RemoteException e) {
587             return handleRemoteExceptionFromCarService(e, false);
588         }
589     }
590 
591     /**
592      * Clears the current zone mapping of the uid
593      *
594      * @param uid The uid to clear
595      * @return true if the zone was successfully cleared
596      * @hide
597      */
598     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
clearZoneIdForUid(int uid)599     public boolean clearZoneIdForUid(int uid) {
600         try {
601             return mService.clearZoneIdForUid(uid);
602         } catch (RemoteException e) {
603             return handleRemoteExceptionFromCarService(e, false);
604         }
605     }
606 
607     /**
608      * Gets the output device for a given {@link AudioAttributes} usage in zoneId.
609      *
610      * <p><b>Note:</b> To be used for routing to a specific device. Most applications should
611      * use the regular routing mechanism, which is to set audio attribute usage to
612      * an audio track.
613      *
614      * @param zoneId zone id to query for device
615      * @param usage usage where audio is routed
616      * @return Audio device info, returns {@code null} if audio device usage fails to map to
617      * an active audio device. This is different from the using an invalid value for
618      * {@link AudioAttributes} usage. In the latter case the query will fail with a
619      * RuntimeException indicating the issue.
620      *
621      * @hide
622      */
623     @SystemApi
624     @Nullable
625     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getOutputDeviceForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)626     public AudioDeviceInfo getOutputDeviceForUsage(int zoneId,
627             @AudioAttributes.AttributeUsage int usage) {
628         try {
629             String deviceAddress = mService.getOutputDeviceAddressForUsage(zoneId, usage);
630             if (deviceAddress == null) {
631                 return null;
632             }
633             AudioDeviceInfo[] outputDevices =
634                     mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
635             for (AudioDeviceInfo info : outputDevices) {
636                 if (info.getAddress().equals(deviceAddress)) {
637                     return info;
638                 }
639             }
640             return null;
641         } catch (RemoteException e) {
642             return handleRemoteExceptionFromCarService(e, null);
643         }
644     }
645 
646     /**
647      * Gets the input devices for an audio zone
648      *
649      * @return list of input devices
650      * @hide
651      */
652     @SystemApi
653     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getInputDevicesForZoneId(int zoneId)654     public @NonNull List<AudioDeviceInfo> getInputDevicesForZoneId(int zoneId) {
655         try {
656             return convertInputDevicesToDeviceInfos(
657                     mService.getInputDevicesForZoneId(zoneId),
658                     AudioManager.GET_DEVICES_INPUTS);
659         } catch (RemoteException e) {
660             return handleRemoteExceptionFromCarService(e, new ArrayList<>());
661         }
662     }
663 
664     /** @hide */
665     @Override
onCarDisconnected()666     public void onCarDisconnected() {
667         if (mService != null && !mCarVolumeCallbacks.isEmpty()) {
668             unregisterVolumeCallback();
669         }
670     }
671 
672     /** @hide */
CarAudioManager(Car car, IBinder service)673     public CarAudioManager(Car car, IBinder service) {
674         super(car);
675         mService = ICarAudio.Stub.asInterface(service);
676         mAudioManager = getContext().getSystemService(AudioManager.class);
677         mCarVolumeCallbacks = new CopyOnWriteArrayList<>();
678         mEventHandler = new EventHandler(getEventHandler().getLooper());
679     }
680 
681     /**
682      * Registers a {@link CarVolumeCallback} to receive volume change callbacks
683      * @param callback {@link CarVolumeCallback} instance, can not be null
684      * <p>
685      * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME
686      */
registerCarVolumeCallback(@onNull CarVolumeCallback callback)687     public void registerCarVolumeCallback(@NonNull CarVolumeCallback callback) {
688         Objects.requireNonNull(callback);
689 
690         if (mCarVolumeCallbacks.isEmpty()) {
691             registerVolumeCallback();
692         }
693 
694         mCarVolumeCallbacks.add(callback);
695     }
696 
697     /**
698      * Unregisters a {@link CarVolumeCallback} from receiving volume change callbacks
699      * @param callback {@link CarVolumeCallback} instance previously registered, can not be null
700      * <p>
701      * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME
702      */
unregisterCarVolumeCallback(@onNull CarVolumeCallback callback)703     public void unregisterCarVolumeCallback(@NonNull CarVolumeCallback callback) {
704         Objects.requireNonNull(callback);
705         if (mCarVolumeCallbacks.remove(callback) && mCarVolumeCallbacks.isEmpty()) {
706             unregisterVolumeCallback();
707         }
708     }
709 
registerVolumeCallback()710     private void registerVolumeCallback() {
711         try {
712             mService.registerVolumeCallback(mCarVolumeCallbackImpl.asBinder());
713         } catch (RemoteException e) {
714             Log.e(CarLibLog.TAG_CAR, "registerVolumeCallback failed", e);
715         }
716     }
717 
unregisterVolumeCallback()718     private void unregisterVolumeCallback() {
719         try {
720             mService.unregisterVolumeCallback(mCarVolumeCallbackImpl.asBinder());
721         } catch (RemoteException e) {
722             handleRemoteExceptionFromCarService(e);
723         }
724     }
725 
726     /**
727      * Returns the whether a volume group is muted
728      *
729      * <p><b>Note:<b/> If {@link #AUDIO_FEATURE_VOLUME_GROUP_MUTING} is disabled this will always
730      * return {@code false} as group mute is disabled.
731      *
732      * @param zoneId The zone id whose volume groups is queried.
733      * @param groupId The volume group id whose mute state is returned.
734      * @return {@code true} if the volume group is muted, {@code false}
735      * otherwise
736      *
737      * @hide
738      */
739     @SystemApi
740     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
isVolumeGroupMuted(int zoneId, int groupId)741     public boolean isVolumeGroupMuted(int zoneId, int groupId) {
742         try {
743             return mService.isVolumeGroupMuted(zoneId, groupId);
744         } catch (RemoteException e) {
745             return handleRemoteExceptionFromCarService(e, false);
746         }
747     }
748 
749     /**
750      * Sets a volume group mute
751      *
752      * <p><b>Note:<b/> If {@link #AUDIO_FEATURE_VOLUME_GROUP_MUTING} is disabled this will throw an
753      * error indicating the issue.
754      *
755      * @param zoneId The zone id whose volume groups will be changed.
756      * @param groupId The volume group id whose mute state will be changed.
757      * @param mute {@code true} to mute volume group, {@code false} otherwise
758      * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI},
759      * {@link android.media.AudioManager#FLAG_PLAY_SOUND})
760      *
761      * @hide
762      */
763     @SystemApi
764     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags)765     public void setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags) {
766         try {
767             mService.setVolumeGroupMute(zoneId, groupId, mute, flags);
768         } catch (RemoteException e) {
769             handleRemoteExceptionFromCarService(e);
770         }
771     }
772 
convertInputDevicesToDeviceInfos( List<AudioDeviceAttributes> devices, @AudioDeviceRole int flag)773     private List<AudioDeviceInfo> convertInputDevicesToDeviceInfos(
774             List<AudioDeviceAttributes> devices, @AudioDeviceRole int flag) {
775         int addressesSize = devices.size();
776         Set<String> deviceAddressMap = new HashSet<>(addressesSize);
777         for (int i = 0; i < addressesSize; ++i) {
778             AudioDeviceAttributes device = devices.get(i);
779             deviceAddressMap.add(device.getAddress());
780         }
781         List<AudioDeviceInfo> deviceInfoList = new ArrayList<>(devices.size());
782         AudioDeviceInfo[] inputDevices = mAudioManager.getDevices(flag);
783         for (int i = 0; i < inputDevices.length; ++i) {
784             AudioDeviceInfo info = inputDevices[i];
785             if (info.isSource() && deviceAddressMap.contains(info.getAddress())) {
786                 deviceInfoList.add(info);
787             }
788         }
789         return deviceInfoList;
790     }
791 
792     private final class EventHandler extends Handler {
793         private static final int MSG_GROUP_VOLUME_CHANGE = 1;
794         private static final int MSG_GROUP_MUTE_CHANGE = 2;
795         private static final int MSG_MASTER_MUTE_CHANGE = 3;
796 
EventHandler(Looper looper)797         private EventHandler(Looper looper) {
798             super(looper);
799         }
800 
801         @Override
handleMessage(Message msg)802         public void handleMessage(Message msg) {
803             switch (msg.what) {
804                 case MSG_GROUP_VOLUME_CHANGE:
805                     VolumeGroupChangeInfo volumeInfo = (VolumeGroupChangeInfo) msg.obj;
806                     handleOnGroupVolumeChanged(volumeInfo.mZoneId, volumeInfo.mGroupId,
807                             volumeInfo.mFlags);
808                     break;
809                 case MSG_GROUP_MUTE_CHANGE:
810                     VolumeGroupChangeInfo muteInfo = (VolumeGroupChangeInfo) msg.obj;
811                     handleOnGroupMuteChanged(muteInfo.mZoneId, muteInfo.mGroupId, muteInfo.mFlags);
812                     break;
813                 case MSG_MASTER_MUTE_CHANGE:
814                     handleOnMasterMuteChanged(msg.arg1, msg.arg2);
815                     break;
816                 default:
817                     Log.e(CarLibLog.TAG_CAR, "Unknown nessage not handled:" + msg.what);
818                     break;
819             }
820         }
821 
dispatchOnGroupVolumeChanged(int zoneId, int groupId, int flags)822         private void dispatchOnGroupVolumeChanged(int zoneId, int groupId, int flags) {
823             VolumeGroupChangeInfo volumeInfo = new VolumeGroupChangeInfo(zoneId, groupId, flags);
824             sendMessage(obtainMessage(MSG_GROUP_VOLUME_CHANGE, volumeInfo));
825         }
826 
dispatchOnMasterMuteChanged(int zoneId, int flags)827         private void dispatchOnMasterMuteChanged(int zoneId, int flags) {
828             sendMessage(obtainMessage(MSG_MASTER_MUTE_CHANGE, zoneId, flags));
829         }
830 
dispatchOnGroupMuteChanged(int zoneId, int groupId, int flags)831         private void dispatchOnGroupMuteChanged(int zoneId, int groupId, int flags) {
832             VolumeGroupChangeInfo volumeInfo = new VolumeGroupChangeInfo(zoneId, groupId, flags);
833             sendMessage(obtainMessage(MSG_GROUP_MUTE_CHANGE, volumeInfo));
834         }
835 
836         private class VolumeGroupChangeInfo {
837             public int mZoneId;
838             public int mGroupId;
839             public int mFlags;
840 
VolumeGroupChangeInfo(int zoneId, int groupId, int flags)841             VolumeGroupChangeInfo(int zoneId, int groupId, int flags) {
842                 mZoneId = zoneId;
843                 mGroupId = groupId;
844                 mFlags = flags;
845             }
846         }
847     }
848 
handleOnGroupVolumeChanged(int zoneId, int groupId, int flags)849     private void handleOnGroupVolumeChanged(int zoneId, int groupId, int flags) {
850         for (CarVolumeCallback callback : mCarVolumeCallbacks) {
851             callback.onGroupVolumeChanged(zoneId, groupId, flags);
852         }
853     }
854 
handleOnMasterMuteChanged(int zoneId, int flags)855     private void handleOnMasterMuteChanged(int zoneId, int flags) {
856         for (CarVolumeCallback callback : mCarVolumeCallbacks) {
857             callback.onMasterMuteChanged(zoneId, flags);
858         }
859     }
860 
handleOnGroupMuteChanged(int zoneId, int groupId, int flags)861     private void handleOnGroupMuteChanged(int zoneId, int groupId, int flags) {
862         for (CarVolumeCallback callback : mCarVolumeCallbacks) {
863             callback.onGroupMuteChanged(zoneId, groupId, flags);
864         }
865     }
866 
867     /**
868      * Callback interface to receive volume change events in a car.
869      * Extend this class and register it with {@link #registerCarVolumeCallback(CarVolumeCallback)}
870      * and unregister it via {@link #unregisterCarVolumeCallback(CarVolumeCallback)}
871      */
872     public abstract static class CarVolumeCallback {
873         /**
874          * This is called whenever a group volume is changed.
875          * The changed-to volume index is not included, the caller is encouraged to
876          * get the current group volume index via CarAudioManager.
877          *
878          * @param zoneId Id of the audio zone that volume change happens
879          * @param groupId Id of the volume group that volume is changed
880          * @param flags see {@link android.media.AudioManager} for flag definitions
881          */
onGroupVolumeChanged(int zoneId, int groupId, int flags)882         public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {}
883 
884         /**
885          * This is called whenever the global mute state is changed.
886          * The changed-to global mute state is not included, the caller is encouraged to
887          * get the current global mute state via AudioManager.
888          *
889          * <p><b>Note:<b/> If {@link CarAudioManager#AUDIO_FEATURE_VOLUME_GROUP_MUTING} is disabled
890          * this will be triggered on mute changes. Otherwise, car audio mute changes will trigger
891          * {@link #onGroupMuteChanged(int, int, int)}
892          *
893          * @param zoneId Id of the audio zone that global mute state change happens
894          * @param flags see {@link android.media.AudioManager} for flag definitions
895          */
onMasterMuteChanged(int zoneId, int flags)896         public void onMasterMuteChanged(int zoneId, int flags) {}
897 
898         /**
899          * This is called whenever a group mute state is changed.
900          * The changed-to mute state is not included, the caller is encouraged to
901          * get the current group mute state via CarAudioManager.
902          *
903          * <p><b>Note:<b/> If {@link CarAudioManager#AUDIO_FEATURE_VOLUME_GROUP_MUTING} is enabled
904          * this will be triggered on mute changes. Otherwise, car audio mute changes will trigger
905          * {@link #onMasterMuteChanged(int, int)}
906          *
907          * @param zoneId Id of the audio zone that volume change happens
908          * @param groupId Id of the volume group that volume is changed
909          * @param flags see {@link android.media.AudioManager} for flag definitions
910          */
onGroupMuteChanged(int zoneId, int groupId, int flags)911         public void onGroupMuteChanged(int zoneId, int groupId, int flags) {}
912     }
913 }
914