blob: a2ca2a1e364d4dee4455644789264c52a4b773b9 [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;
RoboErik8a2cfc32014-05-16 11:19:38 -070020import android.app.Activity;
RoboErike7880d82014-04-30 12:48:25 -070021import android.app.ActivityManager;
RoboErik8a2cfc32014-05-16 11:19:38 -070022import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070023import android.content.ComponentName;
RoboErik01fe6612014-02-13 14:19:04 -080024import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070025import android.content.Intent;
RoboErika278ea72014-04-24 14:49:01 -070026import android.content.pm.PackageManager;
RoboErik07c70772014-03-20 13:33:52 -070027import android.media.routeprovider.RouteRequest;
28import android.media.session.ISession;
29import android.media.session.ISessionCallback;
30import android.media.session.ISessionManager;
31import android.media.session.RouteInfo;
32import android.media.session.RouteOptions;
RoboErik4646d282014-05-13 10:13:04 -070033import android.media.session.Session;
RoboErik01fe6612014-02-13 14:19:04 -080034import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070035import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080036import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070037import android.os.IBinder;
RoboErik8a2cfc32014-05-16 11:19:38 -070038import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080039import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070040import android.os.ResultReceiver;
RoboErike7880d82014-04-30 12:48:25 -070041import android.os.UserHandle;
42import android.provider.Settings;
RoboErik01fe6612014-02-13 14:19:04 -080043import android.text.TextUtils;
44import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070045import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070046import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080047
48import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070049import com.android.server.Watchdog;
50import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080051
RoboErika278ea72014-04-24 14:49:01 -070052import java.io.FileDescriptor;
53import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080054import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070055import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080056
57/**
58 * System implementation of MediaSessionManager
59 */
RoboErika278ea72014-04-24 14:49:01 -070060public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080061 private static final String TAG = "MediaSessionService";
62 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
63
64 private final SessionManagerImpl mSessionManagerImpl;
RoboErik4646d282014-05-13 10:13:04 -070065 // private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErika8f95142014-05-05 14:23:49 -070066 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080067
RoboErik4646d282014-05-13 10:13:04 -070068 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
69 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
70 // private final ArrayList<MediaRouteProviderProxy> mProviders
71 // = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080072 private final Object mLock = new Object();
RoboErik8ae0f342014-02-24 18:02:08 -080073 private final Handler mHandler = new Handler();
RoboErik8a2cfc32014-05-16 11:19:38 -070074 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080075
RoboErike7880d82014-04-30 12:48:25 -070076 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070077 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070078
RoboErik07c70772014-03-20 13:33:52 -070079 // Used to keep track of the current request to show routes for a specific
80 // session so we drop late callbacks properly.
81 private int mShowRoutesRequestId = 0;
82
RoboErika5b02322014-05-07 17:05:49 -070083 // TODO refactor to have per user state for providers. See
84 // MediaRouterService for an example
RoboErik07c70772014-03-20 13:33:52 -070085
RoboErik01fe6612014-02-13 14:19:04 -080086 public MediaSessionService(Context context) {
87 super(context);
88 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -070089 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -070090 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
91 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -080092 }
93
94 @Override
95 public void onStart() {
96 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -070097 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -070098 updateUser();
RoboErik07c70772014-03-20 13:33:52 -070099 }
100
101 /**
102 * Should trigger showing the Media route picker dialog. Right now it just
103 * kicks off a query to all the providers to get routes.
104 *
105 * @param record The session to show the picker for.
106 */
107 public void showRoutePickerForSession(MediaSessionRecord record) {
108 // TODO for now just toggle the route to test (we will only have one
109 // match for now)
RoboErik4646d282014-05-13 10:13:04 -0700110 synchronized (mLock) {
111 if (!mAllSessions.contains(record)) {
112 Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
113 return;
114 }
115 RouteInfo current = record.getRoute();
116 UserRecord user = mUserRecords.get(record.getUserId());
117 if (current != null) {
118 // For now send null to mean the local route
119 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
120 if (proxy != null) {
121 proxy.removeSession(record);
122 }
123 record.selectRoute(null);
124 return;
125 }
126 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
127 mShowRoutesRequestId++;
128 for (int i = providers.size() - 1; i >= 0; i--) {
129 MediaRouteProviderProxy provider = providers.get(i);
130 provider.getRoutes(record, mShowRoutesRequestId);
131 }
RoboErik07c70772014-03-20 13:33:52 -0700132 }
133 }
134
135 /**
136 * Connect a session to the given route.
137 *
138 * @param session The session to connect.
139 * @param route The route to connect to.
140 * @param options The options to use for the connection.
141 */
142 public void connectToRoute(MediaSessionRecord session, RouteInfo route,
143 RouteOptions options) {
144 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700145 if (!mAllSessions.contains(session)) {
146 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
147 return;
148 }
149 UserRecord user = mUserRecords.get(session.getUserId());
150 if (user == null) {
151 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
152 return;
153 }
154 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
RoboErik07c70772014-03-20 13:33:52 -0700155 if (proxy == null) {
156 Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
157 return;
158 }
159 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
RoboErik07c70772014-03-20 13:33:52 -0700160 proxy.connectToRoute(session, route, request);
161 }
RoboErik01fe6612014-02-13 14:19:04 -0800162 }
163
RoboErika8f95142014-05-05 14:23:49 -0700164 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700165 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700166 if (!mAllSessions.contains(record)) {
167 Log.d(TAG, "Unknown session updated. Ignoring.");
168 return;
169 }
RoboErika8f95142014-05-05 14:23:49 -0700170 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700171 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700172 if (record.isActive()) {
173 if (mPrioritySession != null) {
174 Log.w(TAG, "Replacing existing priority session with a new session");
175 }
176 mPrioritySession = record;
177 } else {
178 if (mPrioritySession == record) {
179 mPrioritySession = null;
180 }
RoboErike7880d82014-04-30 12:48:25 -0700181 }
RoboErike7880d82014-04-30 12:48:25 -0700182 }
183 }
184 }
185
RoboErika8f95142014-05-05 14:23:49 -0700186 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
187 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700188 if (!mAllSessions.contains(record)) {
189 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
190 return;
191 }
RoboErika8f95142014-05-05 14:23:49 -0700192 mPriorityStack.onPlaystateChange(record, oldState, newState);
193 }
194 }
195
RoboErika278ea72014-04-24 14:49:01 -0700196 @Override
RoboErik4646d282014-05-13 10:13:04 -0700197 public void onStartUser(int userHandle) {
198 updateUser();
199 }
200
201 @Override
202 public void onSwitchUser(int userHandle) {
203 updateUser();
204 }
205
206 @Override
207 public void onStopUser(int userHandle) {
208 synchronized (mLock) {
209 UserRecord user = mUserRecords.get(userHandle);
210 if (user != null) {
211 destroyUserLocked(user);
212 }
213 }
214 }
215
216 @Override
RoboErika278ea72014-04-24 14:49:01 -0700217 public void monitor() {
218 synchronized (mLock) {
219 // Check for deadlock
220 }
221 }
222
RoboErik4646d282014-05-13 10:13:04 -0700223 protected void enforcePhoneStatePermission(int pid, int uid) {
224 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
225 != PackageManager.PERMISSION_GRANTED) {
226 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
227 }
228 }
229
RoboErik01fe6612014-02-13 14:19:04 -0800230 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700231 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800232 destroySessionLocked(session);
233 }
234 }
235
236 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700237 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800238 destroySessionLocked(session);
239 }
240 }
241
RoboErik4646d282014-05-13 10:13:04 -0700242 private void updateUser() {
243 synchronized (mLock) {
244 int userId = ActivityManager.getCurrentUser();
245 if (mCurrentUserId != userId) {
246 final int oldUserId = mCurrentUserId;
247 mCurrentUserId = userId; // do this first
248
249 UserRecord oldUser = mUserRecords.get(oldUserId);
250 if (oldUser != null) {
251 oldUser.stopLocked();
252 }
253
254 UserRecord newUser = getOrCreateUser(userId);
255 newUser.startLocked();
256 }
257 }
258 }
259
260 /**
261 * Stop the user and unbind from everything.
262 *
263 * @param user The user to dispose of
264 */
265 private void destroyUserLocked(UserRecord user) {
266 user.stopLocked();
267 user.destroyLocked();
268 mUserRecords.remove(user.mUserId);
269 }
270
271 /*
272 * When a session is removed several things need to happen.
273 * 1. We need to remove it from the relevant user.
274 * 2. We need to remove it from the priority stack.
275 * 3. We need to remove it from all sessions.
276 * 4. If this is the system priority session we need to clear it.
277 * 5. We need to unlink to death from the cb binder
278 * 6. We need to tell the session to do any final cleanup (onDestroy)
279 */
RoboErik01fe6612014-02-13 14:19:04 -0800280 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700281 int userId = session.getUserId();
282 UserRecord user = mUserRecords.get(userId);
283 if (user != null) {
284 user.removeSessionLocked(session);
285 }
286
RoboErika8f95142014-05-05 14:23:49 -0700287 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700288 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700289 if (session == mPrioritySession) {
290 mPrioritySession = null;
291 }
RoboErik4646d282014-05-13 10:13:04 -0700292
293 try {
294 session.getCallback().asBinder().unlinkToDeath(session, 0);
295 } catch (Exception e) {
296 // ignore exceptions while destroying a session.
297 }
298 session.onDestroy();
RoboErik01fe6612014-02-13 14:19:04 -0800299 }
300
301 private void enforcePackageName(String packageName, int uid) {
302 if (TextUtils.isEmpty(packageName)) {
303 throw new IllegalArgumentException("packageName may not be empty");
304 }
305 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
306 final int packageCount = packages.length;
307 for (int i = 0; i < packageCount; i++) {
308 if (packageName.equals(packages[i])) {
309 return;
310 }
311 }
312 throw new IllegalArgumentException("packageName is not owned by the calling process");
313 }
314
RoboErike7880d82014-04-30 12:48:25 -0700315 /**
316 * Checks a caller's authorization to register an IRemoteControlDisplay.
317 * Authorization is granted if one of the following is true:
318 * <ul>
319 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
320 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700321 * <li>the caller's listener is one of the enabled notification listeners
322 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700323 * </ul>
324 */
RoboErika5b02322014-05-07 17:05:49 -0700325 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
326 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700327 if (getContext()
328 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
329 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700330 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
331 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700332 throw new SecurityException("Missing permission to control media.");
333 }
334 }
335
RoboErika5b02322014-05-07 17:05:49 -0700336 /**
337 * This checks if the component is an enabled notification listener for the
338 * specified user. Enabled components may only operate on behalf of the user
339 * they're running as.
340 *
341 * @param compName The component that is enabled.
342 * @param userId The user id of the caller.
343 * @param forUserId The user id they're making the request on behalf of.
344 * @return True if the component is enabled, false otherwise
345 */
346 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
347 int forUserId) {
348 if (userId != forUserId) {
349 // You may not access another user's content as an enabled listener.
350 return false;
351 }
RoboErike7880d82014-04-30 12:48:25 -0700352 if (compName != null) {
RoboErike7880d82014-04-30 12:48:25 -0700353 final String enabledNotifListeners = Settings.Secure.getStringForUser(
354 getContext().getContentResolver(),
355 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700356 userId);
RoboErike7880d82014-04-30 12:48:25 -0700357 if (enabledNotifListeners != null) {
358 final String[] components = enabledNotifListeners.split(":");
359 for (int i = 0; i < components.length; i++) {
360 final ComponentName component =
361 ComponentName.unflattenFromString(components[i]);
362 if (component != null) {
363 if (compName.equals(component)) {
364 if (DEBUG) {
365 Log.d(TAG, "ok to get sessions: " + component +
366 " is authorized notification listener");
367 }
368 return true;
369 }
370 }
371 }
372 }
373 if (DEBUG) {
374 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700375 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700376 }
377 }
378 return false;
379 }
380
RoboErika5b02322014-05-07 17:05:49 -0700381 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700382 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800383 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700384 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800385 }
386 }
387
RoboErik4646d282014-05-13 10:13:04 -0700388 /*
389 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700390 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700391 * 2. It needs to be added to all sessions.
392 * 3. It needs to be added to the priority stack.
393 * 4. It needs to be added to the relevant user record.
394 */
RoboErika5b02322014-05-07 17:05:49 -0700395 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
396 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700397
RoboErika5b02322014-05-07 17:05:49 -0700398 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
399 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800400 try {
401 cb.asBinder().linkToDeath(session, 0);
402 } catch (RemoteException e) {
403 throw new RuntimeException("Media Session owner died prematurely.", e);
404 }
RoboErik4646d282014-05-13 10:13:04 -0700405
406 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700407 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700408
409 UserRecord user = getOrCreateUser(userId);
410 user.addSessionLocked(session);
411
RoboErik01fe6612014-02-13 14:19:04 -0800412 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700413 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800414 }
415 return session;
416 }
417
RoboErik4646d282014-05-13 10:13:04 -0700418 private UserRecord getOrCreateUser(int userId) {
419 UserRecord user = mUserRecords.get(userId);
420 if (user == null) {
421 user = new UserRecord(getContext(), userId);
422 mUserRecords.put(userId, user);
423 }
424 return user;
425 }
426
RoboErika8f95142014-05-05 14:23:49 -0700427 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700428 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
429 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700430 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
431 return i;
432 }
433 }
434 return -1;
435 }
436
RoboErike7880d82014-04-30 12:48:25 -0700437 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700438 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700439 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700440 }
441
RoboErik07c70772014-03-20 13:33:52 -0700442 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
443 = new MediaRouteProviderProxy.RoutesListener() {
444 @Override
445 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
446 int reqId) {
447 // TODO for now select the first route to test, eventually add the
448 // new routes to the dialog if it is still open
449 synchronized (mLock) {
450 int index = findIndexOfSessionForIdLocked(sessionId);
451 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700452 MediaSessionRecord record = mAllSessions.get(index);
453 RouteInfo route = routes.get(0);
454 record.selectRoute(route);
455 UserRecord user = mUserRecords.get(record.getUserId());
456 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
457 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700458 }
459 }
460 }
461
462 @Override
463 public void onRouteConnected(String sessionId, RouteInfo route,
464 RouteRequest options, RouteConnectionRecord connection) {
465 synchronized (mLock) {
466 int index = findIndexOfSessionForIdLocked(sessionId);
467 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700468 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700469 session.setRouteConnected(route, options.getConnectionOptions(), connection);
470 }
471 }
472 }
473 };
474
RoboErik4646d282014-05-13 10:13:04 -0700475 /**
476 * Information about a particular user. The contents of this object is
477 * guarded by mLock.
478 */
479 final class UserRecord {
480 private final int mUserId;
481 private final MediaRouteProviderWatcher mRouteProviderWatcher;
482 private final ArrayList<MediaRouteProviderProxy> mProviders
483 = new ArrayList<MediaRouteProviderProxy>();
484 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
485
486 public UserRecord(Context context, int userId) {
487 mUserId = userId;
488 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
489 mProviderWatcherCallback, mHandler, userId);
490 }
491
492 public void startLocked() {
493 mRouteProviderWatcher.start();
494 }
495
496 public void stopLocked() {
497 mRouteProviderWatcher.stop();
498 updateInterestLocked();
499 }
500
501 public void destroyLocked() {
502 for (int i = mSessions.size() - 1; i >= 0; i--) {
503 MediaSessionRecord session = mSessions.get(i);
504 MediaSessionService.this.destroySessionLocked(session);
505 if (session.isConnected()) {
506 session.disconnect(Session.DISCONNECT_REASON_USER_STOPPING);
507 }
508 }
509 }
510
511 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
512 return mProviders;
513 }
514
515 public ArrayList<MediaSessionRecord> getSessionsLocked() {
516 return mSessions;
517 }
518
519 public void addSessionLocked(MediaSessionRecord session) {
520 mSessions.add(session);
521 updateInterestLocked();
522 }
523
524 public void removeSessionLocked(MediaSessionRecord session) {
525 mSessions.remove(session);
526 RouteInfo route = session.getRoute();
527 if (route != null) {
528 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
529 if (provider != null) {
530 provider.removeSession(session);
531 }
532 }
533 updateInterestLocked();
534 }
535
536 public void dumpLocked(PrintWriter pw, String prefix) {
537 pw.println(prefix + "Record for user " + mUserId);
538 String indent = prefix + " ";
539 int size = mProviders.size();
540 pw.println(indent + size + " Providers:");
541 for (int i = 0; i < size; i++) {
542 mProviders.get(i).dump(pw, indent);
543 }
544 pw.println();
545 size = mSessions.size();
546 pw.println(indent + size + " Sessions:");
547 for (int i = 0; i < size; i++) {
548 // Just print the session info, the full session dump will
549 // already be in the list of all sessions.
550 pw.println(indent + mSessions.get(i).getSessionInfo());
551 }
552 }
553
554 public void updateInterestLocked() {
555 // TODO go through the sessions and build up the set of interfaces
556 // we're interested in. Update the provider watcher.
557 // For now, just express interest in all providers for the current
558 // user
559 boolean interested = mUserId == mCurrentUserId;
560 for (int i = mProviders.size() - 1; i >= 0; i--) {
561 mProviders.get(i).setInterested(interested);
562 }
563 }
564
565 private MediaRouteProviderProxy getProviderLocked(String providerId) {
566 for (int i = mProviders.size() - 1; i >= 0; i--) {
567 MediaRouteProviderProxy provider = mProviders.get(i);
568 if (TextUtils.equals(providerId, provider.getId())) {
569 return provider;
570 }
571 }
572 return null;
573 }
574
575 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
576 = new MediaRouteProviderWatcher.Callback() {
577 @Override
578 public void removeProvider(MediaRouteProviderProxy provider) {
579 synchronized (mLock) {
580 mProviders.remove(provider);
581 provider.setRoutesListener(null);
582 provider.setInterested(false);
583 }
584 }
585
586 @Override
587 public void addProvider(MediaRouteProviderProxy provider) {
588 synchronized (mLock) {
589 mProviders.add(provider);
590 provider.setRoutesListener(mRoutesCallback);
591 provider.setInterested(true);
592 }
593 }
594 };
595 }
596
RoboErik07c70772014-03-20 13:33:52 -0700597 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700598 private static final String EXTRA_WAKELOCK_ACQUIRED =
599 "android.media.AudioService.WAKELOCK_ACQUIRED";
600 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
601
RoboErik07c70772014-03-20 13:33:52 -0700602 @Override
RoboErika5b02322014-05-07 17:05:49 -0700603 public ISession createSession(String packageName, ISessionCallback cb, String tag,
604 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800605 final int pid = Binder.getCallingPid();
606 final int uid = Binder.getCallingUid();
607 final long token = Binder.clearCallingIdentity();
608 try {
609 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700610 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
611 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800612 if (cb == null) {
613 throw new IllegalArgumentException("Controller callback cannot be null");
614 }
RoboErika5b02322014-05-07 17:05:49 -0700615 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
616 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700617 } finally {
618 Binder.restoreCallingIdentity(token);
619 }
620 }
621
622 @Override
RoboErika5b02322014-05-07 17:05:49 -0700623 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700624 final int pid = Binder.getCallingPid();
625 final int uid = Binder.getCallingUid();
626 final long token = Binder.clearCallingIdentity();
627
628 try {
RoboErika5b02322014-05-07 17:05:49 -0700629 String packageName = null;
RoboErike7880d82014-04-30 12:48:25 -0700630 if (componentName != null) {
631 // If they gave us a component name verify they own the
632 // package
RoboErika5b02322014-05-07 17:05:49 -0700633 packageName = componentName.getPackageName();
634 enforcePackageName(packageName, uid);
RoboErike7880d82014-04-30 12:48:25 -0700635 }
RoboErika5b02322014-05-07 17:05:49 -0700636 // Check that they can make calls on behalf of the user and
637 // get the final user id
638 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
639 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
640 // Check if they have the permissions or their component is
641 // enabled for the user they're calling from.
642 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
RoboErike7880d82014-04-30 12:48:25 -0700643 ArrayList<IBinder> binders = new ArrayList<IBinder>();
644 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700645 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700646 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700647 int size = records.size();
648 for (int i = 0; i < size; i++) {
649 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700650 }
651 }
652 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800653 } finally {
654 Binder.restoreCallingIdentity(token);
655 }
656 }
RoboErika278ea72014-04-24 14:49:01 -0700657
RoboErik8a2cfc32014-05-16 11:19:38 -0700658 /**
659 * Handles the dispatching of the media button events to one of the
660 * registered listeners, or if there was none, broadcast an
661 * ACTION_MEDIA_BUTTON intent to the rest of the system.
662 *
663 * @param keyEvent a non-null KeyEvent whose key code is one of the
664 * supported media buttons
665 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
666 * while this key event is dispatched.
667 */
668 @Override
669 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
670 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
671 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
672 return;
673 }
674 final int pid = Binder.getCallingPid();
675 final int uid = Binder.getCallingUid();
676 final long token = Binder.clearCallingIdentity();
677
678 try {
679 if (needWakeLock) {
680 mMediaEventWakeLock.acquire();
681 }
682 synchronized (mLock) {
683 MediaSessionRecord mbSession = mPriorityStack
684 .getDefaultMediaButtonSession(mCurrentUserId);
685 if (mbSession != null) {
686 if (DEBUG) {
687 Log.d(TAG, "Sending media key to " + mbSession.getSessionInfo());
688 }
689 mbSession.sendMediaButton(keyEvent,
690 needWakeLock ? mKeyEventDoneReceiver : null);
691 } else {
692 if (DEBUG) {
693 Log.d(TAG, "Sending media key ordered broadcast");
694 }
695 // Fallback to legacy behavior
696 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
697 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
698 if (needWakeLock) {
699 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
700 WAKELOCK_RELEASE_ON_FINISHED);
701 }
702 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
703 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
704 }
705 }
706 } finally {
707 Binder.restoreCallingIdentity(token);
708 }
709 }
710
RoboErika278ea72014-04-24 14:49:01 -0700711 @Override
712 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
713 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
714 != PackageManager.PERMISSION_GRANTED) {
715 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
716 + Binder.getCallingPid()
717 + ", uid=" + Binder.getCallingUid());
718 return;
719 }
720
721 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
722 pw.println();
723
724 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700725 pw.println("Session for calls:" + mPrioritySession);
726 if (mPrioritySession != null) {
727 mPrioritySession.dump(pw, "");
728 }
RoboErik4646d282014-05-13 10:13:04 -0700729 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700730 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700731 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700732 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700733 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700734 }
RoboErika5b02322014-05-07 17:05:49 -0700735 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700736
RoboErik4646d282014-05-13 10:13:04 -0700737 pw.println("User Records:");
738 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700739 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700740 UserRecord user = mUserRecords.get(i);
741 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700742 }
743 }
744 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700745
746 ResultReceiver mKeyEventDoneReceiver = new ResultReceiver(mHandler) {
747 @Override
748 protected void onReceiveResult(int resultCode, Bundle resultData) {
749 synchronized (mLock) {
750 if (mMediaEventWakeLock.isHeld()) {
751 mMediaEventWakeLock.release();
752 }
753 }
754 }
755 };
756
757 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
758 @Override
759 public void onReceive(Context context, Intent intent) {
760 if (intent == null) {
761 return;
762 }
763 Bundle extras = intent.getExtras();
764 if (extras == null) {
765 return;
766 }
767 synchronized (mLock) {
768 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
769 && mMediaEventWakeLock.isHeld()) {
770 mMediaEventWakeLock.release();
771 }
772 }
773 }
774 };
RoboErik01fe6612014-02-13 14:19:04 -0800775 }
776
777}