blob: 78f3b5f2a54b9ecaf18fa8de674f4df702c4117a [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;
RoboErike7880d82014-04-30 12:48:25 -070020import android.app.ActivityManager;
21import android.content.ComponentName;
RoboErik01fe6612014-02-13 14:19:04 -080022import android.content.Context;
RoboErika278ea72014-04-24 14:49:01 -070023import android.content.pm.PackageManager;
RoboErik07c70772014-03-20 13:33:52 -070024import android.media.routeprovider.RouteRequest;
25import android.media.session.ISession;
26import android.media.session.ISessionCallback;
27import android.media.session.ISessionManager;
28import android.media.session.RouteInfo;
29import android.media.session.RouteOptions;
RoboErik4646d282014-05-13 10:13:04 -070030import android.media.session.Session;
RoboErik01fe6612014-02-13 14:19:04 -080031import android.os.Binder;
RoboErik8ae0f342014-02-24 18:02:08 -080032import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070033import android.os.IBinder;
RoboErik01fe6612014-02-13 14:19:04 -080034import android.os.RemoteException;
RoboErike7880d82014-04-30 12:48:25 -070035import android.os.UserHandle;
36import android.provider.Settings;
RoboErik01fe6612014-02-13 14:19:04 -080037import android.text.TextUtils;
38import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070039import android.util.SparseArray;
RoboErik01fe6612014-02-13 14:19:04 -080040
41import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070042import com.android.server.Watchdog;
43import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080044
RoboErika278ea72014-04-24 14:49:01 -070045import java.io.FileDescriptor;
46import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080047import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070048import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080049
50/**
51 * System implementation of MediaSessionManager
52 */
RoboErika278ea72014-04-24 14:49:01 -070053public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080054 private static final String TAG = "MediaSessionService";
55 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
56
57 private final SessionManagerImpl mSessionManagerImpl;
RoboErik4646d282014-05-13 10:13:04 -070058 // private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErika8f95142014-05-05 14:23:49 -070059 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080060
RoboErik4646d282014-05-13 10:13:04 -070061 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
62 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
63 // private final ArrayList<MediaRouteProviderProxy> mProviders
64 // = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080065 private final Object mLock = new Object();
RoboErik8ae0f342014-02-24 18:02:08 -080066 private final Handler mHandler = new Handler();
RoboErik01fe6612014-02-13 14:19:04 -080067
RoboErike7880d82014-04-30 12:48:25 -070068 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070069 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070070
RoboErik07c70772014-03-20 13:33:52 -070071 // Used to keep track of the current request to show routes for a specific
72 // session so we drop late callbacks properly.
73 private int mShowRoutesRequestId = 0;
74
RoboErika5b02322014-05-07 17:05:49 -070075 // TODO refactor to have per user state for providers. See
76 // MediaRouterService for an example
RoboErik07c70772014-03-20 13:33:52 -070077
RoboErik01fe6612014-02-13 14:19:04 -080078 public MediaSessionService(Context context) {
79 super(context);
80 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -070081 mPriorityStack = new MediaSessionStack();
RoboErik01fe6612014-02-13 14:19:04 -080082 }
83
84 @Override
85 public void onStart() {
86 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -070087 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -070088 updateUser();
RoboErik07c70772014-03-20 13:33:52 -070089 }
90
91 /**
92 * Should trigger showing the Media route picker dialog. Right now it just
93 * kicks off a query to all the providers to get routes.
94 *
95 * @param record The session to show the picker for.
96 */
97 public void showRoutePickerForSession(MediaSessionRecord record) {
98 // TODO for now just toggle the route to test (we will only have one
99 // match for now)
RoboErik4646d282014-05-13 10:13:04 -0700100 synchronized (mLock) {
101 if (!mAllSessions.contains(record)) {
102 Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
103 return;
104 }
105 RouteInfo current = record.getRoute();
106 UserRecord user = mUserRecords.get(record.getUserId());
107 if (current != null) {
108 // For now send null to mean the local route
109 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
110 if (proxy != null) {
111 proxy.removeSession(record);
112 }
113 record.selectRoute(null);
114 return;
115 }
116 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
117 mShowRoutesRequestId++;
118 for (int i = providers.size() - 1; i >= 0; i--) {
119 MediaRouteProviderProxy provider = providers.get(i);
120 provider.getRoutes(record, mShowRoutesRequestId);
121 }
RoboErik07c70772014-03-20 13:33:52 -0700122 }
123 }
124
125 /**
126 * Connect a session to the given route.
127 *
128 * @param session The session to connect.
129 * @param route The route to connect to.
130 * @param options The options to use for the connection.
131 */
132 public void connectToRoute(MediaSessionRecord session, RouteInfo route,
133 RouteOptions options) {
134 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700135 if (!mAllSessions.contains(session)) {
136 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
137 return;
138 }
139 UserRecord user = mUserRecords.get(session.getUserId());
140 if (user == null) {
141 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
142 return;
143 }
144 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
RoboErik07c70772014-03-20 13:33:52 -0700145 if (proxy == null) {
146 Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
147 return;
148 }
149 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
RoboErik07c70772014-03-20 13:33:52 -0700150 proxy.connectToRoute(session, route, request);
151 }
RoboErik01fe6612014-02-13 14:19:04 -0800152 }
153
RoboErika8f95142014-05-05 14:23:49 -0700154 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700155 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700156 if (!mAllSessions.contains(record)) {
157 Log.d(TAG, "Unknown session updated. Ignoring.");
158 return;
159 }
RoboErika8f95142014-05-05 14:23:49 -0700160 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700161 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700162 if (record.isActive()) {
163 if (mPrioritySession != null) {
164 Log.w(TAG, "Replacing existing priority session with a new session");
165 }
166 mPrioritySession = record;
167 } else {
168 if (mPrioritySession == record) {
169 mPrioritySession = null;
170 }
RoboErike7880d82014-04-30 12:48:25 -0700171 }
RoboErike7880d82014-04-30 12:48:25 -0700172 }
173 }
174 }
175
RoboErika8f95142014-05-05 14:23:49 -0700176 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
177 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700178 if (!mAllSessions.contains(record)) {
179 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
180 return;
181 }
RoboErika8f95142014-05-05 14:23:49 -0700182 mPriorityStack.onPlaystateChange(record, oldState, newState);
183 }
184 }
185
RoboErika278ea72014-04-24 14:49:01 -0700186 @Override
RoboErik4646d282014-05-13 10:13:04 -0700187 public void onStartUser(int userHandle) {
188 updateUser();
189 }
190
191 @Override
192 public void onSwitchUser(int userHandle) {
193 updateUser();
194 }
195
196 @Override
197 public void onStopUser(int userHandle) {
198 synchronized (mLock) {
199 UserRecord user = mUserRecords.get(userHandle);
200 if (user != null) {
201 destroyUserLocked(user);
202 }
203 }
204 }
205
206 @Override
RoboErika278ea72014-04-24 14:49:01 -0700207 public void monitor() {
208 synchronized (mLock) {
209 // Check for deadlock
210 }
211 }
212
RoboErik4646d282014-05-13 10:13:04 -0700213 protected void enforcePhoneStatePermission(int pid, int uid) {
214 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
215 != PackageManager.PERMISSION_GRANTED) {
216 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
217 }
218 }
219
RoboErik01fe6612014-02-13 14:19:04 -0800220 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700221 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800222 destroySessionLocked(session);
223 }
224 }
225
226 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700227 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800228 destroySessionLocked(session);
229 }
230 }
231
RoboErik4646d282014-05-13 10:13:04 -0700232 private void updateUser() {
233 synchronized (mLock) {
234 int userId = ActivityManager.getCurrentUser();
235 if (mCurrentUserId != userId) {
236 final int oldUserId = mCurrentUserId;
237 mCurrentUserId = userId; // do this first
238
239 UserRecord oldUser = mUserRecords.get(oldUserId);
240 if (oldUser != null) {
241 oldUser.stopLocked();
242 }
243
244 UserRecord newUser = getOrCreateUser(userId);
245 newUser.startLocked();
246 }
247 }
248 }
249
250 /**
251 * Stop the user and unbind from everything.
252 *
253 * @param user The user to dispose of
254 */
255 private void destroyUserLocked(UserRecord user) {
256 user.stopLocked();
257 user.destroyLocked();
258 mUserRecords.remove(user.mUserId);
259 }
260
261 /*
262 * When a session is removed several things need to happen.
263 * 1. We need to remove it from the relevant user.
264 * 2. We need to remove it from the priority stack.
265 * 3. We need to remove it from all sessions.
266 * 4. If this is the system priority session we need to clear it.
267 * 5. We need to unlink to death from the cb binder
268 * 6. We need to tell the session to do any final cleanup (onDestroy)
269 */
RoboErik01fe6612014-02-13 14:19:04 -0800270 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700271 int userId = session.getUserId();
272 UserRecord user = mUserRecords.get(userId);
273 if (user != null) {
274 user.removeSessionLocked(session);
275 }
276
RoboErika8f95142014-05-05 14:23:49 -0700277 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700278 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700279 if (session == mPrioritySession) {
280 mPrioritySession = null;
281 }
RoboErik4646d282014-05-13 10:13:04 -0700282
283 try {
284 session.getCallback().asBinder().unlinkToDeath(session, 0);
285 } catch (Exception e) {
286 // ignore exceptions while destroying a session.
287 }
288 session.onDestroy();
RoboErik01fe6612014-02-13 14:19:04 -0800289 }
290
291 private void enforcePackageName(String packageName, int uid) {
292 if (TextUtils.isEmpty(packageName)) {
293 throw new IllegalArgumentException("packageName may not be empty");
294 }
295 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
296 final int packageCount = packages.length;
297 for (int i = 0; i < packageCount; i++) {
298 if (packageName.equals(packages[i])) {
299 return;
300 }
301 }
302 throw new IllegalArgumentException("packageName is not owned by the calling process");
303 }
304
RoboErike7880d82014-04-30 12:48:25 -0700305 /**
306 * Checks a caller's authorization to register an IRemoteControlDisplay.
307 * Authorization is granted if one of the following is true:
308 * <ul>
309 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
310 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700311 * <li>the caller's listener is one of the enabled notification listeners
312 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700313 * </ul>
314 */
RoboErika5b02322014-05-07 17:05:49 -0700315 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
316 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700317 if (getContext()
318 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
319 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700320 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
321 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700322 throw new SecurityException("Missing permission to control media.");
323 }
324 }
325
RoboErika5b02322014-05-07 17:05:49 -0700326 /**
327 * This checks if the component is an enabled notification listener for the
328 * specified user. Enabled components may only operate on behalf of the user
329 * they're running as.
330 *
331 * @param compName The component that is enabled.
332 * @param userId The user id of the caller.
333 * @param forUserId The user id they're making the request on behalf of.
334 * @return True if the component is enabled, false otherwise
335 */
336 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
337 int forUserId) {
338 if (userId != forUserId) {
339 // You may not access another user's content as an enabled listener.
340 return false;
341 }
RoboErike7880d82014-04-30 12:48:25 -0700342 if (compName != null) {
RoboErike7880d82014-04-30 12:48:25 -0700343 final String enabledNotifListeners = Settings.Secure.getStringForUser(
344 getContext().getContentResolver(),
345 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700346 userId);
RoboErike7880d82014-04-30 12:48:25 -0700347 if (enabledNotifListeners != null) {
348 final String[] components = enabledNotifListeners.split(":");
349 for (int i = 0; i < components.length; i++) {
350 final ComponentName component =
351 ComponentName.unflattenFromString(components[i]);
352 if (component != null) {
353 if (compName.equals(component)) {
354 if (DEBUG) {
355 Log.d(TAG, "ok to get sessions: " + component +
356 " is authorized notification listener");
357 }
358 return true;
359 }
360 }
361 }
362 }
363 if (DEBUG) {
364 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700365 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700366 }
367 }
368 return false;
369 }
370
RoboErika5b02322014-05-07 17:05:49 -0700371 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700372 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800373 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700374 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800375 }
376 }
377
RoboErik4646d282014-05-13 10:13:04 -0700378 /*
379 * When a session is created the following things need to happen.
380 * 1. It's callback binder needs a link to death
381 * 2. It needs to be added to all sessions.
382 * 3. It needs to be added to the priority stack.
383 * 4. It needs to be added to the relevant user record.
384 */
RoboErika5b02322014-05-07 17:05:49 -0700385 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
386 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700387
RoboErika5b02322014-05-07 17:05:49 -0700388 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
389 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800390 try {
391 cb.asBinder().linkToDeath(session, 0);
392 } catch (RemoteException e) {
393 throw new RuntimeException("Media Session owner died prematurely.", e);
394 }
RoboErik4646d282014-05-13 10:13:04 -0700395
396 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700397 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700398
399 UserRecord user = getOrCreateUser(userId);
400 user.addSessionLocked(session);
401
RoboErik01fe6612014-02-13 14:19:04 -0800402 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700403 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800404 }
405 return session;
406 }
407
RoboErik4646d282014-05-13 10:13:04 -0700408 private UserRecord getOrCreateUser(int userId) {
409 UserRecord user = mUserRecords.get(userId);
410 if (user == null) {
411 user = new UserRecord(getContext(), userId);
412 mUserRecords.put(userId, user);
413 }
414 return user;
415 }
416
RoboErika8f95142014-05-05 14:23:49 -0700417 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700418 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
419 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700420 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
421 return i;
422 }
423 }
424 return -1;
425 }
426
RoboErike7880d82014-04-30 12:48:25 -0700427 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700428 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700429 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700430 }
431
RoboErik07c70772014-03-20 13:33:52 -0700432 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
433 = new MediaRouteProviderProxy.RoutesListener() {
434 @Override
435 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
436 int reqId) {
437 // TODO for now select the first route to test, eventually add the
438 // new routes to the dialog if it is still open
439 synchronized (mLock) {
440 int index = findIndexOfSessionForIdLocked(sessionId);
441 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700442 MediaSessionRecord record = mAllSessions.get(index);
443 RouteInfo route = routes.get(0);
444 record.selectRoute(route);
445 UserRecord user = mUserRecords.get(record.getUserId());
446 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
447 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700448 }
449 }
450 }
451
452 @Override
453 public void onRouteConnected(String sessionId, RouteInfo route,
454 RouteRequest options, RouteConnectionRecord connection) {
455 synchronized (mLock) {
456 int index = findIndexOfSessionForIdLocked(sessionId);
457 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700458 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700459 session.setRouteConnected(route, options.getConnectionOptions(), connection);
460 }
461 }
462 }
463 };
464
RoboErik4646d282014-05-13 10:13:04 -0700465 /**
466 * Information about a particular user. The contents of this object is
467 * guarded by mLock.
468 */
469 final class UserRecord {
470 private final int mUserId;
471 private final MediaRouteProviderWatcher mRouteProviderWatcher;
472 private final ArrayList<MediaRouteProviderProxy> mProviders
473 = new ArrayList<MediaRouteProviderProxy>();
474 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
475
476 public UserRecord(Context context, int userId) {
477 mUserId = userId;
478 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
479 mProviderWatcherCallback, mHandler, userId);
480 }
481
482 public void startLocked() {
483 mRouteProviderWatcher.start();
484 }
485
486 public void stopLocked() {
487 mRouteProviderWatcher.stop();
488 updateInterestLocked();
489 }
490
491 public void destroyLocked() {
492 for (int i = mSessions.size() - 1; i >= 0; i--) {
493 MediaSessionRecord session = mSessions.get(i);
494 MediaSessionService.this.destroySessionLocked(session);
495 if (session.isConnected()) {
496 session.disconnect(Session.DISCONNECT_REASON_USER_STOPPING);
497 }
498 }
499 }
500
501 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
502 return mProviders;
503 }
504
505 public ArrayList<MediaSessionRecord> getSessionsLocked() {
506 return mSessions;
507 }
508
509 public void addSessionLocked(MediaSessionRecord session) {
510 mSessions.add(session);
511 updateInterestLocked();
512 }
513
514 public void removeSessionLocked(MediaSessionRecord session) {
515 mSessions.remove(session);
516 RouteInfo route = session.getRoute();
517 if (route != null) {
518 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
519 if (provider != null) {
520 provider.removeSession(session);
521 }
522 }
523 updateInterestLocked();
524 }
525
526 public void dumpLocked(PrintWriter pw, String prefix) {
527 pw.println(prefix + "Record for user " + mUserId);
528 String indent = prefix + " ";
529 int size = mProviders.size();
530 pw.println(indent + size + " Providers:");
531 for (int i = 0; i < size; i++) {
532 mProviders.get(i).dump(pw, indent);
533 }
534 pw.println();
535 size = mSessions.size();
536 pw.println(indent + size + " Sessions:");
537 for (int i = 0; i < size; i++) {
538 // Just print the session info, the full session dump will
539 // already be in the list of all sessions.
540 pw.println(indent + mSessions.get(i).getSessionInfo());
541 }
542 }
543
544 public void updateInterestLocked() {
545 // TODO go through the sessions and build up the set of interfaces
546 // we're interested in. Update the provider watcher.
547 // For now, just express interest in all providers for the current
548 // user
549 boolean interested = mUserId == mCurrentUserId;
550 for (int i = mProviders.size() - 1; i >= 0; i--) {
551 mProviders.get(i).setInterested(interested);
552 }
553 }
554
555 private MediaRouteProviderProxy getProviderLocked(String providerId) {
556 for (int i = mProviders.size() - 1; i >= 0; i--) {
557 MediaRouteProviderProxy provider = mProviders.get(i);
558 if (TextUtils.equals(providerId, provider.getId())) {
559 return provider;
560 }
561 }
562 return null;
563 }
564
565 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
566 = new MediaRouteProviderWatcher.Callback() {
567 @Override
568 public void removeProvider(MediaRouteProviderProxy provider) {
569 synchronized (mLock) {
570 mProviders.remove(provider);
571 provider.setRoutesListener(null);
572 provider.setInterested(false);
573 }
574 }
575
576 @Override
577 public void addProvider(MediaRouteProviderProxy provider) {
578 synchronized (mLock) {
579 mProviders.add(provider);
580 provider.setRoutesListener(mRoutesCallback);
581 provider.setInterested(true);
582 }
583 }
584 };
585 }
586
RoboErik07c70772014-03-20 13:33:52 -0700587 class SessionManagerImpl extends ISessionManager.Stub {
588 // TODO add createSessionAsUser, pass user-id to
589 // ActivityManagerNative.handleIncomingUser and stash result for use
590 // when starting services on that session's behalf.
591 @Override
RoboErika5b02322014-05-07 17:05:49 -0700592 public ISession createSession(String packageName, ISessionCallback cb, String tag,
593 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800594 final int pid = Binder.getCallingPid();
595 final int uid = Binder.getCallingUid();
596 final long token = Binder.clearCallingIdentity();
597 try {
598 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700599 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
600 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800601 if (cb == null) {
602 throw new IllegalArgumentException("Controller callback cannot be null");
603 }
RoboErika5b02322014-05-07 17:05:49 -0700604 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
605 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700606 } finally {
607 Binder.restoreCallingIdentity(token);
608 }
609 }
610
611 @Override
RoboErika5b02322014-05-07 17:05:49 -0700612 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700613 final int pid = Binder.getCallingPid();
614 final int uid = Binder.getCallingUid();
615 final long token = Binder.clearCallingIdentity();
616
617 try {
RoboErika5b02322014-05-07 17:05:49 -0700618 String packageName = null;
RoboErike7880d82014-04-30 12:48:25 -0700619 if (componentName != null) {
620 // If they gave us a component name verify they own the
621 // package
RoboErika5b02322014-05-07 17:05:49 -0700622 packageName = componentName.getPackageName();
623 enforcePackageName(packageName, uid);
RoboErike7880d82014-04-30 12:48:25 -0700624 }
RoboErika5b02322014-05-07 17:05:49 -0700625 // Check that they can make calls on behalf of the user and
626 // get the final user id
627 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
628 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
629 // Check if they have the permissions or their component is
630 // enabled for the user they're calling from.
631 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
RoboErike7880d82014-04-30 12:48:25 -0700632 ArrayList<IBinder> binders = new ArrayList<IBinder>();
633 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700634 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700635 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700636 int size = records.size();
637 for (int i = 0; i < size; i++) {
638 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700639 }
640 }
641 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800642 } finally {
643 Binder.restoreCallingIdentity(token);
644 }
645 }
RoboErika278ea72014-04-24 14:49:01 -0700646
647 @Override
648 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
649 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
650 != PackageManager.PERMISSION_GRANTED) {
651 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
652 + Binder.getCallingPid()
653 + ", uid=" + Binder.getCallingUid());
654 return;
655 }
656
657 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
658 pw.println();
659
660 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700661 pw.println("Session for calls:" + mPrioritySession);
662 if (mPrioritySession != null) {
663 mPrioritySession.dump(pw, "");
664 }
RoboErik4646d282014-05-13 10:13:04 -0700665 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700666 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700667 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700668 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700669 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700670 }
RoboErika5b02322014-05-07 17:05:49 -0700671 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700672
RoboErik4646d282014-05-13 10:13:04 -0700673 pw.println("User Records:");
674 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700675 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700676 UserRecord user = mUserRecords.get(i);
677 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700678 }
679 }
680 }
RoboErik01fe6612014-02-13 14:19:04 -0800681 }
682
683}