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 */
16package android.car.media;
17
18import android.annotation.IntDef;
19import android.annotation.Nullable;
20import android.annotation.SystemApi;
21import android.car.CarLibLog;
22import android.car.CarNotConnectedException;
23import android.content.Context;
24import android.media.AudioAttributes;
25import android.media.AudioManager;
26import android.media.AudioManager.OnAudioFocusChangeListener;
27import android.media.IVolumeController;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.RemoteException;
31import android.car.CarManagerBase;
32import android.util.Log;
33
34import java.lang.annotation.Retention;
35import java.lang.annotation.RetentionPolicy;
36import java.lang.ref.WeakReference;
37
38/**
39 * APIs for handling car specific audio stuffs.
40 */
41public 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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
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
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
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
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
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         */
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     */
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     */
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     */
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     */
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
481    public void onCarDisconnected() {
482    }
483
484    /** @hide */
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
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
500        private ParameterChangeCallback(CarAudioManager manager) {
501            mManager = new WeakReference<>(manager);
502        }
503
504        @Override
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