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.Nullable;
20 import android.annotation.SystemApi;
21 import android.car.CarLibLog;
22 import android.car.CarNotConnectedException;
23 import android.content.Context;
24 import android.media.AudioAttributes;
25 import android.media.AudioManager;
26 import android.media.AudioManager.OnAudioFocusChangeListener;
27 import android.media.IVolumeController;
28 import android.os.Handler;
29 import android.os.IBinder;
30 import android.os.RemoteException;
31 import android.car.CarManagerBase;
32 import android.util.Log;
33 
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 import java.lang.ref.WeakReference;
37 
38 /**
39  * APIs for handling car specific audio stuffs.
40  */
41 public final class CarAudioManager implements CarManagerBase {
42 
43     /**
44      * Audio usage for unspecified type.
45      */
46     public static final int CAR_AUDIO_USAGE_DEFAULT = 0;
47     /**
48      * Audio usage for playing music.
49      */
50     public static final int CAR_AUDIO_USAGE_MUSIC = 1;
51     /**
52      * Audio usage for H/W radio.
53      */
54     public static final int CAR_AUDIO_USAGE_RADIO = 2;
55     /**
56      * Audio usage for playing navigation guidance.
57      */
58     public static final int CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE = 3;
59     /**
60      * Audio usage for voice call
61      */
62     public static final int CAR_AUDIO_USAGE_VOICE_CALL = 4;
63     /**
64      * Audio usage for voice search or voice command.
65      */
66     public static final int CAR_AUDIO_USAGE_VOICE_COMMAND = 5;
67     /**
68      * Audio usage for playing alarm.
69      */
70     public static final int CAR_AUDIO_USAGE_ALARM = 6;
71     /**
72      * Audio usage for notification sound.
73      */
74     public static final int CAR_AUDIO_USAGE_NOTIFICATION = 7;
75     /**
76      * Audio usage for system sound like UI feedback.
77      */
78     public static final int CAR_AUDIO_USAGE_SYSTEM_SOUND = 8;
79     /**
80      * Audio usage for playing safety alert.
81      */
82     public static final int CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT = 9;
83     /**
84      * Audio usage for external audio usage.
85      * @hide
86      */
87     public static final int CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE = 10;
88 
89     /** @hide */
90     public static final int CAR_AUDIO_USAGE_MAX = CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE;
91 
92     /** @hide */
93     @IntDef({CAR_AUDIO_USAGE_DEFAULT, CAR_AUDIO_USAGE_MUSIC, CAR_AUDIO_USAGE_RADIO,
94         CAR_AUDIO_USAGE_NAVIGATION_GUIDANCE, CAR_AUDIO_USAGE_VOICE_CALL,
95         CAR_AUDIO_USAGE_VOICE_COMMAND, CAR_AUDIO_USAGE_ALARM, CAR_AUDIO_USAGE_NOTIFICATION,
96         CAR_AUDIO_USAGE_SYSTEM_SOUND, CAR_AUDIO_USAGE_SYSTEM_SAFETY_ALERT,
97         CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE})
98     @Retention(RetentionPolicy.SOURCE)
99     public @interface CarAudioUsage {}
100 
101     /** @hide */
102     public static final String CAR_RADIO_TYPE_AM_FM = "RADIO_AM_FM";
103     /** @hide */
104     public static final String CAR_RADIO_TYPE_AM_FM_HD = "RADIO_AM_FM_HD";
105     /** @hide */
106     public static final String CAR_RADIO_TYPE_DAB = "RADIO_DAB";
107     /** @hide */
108     public static final String CAR_RADIO_TYPE_SATELLITE = "RADIO_SATELLITE";
109 
110     /** @hide */
111     public static final String CAR_EXTERNAL_SOURCE_TYPE_CD_DVD = "CD_DVD";
112     /** @hide */
113     public static final String CAR_EXTERNAL_SOURCE_TYPE_AUX_IN0 = "AUX_IN0";
114     /** @hide */
115     public static final String CAR_EXTERNAL_SOURCE_TYPE_AUX_IN1 = "AUX_IN1";
116     /** @hide */
117     public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_NAV_GUIDANCE = "EXT_NAV_GUIDANCE";
118     /** @hide */
119     public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_VOICE_CALL = "EXT_VOICE_CALL";
120     /** @hide */
121     public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_VOICE_COMMAND = "EXT_VOICE_COMMAND";
122     /** @hide */
123     public static final String CAR_EXTERNAL_SOURCE_TYPE_EXT_SAFETY_ALERT = "EXT_SAFETY_ALERT";
124 
125     private final ICarAudio mService;
126     private final AudioManager mAudioManager;
127     private final Handler mHandler;
128 
129     private ParameterChangeCallback mParameterChangeCallback;
130     private OnParameterChangeListener mOnParameterChangeListener;
131 
132     /**
133      * Get {@link AudioAttributes} relevant for the given usage in car.
134      * @param carUsage
135      * @return
136      */
getAudioAttributesForCarUsage(@arAudioUsage int carUsage)137     public AudioAttributes getAudioAttributesForCarUsage(@CarAudioUsage int carUsage)
138             throws CarNotConnectedException {
139         try {
140             return mService.getAudioAttributesForCarUsage(carUsage);
141         } catch (RemoteException e) {
142             throw new CarNotConnectedException();
143         }
144     }
145 
146     /**
147      * Get AudioAttributes for radio. This is necessary when there are multiple types of radio
148      * in system.
149      *
150      * @param radioType String specifying the desired radio type. Should use only what is listed in
151      *        {@link #getSupportedRadioTypes()}.
152      * @return
153      * @throws IllegalArgumentException If not supported type is passed.
154      *
155      * @hide
156      */
getAudioAttributesForRadio(String radioType)157     public AudioAttributes getAudioAttributesForRadio(String radioType)
158             throws CarNotConnectedException, IllegalArgumentException {
159         try {
160             return mService.getAudioAttributesForRadio(radioType);
161         } catch (RemoteException e) {
162             throw new CarNotConnectedException();
163         }
164     }
165 
166     /**
167      * Get AudioAttributes for external audio source.
168      *
169      * @param externalSourceType String specifying the desired source type. Should use only what is
170      *        listed in {@link #getSupportedExternalSourceTypes()}.
171      * @return
172      * @throws IllegalArgumentException If not supported type is passed.
173      *
174      * @hide
175      */
getAudioAttributesForExternalSource(String externalSourceType)176     public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType)
177             throws CarNotConnectedException, IllegalArgumentException {
178         try {
179             return mService.getAudioAttributesForExternalSource(externalSourceType);
180         } catch (RemoteException e) {
181             throw new CarNotConnectedException();
182         }
183     }
184 
185     /**
186      * List all supported external audio sources.
187      *
188      * @return
189      *
190      * @hide
191      */
getSupportedExternalSourceTypes()192     public String[] getSupportedExternalSourceTypes() throws CarNotConnectedException {
193         try {
194             return mService.getSupportedExternalSourceTypes();
195         } catch (RemoteException e) {
196             throw new CarNotConnectedException();
197         }
198     }
199 
200     /**
201      * List all supported radio sources.
202      *
203      * @return
204      *
205      * @hide
206      */
getSupportedRadioTypes()207     public String[] getSupportedRadioTypes() throws CarNotConnectedException {
208         try {
209             return mService.getSupportedRadioTypes();
210         } catch (RemoteException e) {
211             throw new CarNotConnectedException();
212         }
213     }
214 
215     /**
216      * Request audio focus.
217      * Send a request to obtain the audio focus.
218      * @param l
219      * @param requestAttributes
220      * @param durationHint
221      * @param flags
222      */
requestAudioFocus(OnAudioFocusChangeListener l, AudioAttributes requestAttributes, int durationHint, int flags)223     public int requestAudioFocus(OnAudioFocusChangeListener l,
224                                  AudioAttributes requestAttributes,
225                                  int durationHint,
226                                  int flags)
227                                          throws CarNotConnectedException, IllegalArgumentException {
228         return mAudioManager.requestAudioFocus(l, requestAttributes, durationHint, flags);
229     }
230 
231     /**
232      * Abandon audio focus. Causes the previous focus owner, if any, to receive focus.
233      * @param l
234      * @param aa
235      */
abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa)236     public void abandonAudioFocus(OnAudioFocusChangeListener l, AudioAttributes aa) {
237         mAudioManager.abandonAudioFocus(l, aa);
238     }
239 
240     /**
241      * Sets the volume index for a particular stream.
242      *
243      * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
244      *
245      * @param streamType The stream whose volume index should be set.
246      * @param index The volume index to set. See
247      *            {@link #getStreamMaxVolume(int)} for the largest valid value.
248      * @param flags One or more flags (e.g., {@link android.media.AudioManager#FLAG_SHOW_UI},
249      *              {@link android.media.AudioManager#FLAG_PLAY_SOUND})
250      */
251     @SystemApi
setStreamVolume(int streamType, int index, int flags)252     public void setStreamVolume(int streamType, int index, int flags)
253             throws CarNotConnectedException {
254         try {
255             mService.setStreamVolume(streamType, index, flags);
256         } catch (RemoteException e) {
257             Log.e(CarLibLog.TAG_CAR, "setStreamVolume failed", e);
258             throw new CarNotConnectedException(e);
259         }
260     }
261 
262     /**
263      * Registers a global volume controller interface.
264      *
265      * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
266      *
267      * @hide
268      */
269     @SystemApi
setVolumeController(IVolumeController controller)270     public void setVolumeController(IVolumeController controller)
271             throws CarNotConnectedException {
272         try {
273             mService.setVolumeController(controller);
274         } catch (RemoteException e) {
275             Log.e(CarLibLog.TAG_CAR, "setVolumeController failed", e);
276             throw new CarNotConnectedException(e);
277         }
278     }
279 
280     /**
281      * Returns the maximum volume index for a particular stream.
282      *
283      * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
284      *
285      * @param stream The stream type whose maximum volume index is returned.
286      * @return The maximum valid volume index for the stream.
287      */
288     @SystemApi
getStreamMaxVolume(int stream)289     public int getStreamMaxVolume(int stream) throws CarNotConnectedException {
290         try {
291             return mService.getStreamMaxVolume(stream);
292         } catch (RemoteException e) {
293             Log.e(CarLibLog.TAG_CAR, "getStreamMaxVolume failed", e);
294             throw new CarNotConnectedException(e);
295         }
296     }
297 
298     /**
299      * Returns the minimum volume index for a particular stream.
300      *
301      * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
302      *
303      * @param stream The stream type whose maximum volume index is returned.
304      * @return The maximum valid volume index for the stream.
305      */
306     @SystemApi
getStreamMinVolume(int stream)307     public int getStreamMinVolume(int stream) throws CarNotConnectedException {
308         try {
309             return mService.getStreamMinVolume(stream);
310         } catch (RemoteException e) {
311             Log.e(CarLibLog.TAG_CAR, "getStreamMaxVolume failed", e);
312             throw new CarNotConnectedException(e);
313         }
314     }
315 
316     /**
317      * Returns the current volume index for a particular stream.
318      *
319      * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
320      *
321      * @param stream The stream type whose volume index is returned.
322      * @return The current volume index for the stream.
323      *
324      * @see #getStreamMaxVolume(int)
325      * @see #setStreamVolume(int, int, int)
326      */
327     @SystemApi
getStreamVolume(int stream)328     public int getStreamVolume(int stream) throws CarNotConnectedException {
329         try {
330             return mService.getStreamVolume(stream);
331         } catch (RemoteException e) {
332             Log.e(CarLibLog.TAG_CAR, "getStreamVolume failed", e);
333             throw new CarNotConnectedException(e);
334         }
335     }
336 
337     /**
338      * Check if media audio is muted or not. This will include music and radio. Any application
339      * taking audio focus for media stream will get it out of mute state.
340      *
341      * @return true if media is muted.
342      * @throws CarNotConnectedException if the connection to the car service has been lost.
343      * @hide
344      */
345     @SystemApi
isMediaMuted()346     public boolean isMediaMuted() throws CarNotConnectedException {
347         try {
348             return mService.isMediaMuted();
349         } catch (RemoteException e) {
350             Log.e(CarLibLog.TAG_CAR, "isMediaMuted failed", e);
351             throw new CarNotConnectedException(e);
352         }
353     }
354 
355     /**
356      * Mute or unmute media stream including radio. This can involve audio focus change to stop
357      * whatever app holding audio focus now. If requester is currently holding audio focus,
358      * it will get LOSS_TRANSIENT focus loss.
359      * This API requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME} permission.
360      *
361      * @param mute
362      * @return Mute state of system after the request. Note that mute request can fail if there
363      *         is higher priority audio already being played like phone call.
364      * @throws CarNotConnectedException if the connection to the car service has been lost.
365      * @hide
366      */
367     @SystemApi
setMediaMute(boolean mute)368     public boolean setMediaMute(boolean mute) throws CarNotConnectedException {
369         try {
370             return mService.setMediaMute(mute);
371         } catch (RemoteException e) {
372             Log.e(CarLibLog.TAG_CAR, "setMediaMute failed", e);
373             throw new CarNotConnectedException(e);
374         }
375     }
376 
377     /**
378      * Listener to monitor audio parameter changes.
379      * @hide
380      */
381     public interface OnParameterChangeListener {
382         /**
383          * Parameter changed.
384          * @param parameters Have format of key1=value1;key2=value2;...
385          */
onParameterChange(String parameters)386         void onParameterChange(String parameters);
387     }
388 
389     /**
390      * Return array of keys supported in this system.
391      * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS} permission.
392      * The list is static and will not change.
393      * @return null if there is no audio parameters supported.
394      * @throws CarNotConnectedException
395      *
396      * @hide
397      */
getParameterKeys()398     public @Nullable String[] getParameterKeys() throws CarNotConnectedException {
399         try {
400             return mService.getParameterKeys();
401         } catch (RemoteException e) {
402             Log.e(CarLibLog.TAG_CAR, "getParameterKeys failed", e);
403             throw new CarNotConnectedException(e);
404         }
405     }
406 
407     /**
408      * Set car specific audio parameters.
409      * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS} permission.
410      * Only keys listed from {@link #getParameterKeys()} should be used.
411      * @param parameters has format of key1=value1;key2=value2;...
412      * @throws CarNotConnectedException
413      *
414      * @hide
415      */
setParameters(String parameters)416     public void setParameters(String parameters) throws CarNotConnectedException {
417         try {
418             mService.setParameters(parameters);
419         } catch (RemoteException e) {
420             Log.e(CarLibLog.TAG_CAR, "setParameters failed", e);
421             throw new CarNotConnectedException(e);
422         }
423     }
424 
425     /**
426      * Get parameters for the key passed.
427      * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS} permission.
428      * Only keys listed from {@link #getParameterKeys()} should be used.
429      * @param keys Keys to get value. Format is key1;key2;...
430      * @return Parameters in format of key1=value1;key2=value2;...
431      * @throws CarNotConnectedException
432      *
433      * @hide
434      */
getParameters(String keys)435     public String getParameters(String keys) throws CarNotConnectedException {
436         try {
437             return mService.getParameters(keys);
438         } catch (RemoteException e) {
439             Log.e(CarLibLog.TAG_CAR, "getParameters failed", e);
440             throw new CarNotConnectedException(e);
441         }
442     }
443 
444     /**
445      * Set listener to monitor audio parameter changes.
446      * Requires {@link android.car.Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS} permission.
447      * @param listener Non-null listener will start monitoring. null listener will stop listening.
448      * @throws CarNotConnectedException
449      *
450      * @hide
451      */
setOnParameterChangeListener(OnParameterChangeListener listener)452     public void setOnParameterChangeListener(OnParameterChangeListener listener)
453             throws CarNotConnectedException {
454         ParameterChangeCallback oldCb = null;
455         ParameterChangeCallback newCb = null;
456         synchronized (this) {
457             if (listener != null) {
458                 if (mParameterChangeCallback != null) {
459                     oldCb = mParameterChangeCallback;
460                 }
461                 newCb = new ParameterChangeCallback(this);
462             }
463             mParameterChangeCallback = newCb;
464             mOnParameterChangeListener = listener;
465         }
466         try {
467             if (oldCb != null) {
468                 mService.unregisterOnParameterChangeListener(oldCb);
469             }
470             if (newCb != null) {
471                 mService.registerOnParameterChangeListener(newCb);
472             }
473         } catch (RemoteException e) {
474             Log.e(CarLibLog.TAG_CAR, "setOnParameterChangeListener failed", e);
475             throw new CarNotConnectedException(e);
476         }
477     }
478 
479     /** @hide */
480     @Override
onCarDisconnected()481     public void onCarDisconnected() {
482     }
483 
484     /** @hide */
CarAudioManager(IBinder service, Context context, Handler handler)485     public CarAudioManager(IBinder service, Context context, Handler handler) {
486         mService = ICarAudio.Stub.asInterface(service);
487         mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
488         mHandler = handler;
489     }
490 
createAudioAttributes(int contentType, int usage)491     private AudioAttributes createAudioAttributes(int contentType, int usage) {
492         AudioAttributes.Builder builder = new AudioAttributes.Builder();
493         return builder.setContentType(contentType).setUsage(usage).build();
494     }
495 
496     private static class ParameterChangeCallback extends ICarAudioCallback.Stub {
497 
498         private final WeakReference<CarAudioManager> mManager;
499 
ParameterChangeCallback(CarAudioManager manager)500         private ParameterChangeCallback(CarAudioManager manager) {
501             mManager = new WeakReference<>(manager);
502         }
503 
504         @Override
onParameterChange(final String params)505         public void onParameterChange(final String params) {
506             CarAudioManager manager = mManager.get();
507             if (manager == null) {
508                 return;
509             }
510             final OnParameterChangeListener listener = manager.mOnParameterChangeListener;
511             if (listener == null) {
512                 return;
513             }
514             manager.mHandler.post(new Runnable() {
515                 @Override
516                 public void run() {
517                     listener.onParameterChange(params);
518                 }
519             });
520         }
521     }
522 }
523