blob: 9a1c6d58c43e0711a832cd3251f8b1a33ffd28fa [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;
RoboErik42ea7ee2014-05-16 16:27:35 -070033import android.media.session.MediaSession;
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
RoboErik418c10c2014-05-19 09:25:25 -070064 private static final int WAKELOCK_TIMEOUT = 5000;
65
RoboErik01fe6612014-02-13 14:19:04 -080066 private final SessionManagerImpl mSessionManagerImpl;
RoboErik4646d282014-05-13 10:13:04 -070067 // private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErika8f95142014-05-05 14:23:49 -070068 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080069
RoboErik4646d282014-05-13 10:13:04 -070070 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
71 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
72 // private final ArrayList<MediaRouteProviderProxy> mProviders
73 // = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080074 private final Object mLock = new Object();
RoboErik8ae0f342014-02-24 18:02:08 -080075 private final Handler mHandler = new Handler();
RoboErik8a2cfc32014-05-16 11:19:38 -070076 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080077
RoboErike7880d82014-04-30 12:48:25 -070078 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070079 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070080
RoboErik07c70772014-03-20 13:33:52 -070081 // Used to keep track of the current request to show routes for a specific
82 // session so we drop late callbacks properly.
83 private int mShowRoutesRequestId = 0;
84
RoboErika5b02322014-05-07 17:05:49 -070085 // TODO refactor to have per user state for providers. See
86 // MediaRouterService for an example
RoboErik07c70772014-03-20 13:33:52 -070087
RoboErik01fe6612014-02-13 14:19:04 -080088 public MediaSessionService(Context context) {
89 super(context);
90 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -070091 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -070092 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
93 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -080094 }
95
96 @Override
97 public void onStart() {
98 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -070099 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700100 updateUser();
RoboErik07c70772014-03-20 13:33:52 -0700101 }
102
103 /**
104 * Should trigger showing the Media route picker dialog. Right now it just
105 * kicks off a query to all the providers to get routes.
106 *
107 * @param record The session to show the picker for.
108 */
109 public void showRoutePickerForSession(MediaSessionRecord record) {
110 // TODO for now just toggle the route to test (we will only have one
111 // match for now)
RoboErik4646d282014-05-13 10:13:04 -0700112 synchronized (mLock) {
113 if (!mAllSessions.contains(record)) {
114 Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
115 return;
116 }
117 RouteInfo current = record.getRoute();
118 UserRecord user = mUserRecords.get(record.getUserId());
119 if (current != null) {
120 // For now send null to mean the local route
121 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
122 if (proxy != null) {
123 proxy.removeSession(record);
124 }
125 record.selectRoute(null);
126 return;
127 }
128 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
129 mShowRoutesRequestId++;
130 for (int i = providers.size() - 1; i >= 0; i--) {
131 MediaRouteProviderProxy provider = providers.get(i);
132 provider.getRoutes(record, mShowRoutesRequestId);
133 }
RoboErik07c70772014-03-20 13:33:52 -0700134 }
135 }
136
137 /**
138 * Connect a session to the given route.
139 *
140 * @param session The session to connect.
141 * @param route The route to connect to.
142 * @param options The options to use for the connection.
143 */
144 public void connectToRoute(MediaSessionRecord session, RouteInfo route,
145 RouteOptions options) {
146 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700147 if (!mAllSessions.contains(session)) {
148 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
149 return;
150 }
151 UserRecord user = mUserRecords.get(session.getUserId());
152 if (user == null) {
153 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
154 return;
155 }
156 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
RoboErik07c70772014-03-20 13:33:52 -0700157 if (proxy == null) {
158 Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
159 return;
160 }
161 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
RoboErik07c70772014-03-20 13:33:52 -0700162 proxy.connectToRoute(session, route, request);
163 }
RoboErik01fe6612014-02-13 14:19:04 -0800164 }
165
RoboErika8f95142014-05-05 14:23:49 -0700166 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700167 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700168 if (!mAllSessions.contains(record)) {
169 Log.d(TAG, "Unknown session updated. Ignoring.");
170 return;
171 }
RoboErika8f95142014-05-05 14:23:49 -0700172 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700173 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700174 if (record.isActive()) {
175 if (mPrioritySession != null) {
176 Log.w(TAG, "Replacing existing priority session with a new session");
177 }
178 mPrioritySession = record;
179 } else {
180 if (mPrioritySession == record) {
181 mPrioritySession = null;
182 }
RoboErike7880d82014-04-30 12:48:25 -0700183 }
RoboErike7880d82014-04-30 12:48:25 -0700184 }
185 }
186 }
187
RoboErika8f95142014-05-05 14:23:49 -0700188 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
189 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700190 if (!mAllSessions.contains(record)) {
191 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
192 return;
193 }
RoboErika8f95142014-05-05 14:23:49 -0700194 mPriorityStack.onPlaystateChange(record, oldState, newState);
195 }
196 }
197
RoboErika278ea72014-04-24 14:49:01 -0700198 @Override
RoboErik4646d282014-05-13 10:13:04 -0700199 public void onStartUser(int userHandle) {
200 updateUser();
201 }
202
203 @Override
204 public void onSwitchUser(int userHandle) {
205 updateUser();
206 }
207
208 @Override
209 public void onStopUser(int userHandle) {
210 synchronized (mLock) {
211 UserRecord user = mUserRecords.get(userHandle);
212 if (user != null) {
213 destroyUserLocked(user);
214 }
215 }
216 }
217
218 @Override
RoboErika278ea72014-04-24 14:49:01 -0700219 public void monitor() {
220 synchronized (mLock) {
221 // Check for deadlock
222 }
223 }
224
RoboErik4646d282014-05-13 10:13:04 -0700225 protected void enforcePhoneStatePermission(int pid, int uid) {
226 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
227 != PackageManager.PERMISSION_GRANTED) {
228 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
229 }
230 }
231
RoboErik01fe6612014-02-13 14:19:04 -0800232 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700233 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800234 destroySessionLocked(session);
235 }
236 }
237
238 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700239 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800240 destroySessionLocked(session);
241 }
242 }
243
RoboErik4646d282014-05-13 10:13:04 -0700244 private void updateUser() {
245 synchronized (mLock) {
246 int userId = ActivityManager.getCurrentUser();
247 if (mCurrentUserId != userId) {
248 final int oldUserId = mCurrentUserId;
249 mCurrentUserId = userId; // do this first
250
251 UserRecord oldUser = mUserRecords.get(oldUserId);
252 if (oldUser != null) {
253 oldUser.stopLocked();
254 }
255
256 UserRecord newUser = getOrCreateUser(userId);
257 newUser.startLocked();
258 }
259 }
260 }
261
262 /**
263 * Stop the user and unbind from everything.
264 *
265 * @param user The user to dispose of
266 */
267 private void destroyUserLocked(UserRecord user) {
268 user.stopLocked();
269 user.destroyLocked();
270 mUserRecords.remove(user.mUserId);
271 }
272
273 /*
274 * When a session is removed several things need to happen.
275 * 1. We need to remove it from the relevant user.
276 * 2. We need to remove it from the priority stack.
277 * 3. We need to remove it from all sessions.
278 * 4. If this is the system priority session we need to clear it.
279 * 5. We need to unlink to death from the cb binder
280 * 6. We need to tell the session to do any final cleanup (onDestroy)
281 */
RoboErik01fe6612014-02-13 14:19:04 -0800282 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700283 int userId = session.getUserId();
284 UserRecord user = mUserRecords.get(userId);
285 if (user != null) {
286 user.removeSessionLocked(session);
287 }
288
RoboErika8f95142014-05-05 14:23:49 -0700289 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700290 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700291 if (session == mPrioritySession) {
292 mPrioritySession = null;
293 }
RoboErik4646d282014-05-13 10:13:04 -0700294
295 try {
296 session.getCallback().asBinder().unlinkToDeath(session, 0);
297 } catch (Exception e) {
298 // ignore exceptions while destroying a session.
299 }
300 session.onDestroy();
RoboErik01fe6612014-02-13 14:19:04 -0800301 }
302
303 private void enforcePackageName(String packageName, int uid) {
304 if (TextUtils.isEmpty(packageName)) {
305 throw new IllegalArgumentException("packageName may not be empty");
306 }
307 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
308 final int packageCount = packages.length;
309 for (int i = 0; i < packageCount; i++) {
310 if (packageName.equals(packages[i])) {
311 return;
312 }
313 }
314 throw new IllegalArgumentException("packageName is not owned by the calling process");
315 }
316
RoboErike7880d82014-04-30 12:48:25 -0700317 /**
318 * Checks a caller's authorization to register an IRemoteControlDisplay.
319 * Authorization is granted if one of the following is true:
320 * <ul>
321 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
322 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700323 * <li>the caller's listener is one of the enabled notification listeners
324 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700325 * </ul>
326 */
RoboErika5b02322014-05-07 17:05:49 -0700327 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
328 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700329 if (getContext()
330 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
331 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700332 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
333 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700334 throw new SecurityException("Missing permission to control media.");
335 }
336 }
337
RoboErika5b02322014-05-07 17:05:49 -0700338 /**
339 * This checks if the component is an enabled notification listener for the
340 * specified user. Enabled components may only operate on behalf of the user
341 * they're running as.
342 *
343 * @param compName The component that is enabled.
344 * @param userId The user id of the caller.
345 * @param forUserId The user id they're making the request on behalf of.
346 * @return True if the component is enabled, false otherwise
347 */
348 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
349 int forUserId) {
350 if (userId != forUserId) {
351 // You may not access another user's content as an enabled listener.
352 return false;
353 }
RoboErike7880d82014-04-30 12:48:25 -0700354 if (compName != null) {
RoboErike7880d82014-04-30 12:48:25 -0700355 final String enabledNotifListeners = Settings.Secure.getStringForUser(
356 getContext().getContentResolver(),
357 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700358 userId);
RoboErike7880d82014-04-30 12:48:25 -0700359 if (enabledNotifListeners != null) {
360 final String[] components = enabledNotifListeners.split(":");
361 for (int i = 0; i < components.length; i++) {
362 final ComponentName component =
363 ComponentName.unflattenFromString(components[i]);
364 if (component != null) {
365 if (compName.equals(component)) {
366 if (DEBUG) {
367 Log.d(TAG, "ok to get sessions: " + component +
368 " is authorized notification listener");
369 }
370 return true;
371 }
372 }
373 }
374 }
375 if (DEBUG) {
376 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700377 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700378 }
379 }
380 return false;
381 }
382
RoboErika5b02322014-05-07 17:05:49 -0700383 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700384 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800385 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700386 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800387 }
388 }
389
RoboErik4646d282014-05-13 10:13:04 -0700390 /*
391 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700392 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700393 * 2. It needs to be added to all sessions.
394 * 3. It needs to be added to the priority stack.
395 * 4. It needs to be added to the relevant user record.
396 */
RoboErika5b02322014-05-07 17:05:49 -0700397 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
398 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700399
RoboErika5b02322014-05-07 17:05:49 -0700400 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
401 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800402 try {
403 cb.asBinder().linkToDeath(session, 0);
404 } catch (RemoteException e) {
405 throw new RuntimeException("Media Session owner died prematurely.", e);
406 }
RoboErik4646d282014-05-13 10:13:04 -0700407
408 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700409 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700410
411 UserRecord user = getOrCreateUser(userId);
412 user.addSessionLocked(session);
413
RoboErik01fe6612014-02-13 14:19:04 -0800414 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700415 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800416 }
417 return session;
418 }
419
RoboErik4646d282014-05-13 10:13:04 -0700420 private UserRecord getOrCreateUser(int userId) {
421 UserRecord user = mUserRecords.get(userId);
422 if (user == null) {
423 user = new UserRecord(getContext(), userId);
424 mUserRecords.put(userId, user);
425 }
426 return user;
427 }
428
RoboErika8f95142014-05-05 14:23:49 -0700429 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700430 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
431 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700432 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
433 return i;
434 }
435 }
436 return -1;
437 }
438
RoboErike7880d82014-04-30 12:48:25 -0700439 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700440 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700441 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700442 }
443
RoboErik07c70772014-03-20 13:33:52 -0700444 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
445 = new MediaRouteProviderProxy.RoutesListener() {
446 @Override
447 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
448 int reqId) {
449 // TODO for now select the first route to test, eventually add the
450 // new routes to the dialog if it is still open
451 synchronized (mLock) {
452 int index = findIndexOfSessionForIdLocked(sessionId);
453 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700454 MediaSessionRecord record = mAllSessions.get(index);
455 RouteInfo route = routes.get(0);
456 record.selectRoute(route);
457 UserRecord user = mUserRecords.get(record.getUserId());
458 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
459 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700460 }
461 }
462 }
463
464 @Override
465 public void onRouteConnected(String sessionId, RouteInfo route,
466 RouteRequest options, RouteConnectionRecord connection) {
467 synchronized (mLock) {
468 int index = findIndexOfSessionForIdLocked(sessionId);
469 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700470 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700471 session.setRouteConnected(route, options.getConnectionOptions(), connection);
472 }
473 }
474 }
475 };
476
RoboErik4646d282014-05-13 10:13:04 -0700477 /**
478 * Information about a particular user. The contents of this object is
479 * guarded by mLock.
480 */
481 final class UserRecord {
482 private final int mUserId;
483 private final MediaRouteProviderWatcher mRouteProviderWatcher;
484 private final ArrayList<MediaRouteProviderProxy> mProviders
485 = new ArrayList<MediaRouteProviderProxy>();
486 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
487
488 public UserRecord(Context context, int userId) {
489 mUserId = userId;
490 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
491 mProviderWatcherCallback, mHandler, userId);
492 }
493
494 public void startLocked() {
495 mRouteProviderWatcher.start();
496 }
497
498 public void stopLocked() {
499 mRouteProviderWatcher.stop();
500 updateInterestLocked();
501 }
502
503 public void destroyLocked() {
504 for (int i = mSessions.size() - 1; i >= 0; i--) {
505 MediaSessionRecord session = mSessions.get(i);
506 MediaSessionService.this.destroySessionLocked(session);
507 if (session.isConnected()) {
RoboErik42ea7ee2014-05-16 16:27:35 -0700508 session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
RoboErik4646d282014-05-13 10:13:04 -0700509 }
510 }
511 }
512
513 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
514 return mProviders;
515 }
516
517 public ArrayList<MediaSessionRecord> getSessionsLocked() {
518 return mSessions;
519 }
520
521 public void addSessionLocked(MediaSessionRecord session) {
522 mSessions.add(session);
523 updateInterestLocked();
524 }
525
526 public void removeSessionLocked(MediaSessionRecord session) {
527 mSessions.remove(session);
528 RouteInfo route = session.getRoute();
529 if (route != null) {
530 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
531 if (provider != null) {
532 provider.removeSession(session);
533 }
534 }
535 updateInterestLocked();
536 }
537
538 public void dumpLocked(PrintWriter pw, String prefix) {
539 pw.println(prefix + "Record for user " + mUserId);
540 String indent = prefix + " ";
541 int size = mProviders.size();
542 pw.println(indent + size + " Providers:");
543 for (int i = 0; i < size; i++) {
544 mProviders.get(i).dump(pw, indent);
545 }
546 pw.println();
547 size = mSessions.size();
548 pw.println(indent + size + " Sessions:");
549 for (int i = 0; i < size; i++) {
550 // Just print the session info, the full session dump will
551 // already be in the list of all sessions.
552 pw.println(indent + mSessions.get(i).getSessionInfo());
553 }
554 }
555
556 public void updateInterestLocked() {
557 // TODO go through the sessions and build up the set of interfaces
558 // we're interested in. Update the provider watcher.
559 // For now, just express interest in all providers for the current
560 // user
561 boolean interested = mUserId == mCurrentUserId;
562 for (int i = mProviders.size() - 1; i >= 0; i--) {
563 mProviders.get(i).setInterested(interested);
564 }
565 }
566
567 private MediaRouteProviderProxy getProviderLocked(String providerId) {
568 for (int i = mProviders.size() - 1; i >= 0; i--) {
569 MediaRouteProviderProxy provider = mProviders.get(i);
570 if (TextUtils.equals(providerId, provider.getId())) {
571 return provider;
572 }
573 }
574 return null;
575 }
576
577 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
578 = new MediaRouteProviderWatcher.Callback() {
579 @Override
580 public void removeProvider(MediaRouteProviderProxy provider) {
581 synchronized (mLock) {
582 mProviders.remove(provider);
583 provider.setRoutesListener(null);
584 provider.setInterested(false);
585 }
586 }
587
588 @Override
589 public void addProvider(MediaRouteProviderProxy provider) {
590 synchronized (mLock) {
591 mProviders.add(provider);
592 provider.setRoutesListener(mRoutesCallback);
593 provider.setInterested(true);
594 }
595 }
596 };
597 }
598
RoboErik07c70772014-03-20 13:33:52 -0700599 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700600 private static final String EXTRA_WAKELOCK_ACQUIRED =
601 "android.media.AudioService.WAKELOCK_ACQUIRED";
602 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
603
RoboErik07c70772014-03-20 13:33:52 -0700604 @Override
RoboErika5b02322014-05-07 17:05:49 -0700605 public ISession createSession(String packageName, ISessionCallback cb, String tag,
606 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800607 final int pid = Binder.getCallingPid();
608 final int uid = Binder.getCallingUid();
609 final long token = Binder.clearCallingIdentity();
610 try {
611 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700612 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
613 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800614 if (cb == null) {
615 throw new IllegalArgumentException("Controller callback cannot be null");
616 }
RoboErika5b02322014-05-07 17:05:49 -0700617 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
618 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700619 } finally {
620 Binder.restoreCallingIdentity(token);
621 }
622 }
623
624 @Override
RoboErika5b02322014-05-07 17:05:49 -0700625 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700626 final int pid = Binder.getCallingPid();
627 final int uid = Binder.getCallingUid();
628 final long token = Binder.clearCallingIdentity();
629
630 try {
RoboErika5b02322014-05-07 17:05:49 -0700631 String packageName = null;
RoboErike7880d82014-04-30 12:48:25 -0700632 if (componentName != null) {
633 // If they gave us a component name verify they own the
634 // package
RoboErika5b02322014-05-07 17:05:49 -0700635 packageName = componentName.getPackageName();
636 enforcePackageName(packageName, uid);
RoboErike7880d82014-04-30 12:48:25 -0700637 }
RoboErika5b02322014-05-07 17:05:49 -0700638 // Check that they can make calls on behalf of the user and
639 // get the final user id
640 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
641 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
642 // Check if they have the permissions or their component is
643 // enabled for the user they're calling from.
644 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
RoboErike7880d82014-04-30 12:48:25 -0700645 ArrayList<IBinder> binders = new ArrayList<IBinder>();
646 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700647 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700648 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700649 int size = records.size();
650 for (int i = 0; i < size; i++) {
651 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700652 }
653 }
654 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800655 } finally {
656 Binder.restoreCallingIdentity(token);
657 }
658 }
RoboErika278ea72014-04-24 14:49:01 -0700659
RoboErik8a2cfc32014-05-16 11:19:38 -0700660 /**
661 * Handles the dispatching of the media button events to one of the
662 * registered listeners, or if there was none, broadcast an
663 * ACTION_MEDIA_BUTTON intent to the rest of the system.
664 *
665 * @param keyEvent a non-null KeyEvent whose key code is one of the
666 * supported media buttons
667 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
668 * while this key event is dispatched.
669 */
670 @Override
671 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
672 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
673 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
674 return;
675 }
676 final int pid = Binder.getCallingPid();
677 final int uid = Binder.getCallingUid();
678 final long token = Binder.clearCallingIdentity();
679
680 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700681 synchronized (mLock) {
682 MediaSessionRecord mbSession = mPriorityStack
683 .getDefaultMediaButtonSession(mCurrentUserId);
684 if (mbSession != null) {
685 if (DEBUG) {
686 Log.d(TAG, "Sending media key to " + mbSession.getSessionInfo());
687 }
RoboErik418c10c2014-05-19 09:25:25 -0700688 if (needWakeLock) {
689 mKeyEventReceiver.aquireWakeLockLocked();
690 }
691 // If we don't need a wakelock use -1 as the id so we
692 // won't release it later
RoboErik8a2cfc32014-05-16 11:19:38 -0700693 mbSession.sendMediaButton(keyEvent,
RoboErik418c10c2014-05-19 09:25:25 -0700694 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
695 mKeyEventReceiver);
RoboErik8a2cfc32014-05-16 11:19:38 -0700696 } else {
RoboErik418c10c2014-05-19 09:25:25 -0700697 if (needWakeLock) {
698 mMediaEventWakeLock.acquire();
699 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700700 if (DEBUG) {
701 Log.d(TAG, "Sending media key ordered broadcast");
702 }
703 // Fallback to legacy behavior
704 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
705 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
706 if (needWakeLock) {
707 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
708 WAKELOCK_RELEASE_ON_FINISHED);
709 }
710 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
711 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
712 }
713 }
714 } finally {
715 Binder.restoreCallingIdentity(token);
716 }
717 }
718
RoboErika278ea72014-04-24 14:49:01 -0700719 @Override
720 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
721 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
722 != PackageManager.PERMISSION_GRANTED) {
723 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
724 + Binder.getCallingPid()
725 + ", uid=" + Binder.getCallingUid());
726 return;
727 }
728
729 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
730 pw.println();
731
732 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700733 pw.println("Session for calls:" + mPrioritySession);
734 if (mPrioritySession != null) {
735 mPrioritySession.dump(pw, "");
736 }
RoboErik4646d282014-05-13 10:13:04 -0700737 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700738 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700739 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700740 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700741 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700742 }
RoboErika5b02322014-05-07 17:05:49 -0700743 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700744
RoboErik4646d282014-05-13 10:13:04 -0700745 pw.println("User Records:");
746 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700747 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700748 UserRecord user = mUserRecords.get(i);
749 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700750 }
751 }
752 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700753
RoboErik418c10c2014-05-19 09:25:25 -0700754 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
755
756 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
757 private final Handler mHandler;
758 private int mRefCount = 0;
759 private int mLastTimeoutId = 0;
760
761 public KeyEventWakeLockReceiver(Handler handler) {
762 super(handler);
763 mHandler = handler;
764 }
765
766 public void onTimeout() {
767 synchronized (mLock) {
768 if (mRefCount == 0) {
769 // We've already released it, so just return
770 return;
771 }
772 mLastTimeoutId++;
773 mRefCount = 0;
774 releaseWakeLockLocked();
775 }
776 }
777
778 public void aquireWakeLockLocked() {
779 if (mRefCount == 0) {
780 mMediaEventWakeLock.acquire();
781 }
782 mRefCount++;
783 mHandler.removeCallbacks(this);
784 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
785
786 }
787
788 @Override
789 public void run() {
790 onTimeout();
791 }
792
RoboErik8a2cfc32014-05-16 11:19:38 -0700793 @Override
794 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -0700795 if (resultCode < mLastTimeoutId) {
796 // Ignore results from calls that were before the last
797 // timeout, just in case.
798 return;
799 } else {
800 synchronized (mLock) {
801 if (mRefCount > 0) {
802 mRefCount--;
803 if (mRefCount == 0) {
804 releaseWakeLockLocked();
805 }
806 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700807 }
808 }
809 }
RoboErik418c10c2014-05-19 09:25:25 -0700810
811 private void releaseWakeLockLocked() {
812 mMediaEventWakeLock.release();
813 mHandler.removeCallbacks(this);
814 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700815 };
816
817 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
818 @Override
819 public void onReceive(Context context, Intent intent) {
820 if (intent == null) {
821 return;
822 }
823 Bundle extras = intent.getExtras();
824 if (extras == null) {
825 return;
826 }
827 synchronized (mLock) {
828 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
829 && mMediaEventWakeLock.isHeld()) {
830 mMediaEventWakeLock.release();
831 }
832 }
833 }
834 };
RoboErik01fe6612014-02-13 14:19:04 -0800835 }
836
837}