blob: 107f6ad257f297866c4ffa1e2ce9f9b61ba775ea [file] [log] [blame]
RoboErik01fe6612014-02-13 14:19:04 -08001/*
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
RoboErika278ea72014-04-24 14:49:01 -070019import android.Manifest;
RoboErik01fe6612014-02-13 14:19:04 -080020import android.content.Context;
RoboErika278ea72014-04-24 14:49:01 -070021import android.content.pm.PackageManager;
RoboErik07c70772014-03-20 13:33:52 -070022import 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;
RoboErik01fe6612014-02-13 14:19:04 -080028import android.os.Binder;
RoboErik8ae0f342014-02-24 18:02:08 -080029import android.os.Handler;
RoboErik01fe6612014-02-13 14:19:04 -080030import android.os.RemoteException;
31import android.text.TextUtils;
32import android.util.Log;
33
34import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070035import com.android.server.Watchdog;
36import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080037
RoboErika278ea72014-04-24 14:49:01 -070038import java.io.FileDescriptor;
39import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080040import java.util.ArrayList;
41
42/**
43 * System implementation of MediaSessionManager
44 */
RoboErika278ea72014-04-24 14:49:01 -070045public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080046 private static final String TAG = "MediaSessionService";
47 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
48
49 private final SessionManagerImpl mSessionManagerImpl;
RoboErik07c70772014-03-20 13:33:52 -070050 private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErik01fe6612014-02-13 14:19:04 -080051
52 private final ArrayList<MediaSessionRecord> mSessions
53 = new ArrayList<MediaSessionRecord>();
RoboErik07c70772014-03-20 13:33:52 -070054 private final ArrayList<MediaRouteProviderProxy> mProviders
55 = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080056 private final Object mLock = new Object();
RoboErik8ae0f342014-02-24 18:02:08 -080057 // TODO do we want a separate thread for handling mediasession messages?
58 private final Handler mHandler = new Handler();
RoboErik01fe6612014-02-13 14:19:04 -080059
RoboErik07c70772014-03-20 13:33:52 -070060 // 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
RoboErik01fe6612014-02-13 14:19:04 -080067 public MediaSessionService(Context context) {
68 super(context);
69 mSessionManagerImpl = new SessionManagerImpl();
RoboErik07c70772014-03-20 13:33:52 -070070 mRouteProviderWatcher = new MediaRouteProviderWatcher(context, mProviderWatcherCallback,
71 mHandler, context.getUserId());
RoboErik01fe6612014-02-13 14:19:04 -080072 }
73
74 @Override
75 public void onStart() {
76 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErik07c70772014-03-20 13:33:52 -070077 mRouteProviderWatcher.start();
RoboErika278ea72014-04-24 14:49:01 -070078 Watchdog.getInstance().addMonitor(this);
RoboErik07c70772014-03-20 13:33:52 -070079 }
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 }
RoboErik01fe6612014-02-13 14:19:04 -0800122 }
123
RoboErika278ea72014-04-24 14:49:01 -0700124 @Override
125 public void monitor() {
126 synchronized (mLock) {
127 // Check for deadlock
128 }
129 }
130
RoboErik01fe6612014-02-13 14:19:04 -0800131 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700132 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800133 destroySessionLocked(session);
134 }
135 }
136
137 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700138 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800139 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,
RoboErik07c70772014-03-20 13:33:52 -0700162 ISessionCallback cb, String tag) {
RoboErik01fe6612014-02-13 14:19:04 -0800163 synchronized (mLock) {
164 return createSessionLocked(pid, packageName, cb, tag);
165 }
166 }
167
168 private MediaSessionRecord createSessionLocked(int pid, String packageName,
RoboErik07c70772014-03-20 13:33:52 -0700169 ISessionCallback cb, String tag) {
RoboErik8ae0f342014-02-24 18:02:08 -0800170 final MediaSessionRecord session = new MediaSessionRecord(pid, packageName, cb, tag, this,
171 mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800172 try {
173 cb.asBinder().linkToDeath(session, 0);
174 } catch (RemoteException e) {
175 throw new RuntimeException("Media Session owner died prematurely.", e);
176 }
RoboErika278ea72014-04-24 14:49:01 -0700177 mSessions.add(session);
RoboErik01fe6612014-02-13 14:19:04 -0800178 if (DEBUG) {
179 Log.d(TAG, "Created session for package " + packageName + " with tag " + tag);
180 }
181 return session;
182 }
183
RoboErik07c70772014-03-20 13:33:52 -0700184 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() {
RoboErik01fe6612014-02-13 14:19:04 -0800206 @Override
RoboErik07c70772014-03-20 13:33:52 -0700207 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)
RoboErik01fe6612014-02-13 14:19:04 -0800260 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 }
RoboErika278ea72014-04-24 14:49:01 -0700274
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 }
RoboErik01fe6612014-02-13 14:19:04 -0800304 }
305
306}