MediaSessionService.java revision a278ea7cecb59a73586e5dd74ec05e85caa370c5
1/* 2 * Copyright (C) 2014 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 17package com.android.server.media; 18 19import android.Manifest; 20import android.content.Context; 21import android.content.pm.PackageManager; 22import android.media.routeprovider.RouteRequest; 23import android.media.session.ISession; 24import android.media.session.ISessionCallback; 25import android.media.session.ISessionManager; 26import android.media.session.RouteInfo; 27import android.media.session.RouteOptions; 28import android.os.Binder; 29import android.os.Handler; 30import android.os.RemoteException; 31import android.text.TextUtils; 32import android.util.Log; 33 34import com.android.server.SystemService; 35import com.android.server.Watchdog; 36import com.android.server.Watchdog.Monitor; 37 38import java.io.FileDescriptor; 39import java.io.PrintWriter; 40import java.util.ArrayList; 41 42/** 43 * System implementation of MediaSessionManager 44 */ 45public class MediaSessionService extends SystemService implements Monitor { 46 private static final String TAG = "MediaSessionService"; 47 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 48 49 private final SessionManagerImpl mSessionManagerImpl; 50 private final MediaRouteProviderWatcher mRouteProviderWatcher; 51 52 private final ArrayList<MediaSessionRecord> mSessions 53 = new ArrayList<MediaSessionRecord>(); 54 private final ArrayList<MediaRouteProviderProxy> mProviders 55 = new ArrayList<MediaRouteProviderProxy>(); 56 private final Object mLock = new Object(); 57 // TODO do we want a separate thread for handling mediasession messages? 58 private final Handler mHandler = new Handler(); 59 60 // Used to keep track of the current request to show routes for a specific 61 // session so we drop late callbacks properly. 62 private int mShowRoutesRequestId = 0; 63 64 // TODO refactor to have per user state. See MediaRouterService for an 65 // example 66 67 public MediaSessionService(Context context) { 68 super(context); 69 mSessionManagerImpl = new SessionManagerImpl(); 70 mRouteProviderWatcher = new MediaRouteProviderWatcher(context, mProviderWatcherCallback, 71 mHandler, context.getUserId()); 72 } 73 74 @Override 75 public void onStart() { 76 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl); 77 mRouteProviderWatcher.start(); 78 Watchdog.getInstance().addMonitor(this); 79 } 80 81 /** 82 * Should trigger showing the Media route picker dialog. Right now it just 83 * kicks off a query to all the providers to get routes. 84 * 85 * @param record The session to show the picker for. 86 */ 87 public void showRoutePickerForSession(MediaSessionRecord record) { 88 // TODO for now just toggle the route to test (we will only have one 89 // match for now) 90 if (record.getRoute() != null) { 91 // For now send null to mean the local route 92 record.selectRoute(null); 93 return; 94 } 95 mShowRoutesRequestId++; 96 ArrayList<MediaRouteProviderProxy> providers = mRouteProviderWatcher.getProviders(); 97 for (int i = providers.size() - 1; i >= 0; i--) { 98 MediaRouteProviderProxy provider = providers.get(i); 99 provider.getRoutes(record, mShowRoutesRequestId); 100 } 101 } 102 103 /** 104 * Connect a session to the given route. 105 * 106 * @param session The session to connect. 107 * @param route The route to connect to. 108 * @param options The options to use for the connection. 109 */ 110 public void connectToRoute(MediaSessionRecord session, RouteInfo route, 111 RouteOptions options) { 112 synchronized (mLock) { 113 MediaRouteProviderProxy proxy = getProviderLocked(route.getProvider()); 114 if (proxy == null) { 115 Log.w(TAG, "Provider for route " + route.getName() + " does not exist."); 116 return; 117 } 118 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true); 119 // TODO make connect an async call to a ThreadPoolExecutor 120 proxy.connectToRoute(session, route, request); 121 } 122 } 123 124 @Override 125 public void monitor() { 126 synchronized (mLock) { 127 // Check for deadlock 128 } 129 } 130 131 void sessionDied(MediaSessionRecord session) { 132 synchronized (mLock) { 133 destroySessionLocked(session); 134 } 135 } 136 137 void destroySession(MediaSessionRecord session) { 138 synchronized (mLock) { 139 destroySessionLocked(session); 140 } 141 } 142 143 private void destroySessionLocked(MediaSessionRecord session) { 144 mSessions.remove(session); 145 } 146 147 private void enforcePackageName(String packageName, int uid) { 148 if (TextUtils.isEmpty(packageName)) { 149 throw new IllegalArgumentException("packageName may not be empty"); 150 } 151 String[] packages = getContext().getPackageManager().getPackagesForUid(uid); 152 final int packageCount = packages.length; 153 for (int i = 0; i < packageCount; i++) { 154 if (packageName.equals(packages[i])) { 155 return; 156 } 157 } 158 throw new IllegalArgumentException("packageName is not owned by the calling process"); 159 } 160 161 private MediaSessionRecord createSessionInternal(int pid, String packageName, 162 ISessionCallback cb, String tag) { 163 synchronized (mLock) { 164 return createSessionLocked(pid, packageName, cb, tag); 165 } 166 } 167 168 private MediaSessionRecord createSessionLocked(int pid, String packageName, 169 ISessionCallback cb, String tag) { 170 final MediaSessionRecord session = new MediaSessionRecord(pid, packageName, cb, tag, this, 171 mHandler); 172 try { 173 cb.asBinder().linkToDeath(session, 0); 174 } catch (RemoteException e) { 175 throw new RuntimeException("Media Session owner died prematurely.", e); 176 } 177 mSessions.add(session); 178 if (DEBUG) { 179 Log.d(TAG, "Created session for package " + packageName + " with tag " + tag); 180 } 181 return session; 182 } 183 184 private MediaRouteProviderProxy getProviderLocked(String providerId) { 185 for (int i = mProviders.size() - 1; i >= 0; i--) { 186 MediaRouteProviderProxy provider = mProviders.get(i); 187 if (TextUtils.equals(providerId, provider.getId())) { 188 return provider; 189 } 190 } 191 return null; 192 } 193 194 private int findIndexOfSessionForIdLocked(String sessionId) { 195 for (int i = mSessions.size() - 1; i >= 0; i--) { 196 MediaSessionRecord session = mSessions.get(i); 197 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) { 198 return i; 199 } 200 } 201 return -1; 202 } 203 204 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback 205 = new MediaRouteProviderWatcher.Callback() { 206 @Override 207 public void removeProvider(MediaRouteProviderProxy provider) { 208 synchronized (mLock) { 209 mProviders.remove(provider); 210 provider.setRoutesListener(null); 211 provider.setInterested(false); 212 } 213 } 214 215 @Override 216 public void addProvider(MediaRouteProviderProxy provider) { 217 synchronized (mLock) { 218 mProviders.add(provider); 219 provider.setRoutesListener(mRoutesCallback); 220 provider.setInterested(true); 221 } 222 } 223 }; 224 225 private MediaRouteProviderProxy.RoutesListener mRoutesCallback 226 = new MediaRouteProviderProxy.RoutesListener() { 227 @Override 228 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes, 229 int reqId) { 230 // TODO for now select the first route to test, eventually add the 231 // new routes to the dialog if it is still open 232 synchronized (mLock) { 233 int index = findIndexOfSessionForIdLocked(sessionId); 234 if (index != -1 && routes != null && routes.size() > 0) { 235 MediaSessionRecord record = mSessions.get(index); 236 record.selectRoute(routes.get(0)); 237 } 238 } 239 } 240 241 @Override 242 public void onRouteConnected(String sessionId, RouteInfo route, 243 RouteRequest options, RouteConnectionRecord connection) { 244 synchronized (mLock) { 245 int index = findIndexOfSessionForIdLocked(sessionId); 246 if (index != -1) { 247 MediaSessionRecord session = mSessions.get(index); 248 session.setRouteConnected(route, options.getConnectionOptions(), connection); 249 } 250 } 251 } 252 }; 253 254 class SessionManagerImpl extends ISessionManager.Stub { 255 // TODO add createSessionAsUser, pass user-id to 256 // ActivityManagerNative.handleIncomingUser and stash result for use 257 // when starting services on that session's behalf. 258 @Override 259 public ISession createSession(String packageName, ISessionCallback cb, String tag) 260 throws RemoteException { 261 final int pid = Binder.getCallingPid(); 262 final int uid = Binder.getCallingUid(); 263 final long token = Binder.clearCallingIdentity(); 264 try { 265 enforcePackageName(packageName, uid); 266 if (cb == null) { 267 throw new IllegalArgumentException("Controller callback cannot be null"); 268 } 269 return createSessionInternal(pid, packageName, cb, tag).getSessionBinder(); 270 } finally { 271 Binder.restoreCallingIdentity(token); 272 } 273 } 274 275 @Override 276 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) { 277 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP) 278 != PackageManager.PERMISSION_GRANTED) { 279 pw.println("Permission Denial: can't dump MediaSessionService from from pid=" 280 + Binder.getCallingPid() 281 + ", uid=" + Binder.getCallingUid()); 282 return; 283 } 284 285 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)"); 286 pw.println(); 287 288 synchronized (mLock) { 289 int count = mSessions.size(); 290 pw.println("Sessions - have " + count + " states:"); 291 for (int i = 0; i < count; i++) { 292 MediaSessionRecord record = mSessions.get(i); 293 pw.println(); 294 record.dump(pw, ""); 295 } 296 pw.println("Providers:"); 297 count = mProviders.size(); 298 for (int i = 0; i < count; i++) { 299 MediaRouteProviderProxy provider = mProviders.get(i); 300 provider.dump(pw, ""); 301 } 302 } 303 } 304 } 305 306} 307