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.NonNull;
19 import android.annotation.Nullable;
20 import android.annotation.RequiresPermission;
21 import android.annotation.SystemApi;
22 import android.annotation.TestApi;
23 import android.car.Car;
24 import android.car.CarLibLog;
25 import android.car.CarManagerBase;
26 import android.media.AudioAttributes;
27 import android.media.AudioDeviceAttributes;
28 import android.media.AudioDeviceInfo;
29 import android.media.AudioManager;
30 import android.media.AudioManager.AudioDeviceRole;
31 import android.os.Bundle;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.util.Log;
35 
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Objects;
41 import java.util.Set;
42 import java.util.concurrent.CopyOnWriteArrayList;
43 
44 /**
45  * APIs for handling audio in a car.
46  *
47  * In a car environment, we introduced the support to turn audio dynamic routing on /off by
48  * setting the "audioUseDynamicRouting" attribute in config.xml
49  *
50  * When audio dynamic routing is enabled:
51  * - Audio devices are grouped into zones
52  * - There is at least one primary zone, and extra secondary zones such as RSE
53  *   (Reat Seat Entertainment)
54  * - Within each zone, audio devices are grouped into volume groups for volume control
55  * - Audio is assigned to an audio device based on its AudioAttributes usage
56  *
57  * When audio dynamic routing is disabled:
58  * - There is exactly one audio zone, which is the primary zone
59  * - Each volume group represents a controllable STREAM_TYPE, same as AudioManager
60  */
61 public final class CarAudioManager extends CarManagerBase {
62 
63     /**
64      * Zone id of the primary audio zone.
65      * @hide
66      */
67     @SystemApi
68     public static final int PRIMARY_AUDIO_ZONE = 0x0;
69 
70     /**
71      * Zone id of the invalid audio zone.
72      * @hide
73      */
74     @SystemApi
75     public static final int INVALID_AUDIO_ZONE = 0xffffffff;
76 
77     /**
78      * Volume Group ID when volume group not found.
79      * @hide
80      */
81     public static final int INVALID_VOLUME_GROUP_ID = -1;
82 
83     /**
84      * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an
85      * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus events,
86      * including {@link android.media.AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
87      * The requester must hold {@link Car#PERMISSION_RECEIVE_CAR_AUDIO_DUCKING_EVENTS}; otherwise,
88      * this extra is ignored.
89      *
90      * @hide
91      */
92     @SystemApi
93     public static final String AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS =
94             "android.car.media.AUDIOFOCUS_EXTRA_RECEIVE_DUCKING_EVENTS";
95 
96     /**
97      * Extra for {@link android.media.AudioAttributes.Builder#addBundle(Bundle)}: when used in an
98      * {@link android.media.AudioFocusRequest}, the requester should receive all audio focus for the
99      * the zone. If the zone id is not defined: the audio focus request will default to the
100      * currently mapped zone for the requesting uid or {@link CarAudioManager.PRIMARY_AUDIO_ZONE}
101      * if no uid mapping currently exist.
102      *
103      * @hide
104      */
105     public static final String AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID =
106             "android.car.media.AUDIOFOCUS_EXTRA_REQUEST_ZONE_ID";
107 
108     private final ICarAudio mService;
109     private final CopyOnWriteArrayList<CarVolumeCallback> mCarVolumeCallbacks;
110     private final AudioManager mAudioManager;
111 
112     private final ICarVolumeCallback mCarVolumeCallbackImpl = new ICarVolumeCallback.Stub() {
113         @Override
114         public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
115             for (CarVolumeCallback callback : mCarVolumeCallbacks) {
116                 callback.onGroupVolumeChanged(zoneId, groupId, flags);
117             }
118         }
119 
120         @Override
121         public void onMasterMuteChanged(int zoneId, int flags) {
122             for (CarVolumeCallback callback : mCarVolumeCallbacks) {
123                 callback.onMasterMuteChanged(zoneId, flags);
124             }
125         }
126     };
127 
128     /**
129      * @return Whether dynamic routing is enabled or not.
130      * @hide
131      */
132     @TestApi
isDynamicRoutingEnabled()133     public boolean isDynamicRoutingEnabled() {
134         try {
135             return mService.isDynamicRoutingEnabled();
136         } catch (RemoteException e) {
137             return handleRemoteExceptionFromCarService(e, false);
138         }
139     }
140 
141     /**
142      * Sets the volume index for a volume group in primary zone.
143      *
144      * @see {@link #setGroupVolume(int, int, int, int)}
145      * @hide
146      */
147     @SystemApi
148     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
setGroupVolume(int groupId, int index, int flags)149     public void setGroupVolume(int groupId, int index, int flags) {
150         setGroupVolume(PRIMARY_AUDIO_ZONE, groupId, index, flags);
151     }
152 
153     /**
154      * Sets the volume index for a volume group.
155      *
156      * @param zoneId The zone id whose volume group is affected.
157      * @param groupId The volume group id whose volume index should be set.
158      * @param index The volume index to set. See
159      *            {@link #getGroupMaxVolume(int, int)} for the largest valid value.
160      * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI},
161      *              {@link android.media.AudioManager#FLAG_PLAY_SOUND})
162      * @hide
163      */
164     @SystemApi
165     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
setGroupVolume(int zoneId, int groupId, int index, int flags)166     public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
167         try {
168             mService.setGroupVolume(zoneId, groupId, index, flags);
169         } catch (RemoteException e) {
170             handleRemoteExceptionFromCarService(e);
171         }
172     }
173 
174     /**
175      * Returns the maximum volume index for a volume group in primary zone.
176      *
177      * @see {@link #getGroupMaxVolume(int, int)}
178      * @hide
179      */
180     @SystemApi
181     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupMaxVolume(int groupId)182     public int getGroupMaxVolume(int groupId) {
183         return getGroupMaxVolume(PRIMARY_AUDIO_ZONE, groupId);
184     }
185 
186     /**
187      * Returns the maximum volume index for a volume group.
188      *
189      * @param zoneId The zone id whose volume group is queried.
190      * @param groupId The volume group id whose maximum volume index is returned.
191      * @return The maximum valid volume index for the given group.
192      * @hide
193      */
194     @SystemApi
195     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupMaxVolume(int zoneId, int groupId)196     public int getGroupMaxVolume(int zoneId, int groupId) {
197         try {
198             return mService.getGroupMaxVolume(zoneId, groupId);
199         } catch (RemoteException e) {
200             return handleRemoteExceptionFromCarService(e, 0);
201         }
202     }
203 
204     /**
205      * Returns the minimum volume index for a volume group in primary zone.
206      *
207      * @see {@link #getGroupMinVolume(int, int)}
208      * @hide
209      */
210     @SystemApi
211     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupMinVolume(int groupId)212     public int getGroupMinVolume(int groupId) {
213         return getGroupMinVolume(PRIMARY_AUDIO_ZONE, groupId);
214     }
215 
216     /**
217      * Returns the minimum volume index for a volume group.
218      *
219      * @param zoneId The zone id whose volume group is queried.
220      * @param groupId The volume group id whose minimum volume index is returned.
221      * @return The minimum valid volume index for the given group, non-negative
222      * @hide
223      */
224     @SystemApi
225     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupMinVolume(int zoneId, int groupId)226     public int getGroupMinVolume(int zoneId, int groupId) {
227         try {
228             return mService.getGroupMinVolume(zoneId, groupId);
229         } catch (RemoteException e) {
230             return handleRemoteExceptionFromCarService(e, 0);
231         }
232     }
233 
234     /**
235      * Returns the current volume index for a volume group in primary zone.
236      *
237      * @see {@link #getGroupVolume(int, int)}
238      * @hide
239      */
240     @SystemApi
241     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupVolume(int groupId)242     public int getGroupVolume(int groupId) {
243         return getGroupVolume(PRIMARY_AUDIO_ZONE, groupId);
244     }
245 
246     /**
247      * Returns the current volume index for a volume group.
248      *
249      * @param zoneId The zone id whose volume groups is queried.
250      * @param groupId The volume group id whose volume index is returned.
251      * @return The current volume index for the given group.
252      *
253      * @see #getGroupMaxVolume(int, int)
254      * @see #setGroupVolume(int, int, int, int)
255      * @hide
256      */
257     @SystemApi
258     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getGroupVolume(int zoneId, int groupId)259     public int getGroupVolume(int zoneId, int groupId) {
260         try {
261             return mService.getGroupVolume(zoneId, groupId);
262         } catch (RemoteException e) {
263             return handleRemoteExceptionFromCarService(e, 0);
264         }
265     }
266 
267     /**
268      * Adjust the relative volume in the front vs back of the vehicle cabin.
269      *
270      * @param value in the range -1.0 to 1.0 for fully toward the back through
271      *              fully toward the front.  0.0 means evenly balanced.
272      *
273      * @see #setBalanceTowardRight(float)
274      * @hide
275      */
276     @SystemApi
277     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
setFadeTowardFront(float value)278     public void setFadeTowardFront(float value) {
279         try {
280             mService.setFadeTowardFront(value);
281         } catch (RemoteException e) {
282             handleRemoteExceptionFromCarService(e);
283         }
284     }
285 
286     /**
287      * Adjust the relative volume on the left vs right side of the vehicle cabin.
288      *
289      * @param value in the range -1.0 to 1.0 for fully toward the left through
290      *              fully toward the right.  0.0 means evenly balanced.
291      *
292      * @see #setFadeTowardFront(float)
293      * @hide
294      */
295     @SystemApi
296     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
setBalanceTowardRight(float value)297     public void setBalanceTowardRight(float value) {
298         try {
299             mService.setBalanceTowardRight(value);
300         } catch (RemoteException e) {
301             handleRemoteExceptionFromCarService(e);
302         }
303     }
304 
305     /**
306      * Queries the system configuration in order to report the available, non-microphone audio
307      * input devices.
308      *
309      * @return An array of strings representing the available input ports.
310      * Each port is identified by it's "address" tag in the audioPolicyConfiguration xml file.
311      * Empty array if we find nothing.
312      *
313      * @see #createAudioPatch(String, int, int)
314      * @see #releaseAudioPatch(CarAudioPatchHandle)
315      * @hide
316      */
317     @SystemApi
318     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getExternalSources()319     public @NonNull String[] getExternalSources() {
320         try {
321             return mService.getExternalSources();
322         } catch (RemoteException e) {
323             handleRemoteExceptionFromCarService(e);
324             return new String[0];
325         }
326     }
327 
328     /**
329      * Given an input port identified by getExternalSources(), request that it's audio signal
330      * be routed below the HAL to the output port associated with the given usage.  For example,
331      * The output of a tuner might be routed directly to the output buss associated with
332      * AudioAttributes.USAGE_MEDIA while the tuner is playing.
333      *
334      * @param sourceAddress the input port name obtained from getExternalSources().
335      * @param usage the type of audio represented by this source (usually USAGE_MEDIA).
336      * @param gainInMillibels How many steps above the minimum value defined for the source port to
337      *                       set the gain when creating the patch.
338      *                       This may be used for source balancing without affecting the user
339      *                       controlled volumes applied to the destination ports.  A value of
340      *                       0 indicates no gain change is requested.
341      * @return A handle for the created patch which can be used to later remove it.
342      *
343      * @see #getExternalSources()
344      * @see #releaseAudioPatch(CarAudioPatchHandle)
345      * @hide
346      */
347     @SystemApi
348     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
createAudioPatch(String sourceAddress, @AudioAttributes.AttributeUsage int usage, int gainInMillibels)349     public CarAudioPatchHandle createAudioPatch(String sourceAddress,
350             @AudioAttributes.AttributeUsage int usage, int gainInMillibels) {
351         try {
352             return mService.createAudioPatch(sourceAddress, usage, gainInMillibels);
353         } catch (RemoteException e) {
354             return handleRemoteExceptionFromCarService(e, null);
355         }
356     }
357 
358     /**
359      * Removes the association between an input port and an output port identified by the provided
360      * handle.
361      *
362      * @param patch CarAudioPatchHandle returned from createAudioPatch().
363      *
364      * @see #getExternalSources()
365      * @see #createAudioPatch(String, int, int)
366      * @hide
367      */
368     @SystemApi
369     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
releaseAudioPatch(CarAudioPatchHandle patch)370     public void releaseAudioPatch(CarAudioPatchHandle patch) {
371         try {
372             mService.releaseAudioPatch(patch);
373         } catch (RemoteException e) {
374             handleRemoteExceptionFromCarService(e);
375         }
376     }
377 
378     /**
379      * Gets the count of available volume groups in primary zone.
380      *
381      * @see {@link #getVolumeGroupCount(int)}
382      * @hide
383      */
384     @SystemApi
385     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getVolumeGroupCount()386     public int getVolumeGroupCount() {
387         return getVolumeGroupCount(PRIMARY_AUDIO_ZONE);
388     }
389 
390     /**
391      * Gets the count of available volume groups in the system.
392      *
393      * @param zoneId The zone id whois count of volume groups is queried.
394      * @return Count of volume groups
395      * @hide
396      */
397     @SystemApi
398     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getVolumeGroupCount(int zoneId)399     public int getVolumeGroupCount(int zoneId) {
400         try {
401             return mService.getVolumeGroupCount(zoneId);
402         } catch (RemoteException e) {
403             return handleRemoteExceptionFromCarService(e, 0);
404         }
405     }
406 
407     /**
408      * Gets the volume group id for a given {@link AudioAttributes} usage in primary zone.
409      *
410      * @see {@link #getVolumeGroupIdForUsage(int, int)}
411      * @hide
412      */
413     @SystemApi
414     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getVolumeGroupIdForUsage(@udioAttributes.AttributeUsage int usage)415     public int getVolumeGroupIdForUsage(@AudioAttributes.AttributeUsage int usage) {
416         return getVolumeGroupIdForUsage(PRIMARY_AUDIO_ZONE, usage);
417     }
418 
419     /**
420      * Gets the volume group id for a given {@link AudioAttributes} usage.
421      *
422      * @param zoneId The zone id whose volume group is queried.
423      * @param usage The {@link AudioAttributes} usage to get a volume group from.
424      * @return The volume group id where the usage belongs to
425      * @hide
426      */
427     @SystemApi
428     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)429     public int getVolumeGroupIdForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage) {
430         try {
431             return mService.getVolumeGroupIdForUsage(zoneId, usage);
432         } catch (RemoteException e) {
433             return handleRemoteExceptionFromCarService(e, 0);
434         }
435     }
436 
437     /**
438      * Gets array of {@link AudioAttributes} usages for a volume group in primary zone.
439      *
440      * @see {@link #getUsagesForVolumeGroupId(int, int)}
441      * @hide
442      */
443     @SystemApi
444     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getUsagesForVolumeGroupId(int groupId)445     public @NonNull int[] getUsagesForVolumeGroupId(int groupId) {
446         return getUsagesForVolumeGroupId(PRIMARY_AUDIO_ZONE, groupId);
447     }
448 
449     /**
450      * Gets array of {@link AudioAttributes} usages for a volume group in a zone.
451      *
452      * @param zoneId The zone id whose volume group is queried.
453      * @param groupId The volume group id whose associated audio usages is returned.
454      * @return Array of {@link AudioAttributes} usages for a given volume group id
455      * @hide
456      */
457     @SystemApi
458     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
getUsagesForVolumeGroupId(int zoneId, int groupId)459     public @NonNull int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
460         try {
461             return mService.getUsagesForVolumeGroupId(zoneId, groupId);
462         } catch (RemoteException e) {
463             return handleRemoteExceptionFromCarService(e, new int[0]);
464         }
465     }
466 
467     /**
468      * Gets the audio zones currently available
469      *
470      * @return audio zone ids
471      * @hide
472      */
473     @SystemApi
474     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getAudioZoneIds()475     public @NonNull List<Integer> getAudioZoneIds() {
476         try {
477             int[] zoneIdArray = mService.getAudioZoneIds();
478             List<Integer> zoneIdList = new ArrayList<Integer>(zoneIdArray.length);
479             for (int zoneIdValue : zoneIdArray) {
480                 zoneIdList.add(zoneIdValue);
481             }
482             return zoneIdList;
483         } catch (RemoteException e) {
484             return handleRemoteExceptionFromCarService(e, Collections.emptyList());
485         }
486     }
487 
488     /**
489      * Gets the audio zone id currently mapped to uId,
490      * defaults to PRIMARY_AUDIO_ZONE if no mapping exist
491      *
492      * @param uid The uid to map
493      * @return zone id mapped to uid
494      * @hide
495      */
496     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getZoneIdForUid(int uid)497     public int getZoneIdForUid(int uid) {
498         try {
499             return mService.getZoneIdForUid(uid);
500         } catch (RemoteException e) {
501             return handleRemoteExceptionFromCarService(e, 0);
502         }
503     }
504 
505     /**
506      * Maps the audio zone id to uid
507      *
508      * @param zoneId The audio zone id
509      * @param uid The uid to map
510      * @return true if the uid is successfully mapped
511      * @hide
512      */
513     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
setZoneIdForUid(int zoneId, int uid)514     public boolean setZoneIdForUid(int zoneId, int uid) {
515         try {
516             return mService.setZoneIdForUid(zoneId, uid);
517         } catch (RemoteException e) {
518             return handleRemoteExceptionFromCarService(e, false);
519         }
520     }
521 
522     /**
523      * Clears the current zone mapping of the uid
524      *
525      * @param uid The uid to clear
526      * @return true if the zone was successfully cleared
527      * @hide
528      */
529     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
clearZoneIdForUid(int uid)530     public boolean clearZoneIdForUid(int uid) {
531         try {
532             return mService.clearZoneIdForUid(uid);
533         } catch (RemoteException e) {
534             return handleRemoteExceptionFromCarService(e, false);
535         }
536     }
537 
538     /**
539      * Gets the output device for a given {@link AudioAttributes} usage in zoneId.
540      *
541      * <p><b>Note:</b> To be used for routing to a specific device. Most applications should
542      * use the regular routing mechanism, which is to set audio attribute usage to
543      * an audio track.
544      *
545      * @param zoneId zone id to query for device
546      * @param usage usage where audio is routed
547      * @return Audio device info, returns {@code null} if audio device usage fails to map to
548      * an active audio device. This is different from the using an invalid value for
549      * {@link AudioAttributes} usage. In the latter case the query will fail with a
550      * RuntimeException indicating the issue.
551      *
552      * @hide
553      */
554     @SystemApi
555     @Nullable
556     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getOutputDeviceForUsage(int zoneId, @AudioAttributes.AttributeUsage int usage)557     public AudioDeviceInfo getOutputDeviceForUsage(int zoneId,
558             @AudioAttributes.AttributeUsage int usage) {
559         try {
560             String deviceAddress = mService.getOutputDeviceAddressForUsage(zoneId, usage);
561             if (deviceAddress == null) {
562                 return null;
563             }
564             AudioDeviceInfo[] outputDevices =
565                     mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
566             for (AudioDeviceInfo info : outputDevices) {
567                 if (info.getAddress().equals(deviceAddress)) {
568                     return info;
569                 }
570             }
571             return null;
572         } catch (RemoteException e) {
573             return handleRemoteExceptionFromCarService(e, null);
574         }
575     }
576 
577     /**
578      * Gets the input devices for an audio zone
579      *
580      * @return list of input devices
581      * @hide
582      */
583     @SystemApi
584     @RequiresPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
getInputDevicesForZoneId(int zoneId)585     public @NonNull List<AudioDeviceInfo> getInputDevicesForZoneId(int zoneId) {
586         try {
587             return convertInputDevicesToDeviceInfos(
588                     mService.getInputDevicesForZoneId(zoneId),
589                     AudioManager.GET_DEVICES_INPUTS);
590         } catch (RemoteException e) {
591             return handleRemoteExceptionFromCarService(e, new ArrayList<>());
592         }
593     }
594 
595     /** @hide */
596     @Override
onCarDisconnected()597     public void onCarDisconnected() {
598         if (mService != null) {
599             unregisterVolumeCallback();
600         }
601     }
602 
603     /** @hide */
CarAudioManager(Car car, IBinder service)604     public CarAudioManager(Car car, IBinder service) {
605         super(car);
606         mService = ICarAudio.Stub.asInterface(service);
607         mAudioManager = getContext().getSystemService(AudioManager.class);
608         mCarVolumeCallbacks = new CopyOnWriteArrayList<>();
609     }
610 
611     /**
612      * Registers a {@link CarVolumeCallback} to receive volume change callbacks
613      * @param callback {@link CarVolumeCallback} instance, can not be null
614      * <p>
615      * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME
616      */
registerCarVolumeCallback(@onNull CarVolumeCallback callback)617     public void registerCarVolumeCallback(@NonNull CarVolumeCallback callback) {
618         Objects.requireNonNull(callback);
619 
620         if (mCarVolumeCallbacks.isEmpty()) {
621             registerVolumeCallback();
622         }
623 
624         mCarVolumeCallbacks.add(callback);
625     }
626 
627     /**
628      * Unregisters a {@link CarVolumeCallback} from receiving volume change callbacks
629      * @param callback {@link CarVolumeCallback} instance previously registered, can not be null
630      * <p>
631      * Requires permission Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME
632      */
unregisterCarVolumeCallback(@onNull CarVolumeCallback callback)633     public void unregisterCarVolumeCallback(@NonNull CarVolumeCallback callback) {
634         Objects.requireNonNull(callback);
635         if (mCarVolumeCallbacks.remove(callback) && mCarVolumeCallbacks.isEmpty()) {
636             unregisterVolumeCallback();
637         }
638     }
639 
registerVolumeCallback()640     private void registerVolumeCallback() {
641         try {
642             mService.registerVolumeCallback(mCarVolumeCallbackImpl.asBinder());
643         } catch (RemoteException e) {
644             Log.e(CarLibLog.TAG_CAR, "registerVolumeCallback failed", e);
645         }
646     }
647 
unregisterVolumeCallback()648     private void unregisterVolumeCallback() {
649         try {
650             mService.unregisterVolumeCallback(mCarVolumeCallbackImpl.asBinder());
651         } catch (RemoteException e) {
652             handleRemoteExceptionFromCarService(e);
653         }
654     }
655 
convertInputDevicesToDeviceInfos( List<AudioDeviceAttributes> devices, @AudioDeviceRole int flag)656     private List<AudioDeviceInfo> convertInputDevicesToDeviceInfos(
657             List<AudioDeviceAttributes> devices, @AudioDeviceRole int flag) {
658         int addressesSize = devices.size();
659         Set<String> deviceAddressMap = new HashSet<>(addressesSize);
660         for (int i = 0; i < addressesSize; ++i) {
661             AudioDeviceAttributes device = devices.get(i);
662             deviceAddressMap.add(device.getAddress());
663         }
664         List<AudioDeviceInfo> deviceInfoList = new ArrayList<>(devices.size());
665         AudioDeviceInfo[] inputDevices = mAudioManager.getDevices(flag);
666         for (int i = 0; i < inputDevices.length; ++i) {
667             AudioDeviceInfo info = inputDevices[i];
668             if (info.isSource() && deviceAddressMap.contains(info.getAddress())) {
669                 deviceInfoList.add(info);
670             }
671         }
672         return deviceInfoList;
673     }
674 
675     /**
676      * Callback interface to receive volume change events in a car.
677      * Extend this class and register it with {@link #registerCarVolumeCallback(CarVolumeCallback)}
678      * and unregister it via {@link #unregisterCarVolumeCallback(CarVolumeCallback)}
679      */
680     public abstract static class CarVolumeCallback {
681         /**
682          * This is called whenever a group volume is changed.
683          * The changed-to volume index is not included, the caller is encouraged to
684          * get the current group volume index via CarAudioManager.
685          *
686          * @param zoneId Id of the audio zone that volume change happens
687          * @param groupId Id of the volume group that volume is changed
688          * @param flags see {@link android.media.AudioManager} for flag definitions
689          */
onGroupVolumeChanged(int zoneId, int groupId, int flags)690         public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {}
691 
692         /**
693          * This is called whenever the master mute state is changed.
694          * The changed-to master mute state is not included, the caller is encouraged to
695          * get the current master mute state via AudioManager.
696          *
697          * @param zoneId Id of the audio zone that master mute state change happens
698          * @param flags see {@link android.media.AudioManager} for flag definitions
699          */
onMasterMuteChanged(int zoneId, int flags)700         public void onMasterMuteChanged(int zoneId, int flags) {}
701     }
702 }
703