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