1 /* 2 * Copyright (C) 2022 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 17 package com.google.android.car.kitchensink.media; 18 19 import android.app.Service; 20 import android.content.ComponentName; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.ServiceConnection; 24 import android.os.Binder; 25 import android.os.Bundle; 26 import android.os.Handler; 27 import android.os.HandlerThread; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.os.ResultReceiver; 31 import android.os.UserHandle; 32 import android.service.media.IMediaBrowserService; 33 import android.service.media.IMediaBrowserServiceCallbacks; 34 import android.service.media.MediaBrowserService; 35 import android.util.Log; 36 37 import java.io.FileDescriptor; 38 import java.io.PrintWriter; 39 40 /** 41 * A proxy to allow a connection from {@link android.media.browse.MediaBrowser} to {@link 42 * android.service.media.MediaBrowserService} across user boundaries. 43 * 44 * <p>The {@code MediaBrowser} client connects to this service as if it is connecting to a 45 * {@code MediaBrowserService}. It needs to pass the actual {@code MediaBrowserService}'s component 46 * name as part the {@code rootHints} bundle, so that this proxy can know what the actual 47 * {@code MediaBrowserService} is to connect to. 48 */ 49 public class MediaBrowserProxyService extends Service { 50 51 private static final String TAG = MediaBrowserProxyService.class.getSimpleName(); 52 53 // The key to specify the actual media browser service in the rootHints bundle. 54 public static final String MEDIA_BROWSER_SERVICE_COMPONENT_KEY = 55 "media_browser_service_component_key"; 56 57 private HandlerThread mNativeHandlerThread; 58 // Handler associated with the native worker thread. 59 private Handler mNativeHandler; 60 private Binder mBinder; 61 62 private ComponentName mMediaBrowserServiceComponent; 63 private IMediaBrowserService mServiceBinder; 64 private MediaServiceConnection mServiceConnection; 65 66 private final class MediaBrowserProxyServiceImpl extends IMediaBrowserService.Stub { 67 68 private final Context mContext; 69 MediaBrowserProxyServiceImpl(Context context)70 MediaBrowserProxyServiceImpl(Context context) { 71 mContext = context; 72 } 73 74 @Override connect(String pkg, Bundle rootHints, IMediaBrowserServiceCallbacks callbacks)75 public void connect(String pkg, Bundle rootHints, IMediaBrowserServiceCallbacks callbacks) { 76 Log.d(TAG, "connect() with pkg=" + pkg + ", rootHints=" + rootHints 77 + ", callbacks=" + callbacks 78 + ", callingUserId=" + Binder.getCallingUserHandle().getIdentifier() 79 + ", myUserId=" + UserHandle.myUserId()); 80 81 // The actual MediaBrowserService component is specified in the bundle. 82 // Retrieve the info here and remove it from the bundle, and then pass the original 83 // rootHints to the actual MediaBrowserService. 84 String mediaBrowserServiceComponent = 85 rootHints.getString(MEDIA_BROWSER_SERVICE_COMPONENT_KEY); 86 mMediaBrowserServiceComponent = 87 ComponentName.unflattenFromString(mediaBrowserServiceComponent); 88 rootHints.remove(MEDIA_BROWSER_SERVICE_COMPONENT_KEY); 89 Bundle origRootHints = rootHints.size() == 0 ? null : rootHints; 90 91 mNativeHandler.post(() -> { 92 Intent intent = new Intent(MediaBrowserService.SERVICE_INTERFACE); 93 intent.setComponent(mMediaBrowserServiceComponent); 94 95 mServiceConnection = new MediaServiceConnection(pkg, origRootHints, callbacks); 96 97 boolean bound = false; 98 try { 99 bound = mContext.bindService(intent, mServiceConnection, 100 Context.BIND_AUTO_CREATE | Context.BIND_INCLUDE_CAPABILITIES); 101 } catch (Exception ex) { 102 Log.e(TAG, "Failed binding to service " + mMediaBrowserServiceComponent); 103 } 104 105 if (!bound) { 106 Log.d(TAG, "Cannot bind to MediaBrowserService: " 107 + mMediaBrowserServiceComponent); 108 mServiceConnection = null; 109 try { 110 callbacks.onConnectFailed(); 111 } catch (RemoteException e) { 112 Log.e(TAG, "Failed call onConnectionFailed"); 113 } 114 } 115 }); 116 } 117 118 @Override disconnect(IMediaBrowserServiceCallbacks callbacks)119 public void disconnect(IMediaBrowserServiceCallbacks callbacks) { 120 Log.d(TAG, "disconnect() for user " + UserHandle.myUserId()); 121 mNativeHandler.post(() -> { 122 try { 123 mServiceBinder.disconnect(callbacks); 124 } catch (Exception e) { 125 Log.e(TAG, "Failed to disconnect MediaBrowserService", e); 126 } 127 }); 128 } 129 130 @Override addSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks)131 public void addSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks) { 132 Log.d(TAG, "addSubscriptionDeprecated() for user " + UserHandle.myUserId()); 133 mNativeHandler.post(() -> { 134 try { 135 mServiceBinder.addSubscriptionDeprecated(uri, callbacks); 136 } catch (Exception e) { 137 Log.e(TAG, "Failed to call MediaBrowserService#addSubscriptionDeprecated", 138 e); 139 } 140 }); 141 } 142 143 @Override removeSubscriptionDeprecated(String uri, IMediaBrowserServiceCallbacks callbacks)144 public void removeSubscriptionDeprecated(String uri, 145 IMediaBrowserServiceCallbacks callbacks) { 146 Log.d(TAG, "removeSubscriptionDeprecated() for user " + UserHandle.myUserId()); 147 mNativeHandler.post(() -> { 148 try { 149 mServiceBinder.removeSubscriptionDeprecated(uri, callbacks); 150 } catch (Exception e) { 151 Log.e(TAG, "Failed to call " 152 + " MediaBrowserService#removeSubscriptionDeprecated", e); 153 } 154 }); 155 } 156 157 @Override getMediaItem(String uri, ResultReceiver cb, IMediaBrowserServiceCallbacks callbacks)158 public void getMediaItem(String uri, ResultReceiver cb, 159 IMediaBrowserServiceCallbacks callbacks) { 160 Log.d(TAG, "getMediaItem() for user " + UserHandle.myUserId()); 161 mNativeHandler.post(() -> { 162 try { 163 mServiceBinder.getMediaItem(uri, cb, callbacks); 164 } catch (Exception e) { 165 Log.e(TAG, "Failed to call MediaBrowserService#getMediaitem", e); 166 } 167 }); 168 } 169 170 @Override addSubscription(String uri, IBinder token, Bundle options, IMediaBrowserServiceCallbacks callbacks)171 public void addSubscription(String uri, IBinder token, Bundle options, 172 IMediaBrowserServiceCallbacks callbacks) { 173 Log.d(TAG, "addSubscription() for user " + UserHandle.myUserId()); 174 mNativeHandler.post(() -> { 175 try { 176 mServiceBinder.addSubscription(uri, token, options, callbacks); 177 } catch (Exception e) { 178 Log.e(TAG, "Failed to call MediaBrowserService#addSubscription", e); 179 } 180 }); 181 } 182 183 @Override removeSubscription(String uri, IBinder token, IMediaBrowserServiceCallbacks callbacks)184 public void removeSubscription(String uri, IBinder token, 185 IMediaBrowserServiceCallbacks callbacks) { 186 Log.d(TAG, "removeSubscription() for user " + UserHandle.myUserId()); 187 mNativeHandler.post(() -> { 188 try { 189 mServiceBinder.removeSubscription(uri, token, callbacks); 190 } catch (Exception e) { 191 Log.e(TAG, "Failed to call MediaBrowserService#removeSubscription", e); 192 } 193 }); 194 } 195 } 196 197 @Override onCreate()198 public void onCreate() { 199 super.onCreate(); 200 201 mNativeHandlerThread = new HandlerThread(MediaBrowserProxyService.class.getSimpleName()); 202 mNativeHandlerThread.start(); 203 mNativeHandler = new Handler(mNativeHandlerThread.getLooper()); 204 Log.d(TAG, "Started a native handler: " + mNativeHandler + " on thread; " 205 + mNativeHandlerThread); 206 207 mBinder = new MediaBrowserProxyServiceImpl(this); 208 } 209 210 @Override onDestroy()211 public void onDestroy() { 212 mNativeHandlerThread.quit(); 213 super.onDestroy(); 214 } 215 216 @Override onBind(Intent intent)217 public IBinder onBind(Intent intent) { 218 Log.d(TAG, "onBind() intent: " + intent); 219 return mBinder; 220 } 221 222 @Override dump(FileDescriptor fd, PrintWriter writer, String[] args)223 public void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 224 writer.printf("mNativeHandlerThread: %s\n", mNativeHandlerThread); 225 writer.printf("mNativeHandler: %s\n", mNativeHandler); 226 writer.printf("mBinder: %s\n", mBinder); 227 writer.printf("mMediaBrowserServiceComponent: %s\n", mMediaBrowserServiceComponent); 228 writer.printf("mServiceBinder: %s\n", mServiceBinder); 229 writer.printf("mServiceConnection: %s\n", mServiceConnection); 230 } 231 232 private final class MediaServiceConnection implements ServiceConnection { 233 234 private final String mCallerPackage; 235 private final Bundle mRootHints; 236 private final IMediaBrowserServiceCallbacks mCallbacks; 237 MediaServiceConnection(String pkg, Bundle rootHints, IMediaBrowserServiceCallbacks callbacks)238 MediaServiceConnection(String pkg, Bundle rootHints, 239 IMediaBrowserServiceCallbacks callbacks) { 240 mCallerPackage = pkg; 241 mRootHints = rootHints; 242 mCallbacks = callbacks; 243 } 244 245 @Override onServiceConnected(ComponentName componentName, IBinder iBinder)246 public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 247 Log.d(TAG, "onServiceConnected(ComponentName: " + componentName.toShortString() 248 + ", Binder: " + iBinder + ")"); 249 250 mServiceBinder = IMediaBrowserService.Stub.asInterface(iBinder); 251 252 // Connection from Proxy to MediaBrowserService has been established now. 253 // Now calling MediaBrowserService#connect(). 254 mNativeHandler.post(() -> { 255 Log.d(TAG, "Connecting MediaBrowserService " + mServiceBinder); 256 try { 257 mServiceBinder.connect(mCallerPackage, mRootHints, mCallbacks); 258 } catch (Exception e) { 259 Log.e(TAG, "Failed to connect MediaBrowserBrowserService", e); 260 } 261 }); 262 } 263 264 @Override onServiceDisconnected(ComponentName componentName)265 public void onServiceDisconnected(ComponentName componentName) { 266 Log.d(TAG, "onServiceDisconnected(ComponentName: " 267 + componentName.toShortString() + ")"); 268 // No need to call mCallbacks.onConnectFailed() here, because we passed the callbacks 269 // to the actual MediaBrowserService, and the call back will be invoked by it. 270 mServiceBinder = null; 271 } 272 } 273 } 274