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