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