1 /*
2  * Copyright (C) 2019 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.RequiresPermission;
21 import android.annotation.SystemApi;
22 import android.annotation.TestApi;
23 import android.car.Car;
24 import android.car.CarManagerBase;
25 import android.content.ComponentName;
26 import android.os.IBinder;
27 import android.os.RemoteException;
28 
29 import com.android.internal.annotations.GuardedBy;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 
37 /**
38  * API for updating and receiving updates to the primary media source in the car.
39  * @hide
40  */
41 @SystemApi
42 public final class CarMediaManager extends CarManagerBase {
43 
44     public static final int MEDIA_SOURCE_MODE_PLAYBACK = 0;
45     public static final int MEDIA_SOURCE_MODE_BROWSE = 1;
46 
47     /** @hide */
48     @IntDef(prefix = { "MEDIA_SOURCE_MODE_" }, value = {
49             MEDIA_SOURCE_MODE_PLAYBACK,
50             MEDIA_SOURCE_MODE_BROWSE
51     })
52     @Retention(RetentionPolicy.SOURCE)
53     public @interface MediaSourceMode {}
54 
55     private final Object mLock = new Object();
56 
57     private final ICarMedia mService;
58     @GuardedBy("mLock")
59     private Map<MediaSourceChangedListener, ICarMediaSourceListener> mCallbackMap = new HashMap();
60 
61     /**
62      * Get an instance of the CarMediaManager.
63      *
64      * Should not be obtained directly by clients, use {@link Car#getCarManager(String)} instead.
65      * @hide
66      */
CarMediaManager(Car car, IBinder service)67     public CarMediaManager(Car car, IBinder service) {
68         super(car);
69         mService = ICarMedia.Stub.asInterface(service);
70     }
71 
72     /**
73      * Listener for updates to the primary media source
74      */
75     public interface MediaSourceChangedListener {
76 
77         /**
78          * Called when the primary media source is changed
79          */
onMediaSourceChanged(@onNull ComponentName componentName)80         void onMediaSourceChanged(@NonNull ComponentName componentName);
81     }
82 
83     /**
84      * Gets the currently active media source for the provided mode
85      *
86      * @param mode the mode (playback or browse) for which the media source is active in.
87      * @return the active media source in the provided mode, will be non-{@code null}.
88      */
89     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
getMediaSource(@ediaSourceMode int mode)90     public @NonNull ComponentName getMediaSource(@MediaSourceMode int mode) {
91         try {
92             return mService.getMediaSource(mode);
93         } catch (RemoteException e) {
94             return handleRemoteExceptionFromCarService(e, null);
95         }
96     }
97 
98     /**
99      * Sets the currently active media source for the provided mode
100      *
101      * @param mode the mode (playback or browse) for which the media source is active in.
102      */
103     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
setMediaSource(@onNull ComponentName componentName, @MediaSourceMode int mode)104     public void setMediaSource(@NonNull ComponentName componentName, @MediaSourceMode int mode) {
105         try {
106             mService.setMediaSource(componentName, mode);
107         } catch (RemoteException e) {
108             handleRemoteExceptionFromCarService(e);
109         }
110     }
111 
112     /**
113      * Register a callback that receives updates to the active media source.
114      *
115      * @param callback the callback to receive active media source updates.
116      * @param mode the mode to receive updates for.
117      */
118     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
addMediaSourceListener(@onNull MediaSourceChangedListener callback, @MediaSourceMode int mode)119     public void addMediaSourceListener(@NonNull MediaSourceChangedListener callback,
120             @MediaSourceMode int mode) {
121         try {
122             ICarMediaSourceListener binderCallback = new ICarMediaSourceListener.Stub() {
123                 @Override
124                 public void onMediaSourceChanged(ComponentName componentName) {
125                     callback.onMediaSourceChanged(componentName);
126                 }
127             };
128             synchronized (mLock) {
129                 mCallbackMap.put(callback, binderCallback);
130             }
131             mService.registerMediaSourceListener(binderCallback, mode);
132         } catch (RemoteException e) {
133             handleRemoteExceptionFromCarService(e);
134         }
135     }
136 
137     /**
138      * Unregister a callback that receives updates to the active media source.
139      *
140      * @param callback the callback to be unregistered.
141      * @param mode the mode that the callback was registered to receive updates for.
142      */
143     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
removeMediaSourceListener(@onNull MediaSourceChangedListener callback, @MediaSourceMode int mode)144     public void removeMediaSourceListener(@NonNull MediaSourceChangedListener callback,
145             @MediaSourceMode int mode) {
146         try {
147             synchronized (mLock) {
148                 ICarMediaSourceListener binderCallback = mCallbackMap.remove(callback);
149                 mService.unregisterMediaSourceListener(binderCallback, mode);
150             }
151         } catch (RemoteException e) {
152             handleRemoteExceptionFromCarService(e);
153         }
154     }
155 
156     /**
157      * Retrieve a list of media sources, ordered by most recently used.
158      *
159      * @param mode the mode (playback or browse) for which to retrieve media sources from.
160      * @return non-{@code null} list of media sources, ordered by most recently used
161      */
162     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
getLastMediaSources(@ediaSourceMode int mode)163     public @NonNull List<ComponentName> getLastMediaSources(@MediaSourceMode int mode) {
164         try {
165             return mService.getLastMediaSources(mode);
166         } catch (RemoteException e) {
167             return handleRemoteExceptionFromCarService(e, null);
168         }
169     }
170 
171     /** @hide */
172     @Override
onCarDisconnected()173     public void onCarDisconnected() {
174         synchronized (mLock) {
175             mCallbackMap.clear();
176         }
177     }
178 
179     /**
180      * Returns whether the browse and playback sources can be changed independently.
181      * @return true if the browse and playback sources can be changed independently, false if it
182      * isn't or if the value could not be determined.
183      * @hide
184      */
185     @TestApi
186     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
isIndependentPlaybackConfig()187     public boolean isIndependentPlaybackConfig() {
188         try {
189             return mService.isIndependentPlaybackConfig();
190         } catch (RemoteException e) {
191             return handleRemoteExceptionFromCarService(e, null);
192         }
193     }
194 
195     /**
196      * Sets whether the browse and playback sources can be changed independently.
197      * @param independent whether the browse and playback sources can be changed independently.
198      * @hide
199      */
200     @TestApi
201     @RequiresPermission(value = android.Manifest.permission.MEDIA_CONTENT_CONTROL)
setIndependentPlaybackConfig(boolean independent)202     public void setIndependentPlaybackConfig(boolean independent) {
203         try {
204             mService.setIndependentPlaybackConfig(independent);
205         } catch (RemoteException e) {
206             handleRemoteExceptionFromCarService(e);
207         }
208     }
209 }
210