blob: 9d85167576260974c1b82e4c2c63d3a98df72b07 [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;
RoboErik9a9d0b52014-05-20 14:53:39 -070022import android.app.KeyguardManager;
23import android.content.ActivityNotFoundException;
RoboErik8a2cfc32014-05-16 11:19:38 -070024import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070025import android.content.ComponentName;
RoboErik01fe6612014-02-13 14:19:04 -080026import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070027import android.content.Intent;
RoboErika278ea72014-04-24 14:49:01 -070028import android.content.pm.PackageManager;
RoboErik07c70772014-03-20 13:33:52 -070029import android.media.routeprovider.RouteRequest;
30import android.media.session.ISession;
31import android.media.session.ISessionCallback;
32import android.media.session.ISessionManager;
33import android.media.session.RouteInfo;
34import android.media.session.RouteOptions;
RoboErik42ea7ee2014-05-16 16:27:35 -070035import android.media.session.MediaSession;
RoboErik01fe6612014-02-13 14:19:04 -080036import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070037import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080038import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070039import android.os.IBinder;
RoboErik8a2cfc32014-05-16 11:19:38 -070040import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080041import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070042import android.os.ResultReceiver;
RoboErike7880d82014-04-30 12:48:25 -070043import android.os.UserHandle;
44import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070045import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080046import android.text.TextUtils;
47import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070048import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070049import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080050
51import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070052import com.android.server.Watchdog;
53import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080054
RoboErika278ea72014-04-24 14:49:01 -070055import java.io.FileDescriptor;
56import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080057import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070058import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080059
60/**
61 * System implementation of MediaSessionManager
62 */
RoboErika278ea72014-04-24 14:49:01 -070063public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080064 private static final String TAG = "MediaSessionService";
65 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
66
RoboErik418c10c2014-05-19 09:25:25 -070067 private static final int WAKELOCK_TIMEOUT = 5000;
68
RoboErik01fe6612014-02-13 14:19:04 -080069 private final SessionManagerImpl mSessionManagerImpl;
RoboErik4646d282014-05-13 10:13:04 -070070 // private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErika8f95142014-05-05 14:23:49 -070071 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080072
RoboErik4646d282014-05-13 10:13:04 -070073 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
74 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
75 // private final ArrayList<MediaRouteProviderProxy> mProviders
76 // = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080077 private final Object mLock = new Object();
RoboErik8ae0f342014-02-24 18:02:08 -080078 private final Handler mHandler = new Handler();
RoboErik8a2cfc32014-05-16 11:19:38 -070079 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080080
RoboErik9a9d0b52014-05-20 14:53:39 -070081 private KeyguardManager mKeyguardManager;
82
RoboErike7880d82014-04-30 12:48:25 -070083 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070084 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070085
RoboErik07c70772014-03-20 13:33:52 -070086 // Used to keep track of the current request to show routes for a specific
87 // session so we drop late callbacks properly.
88 private int mShowRoutesRequestId = 0;
89
RoboErika5b02322014-05-07 17:05:49 -070090 // TODO refactor to have per user state for providers. See
91 // MediaRouterService for an example
RoboErik07c70772014-03-20 13:33:52 -070092
RoboErik01fe6612014-02-13 14:19:04 -080093 public MediaSessionService(Context context) {
94 super(context);
95 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -070096 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -070097 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
98 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -080099 }
100
101 @Override
102 public void onStart() {
103 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700104 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700105 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700106 mKeyguardManager =
107 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErik07c70772014-03-20 13:33:52 -0700108 }
109
110 /**
111 * Should trigger showing the Media route picker dialog. Right now it just
112 * kicks off a query to all the providers to get routes.
113 *
114 * @param record The session to show the picker for.
115 */
116 public void showRoutePickerForSession(MediaSessionRecord record) {
117 // TODO for now just toggle the route to test (we will only have one
118 // match for now)
RoboErik4646d282014-05-13 10:13:04 -0700119 synchronized (mLock) {
120 if (!mAllSessions.contains(record)) {
121 Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
122 return;
123 }
124 RouteInfo current = record.getRoute();
125 UserRecord user = mUserRecords.get(record.getUserId());
126 if (current != null) {
127 // For now send null to mean the local route
128 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
129 if (proxy != null) {
130 proxy.removeSession(record);
131 }
132 record.selectRoute(null);
133 return;
134 }
135 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
136 mShowRoutesRequestId++;
137 for (int i = providers.size() - 1; i >= 0; i--) {
138 MediaRouteProviderProxy provider = providers.get(i);
139 provider.getRoutes(record, mShowRoutesRequestId);
140 }
RoboErik07c70772014-03-20 13:33:52 -0700141 }
142 }
143
144 /**
145 * Connect a session to the given route.
146 *
147 * @param session The session to connect.
148 * @param route The route to connect to.
149 * @param options The options to use for the connection.
150 */
151 public void connectToRoute(MediaSessionRecord session, RouteInfo route,
152 RouteOptions options) {
153 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700154 if (!mAllSessions.contains(session)) {
155 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
156 return;
157 }
158 UserRecord user = mUserRecords.get(session.getUserId());
159 if (user == null) {
160 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
161 return;
162 }
163 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
RoboErik07c70772014-03-20 13:33:52 -0700164 if (proxy == null) {
165 Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
166 return;
167 }
168 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
RoboErik07c70772014-03-20 13:33:52 -0700169 proxy.connectToRoute(session, route, request);
170 }
RoboErik01fe6612014-02-13 14:19:04 -0800171 }
172
RoboErika8f95142014-05-05 14:23:49 -0700173 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700174 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700175 if (!mAllSessions.contains(record)) {
176 Log.d(TAG, "Unknown session updated. Ignoring.");
177 return;
178 }
RoboErika8f95142014-05-05 14:23:49 -0700179 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700180 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700181 if (record.isActive()) {
182 if (mPrioritySession != null) {
183 Log.w(TAG, "Replacing existing priority session with a new session");
184 }
185 mPrioritySession = record;
186 } else {
187 if (mPrioritySession == record) {
188 mPrioritySession = null;
189 }
RoboErike7880d82014-04-30 12:48:25 -0700190 }
RoboErike7880d82014-04-30 12:48:25 -0700191 }
192 }
193 }
194
RoboErika8f95142014-05-05 14:23:49 -0700195 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
196 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700197 if (!mAllSessions.contains(record)) {
198 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
199 return;
200 }
RoboErika8f95142014-05-05 14:23:49 -0700201 mPriorityStack.onPlaystateChange(record, oldState, newState);
202 }
203 }
204
RoboErika278ea72014-04-24 14:49:01 -0700205 @Override
RoboErik4646d282014-05-13 10:13:04 -0700206 public void onStartUser(int userHandle) {
207 updateUser();
208 }
209
210 @Override
211 public void onSwitchUser(int userHandle) {
212 updateUser();
213 }
214
215 @Override
216 public void onStopUser(int userHandle) {
217 synchronized (mLock) {
218 UserRecord user = mUserRecords.get(userHandle);
219 if (user != null) {
220 destroyUserLocked(user);
221 }
222 }
223 }
224
225 @Override
RoboErika278ea72014-04-24 14:49:01 -0700226 public void monitor() {
227 synchronized (mLock) {
228 // Check for deadlock
229 }
230 }
231
RoboErik4646d282014-05-13 10:13:04 -0700232 protected void enforcePhoneStatePermission(int pid, int uid) {
233 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
234 != PackageManager.PERMISSION_GRANTED) {
235 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
236 }
237 }
238
RoboErik01fe6612014-02-13 14:19:04 -0800239 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700240 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800241 destroySessionLocked(session);
242 }
243 }
244
245 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700246 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800247 destroySessionLocked(session);
248 }
249 }
250
RoboErik4646d282014-05-13 10:13:04 -0700251 private void updateUser() {
252 synchronized (mLock) {
253 int userId = ActivityManager.getCurrentUser();
254 if (mCurrentUserId != userId) {
255 final int oldUserId = mCurrentUserId;
256 mCurrentUserId = userId; // do this first
257
258 UserRecord oldUser = mUserRecords.get(oldUserId);
259 if (oldUser != null) {
260 oldUser.stopLocked();
261 }
262
263 UserRecord newUser = getOrCreateUser(userId);
264 newUser.startLocked();
265 }
266 }
267 }
268
269 /**
270 * Stop the user and unbind from everything.
271 *
272 * @param user The user to dispose of
273 */
274 private void destroyUserLocked(UserRecord user) {
275 user.stopLocked();
276 user.destroyLocked();
277 mUserRecords.remove(user.mUserId);
278 }
279
280 /*
281 * When a session is removed several things need to happen.
282 * 1. We need to remove it from the relevant user.
283 * 2. We need to remove it from the priority stack.
284 * 3. We need to remove it from all sessions.
285 * 4. If this is the system priority session we need to clear it.
286 * 5. We need to unlink to death from the cb binder
287 * 6. We need to tell the session to do any final cleanup (onDestroy)
288 */
RoboErik01fe6612014-02-13 14:19:04 -0800289 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700290 int userId = session.getUserId();
291 UserRecord user = mUserRecords.get(userId);
292 if (user != null) {
293 user.removeSessionLocked(session);
294 }
295
RoboErika8f95142014-05-05 14:23:49 -0700296 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700297 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700298 if (session == mPrioritySession) {
299 mPrioritySession = null;
300 }
RoboErik4646d282014-05-13 10:13:04 -0700301
302 try {
303 session.getCallback().asBinder().unlinkToDeath(session, 0);
304 } catch (Exception e) {
305 // ignore exceptions while destroying a session.
306 }
307 session.onDestroy();
RoboErik01fe6612014-02-13 14:19:04 -0800308 }
309
310 private void enforcePackageName(String packageName, int uid) {
311 if (TextUtils.isEmpty(packageName)) {
312 throw new IllegalArgumentException("packageName may not be empty");
313 }
314 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
315 final int packageCount = packages.length;
316 for (int i = 0; i < packageCount; i++) {
317 if (packageName.equals(packages[i])) {
318 return;
319 }
320 }
321 throw new IllegalArgumentException("packageName is not owned by the calling process");
322 }
323
RoboErike7880d82014-04-30 12:48:25 -0700324 /**
325 * Checks a caller's authorization to register an IRemoteControlDisplay.
326 * Authorization is granted if one of the following is true:
327 * <ul>
328 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
329 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700330 * <li>the caller's listener is one of the enabled notification listeners
331 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700332 * </ul>
333 */
RoboErika5b02322014-05-07 17:05:49 -0700334 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
335 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700336 if (getContext()
337 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
338 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700339 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
340 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700341 throw new SecurityException("Missing permission to control media.");
342 }
343 }
344
RoboErika5b02322014-05-07 17:05:49 -0700345 /**
346 * This checks if the component is an enabled notification listener for the
347 * specified user. Enabled components may only operate on behalf of the user
348 * they're running as.
349 *
350 * @param compName The component that is enabled.
351 * @param userId The user id of the caller.
352 * @param forUserId The user id they're making the request on behalf of.
353 * @return True if the component is enabled, false otherwise
354 */
355 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
356 int forUserId) {
357 if (userId != forUserId) {
358 // You may not access another user's content as an enabled listener.
359 return false;
360 }
RoboErike7880d82014-04-30 12:48:25 -0700361 if (compName != null) {
RoboErike7880d82014-04-30 12:48:25 -0700362 final String enabledNotifListeners = Settings.Secure.getStringForUser(
363 getContext().getContentResolver(),
364 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700365 userId);
RoboErike7880d82014-04-30 12:48:25 -0700366 if (enabledNotifListeners != null) {
367 final String[] components = enabledNotifListeners.split(":");
368 for (int i = 0; i < components.length; i++) {
369 final ComponentName component =
370 ComponentName.unflattenFromString(components[i]);
371 if (component != null) {
372 if (compName.equals(component)) {
373 if (DEBUG) {
374 Log.d(TAG, "ok to get sessions: " + component +
375 " is authorized notification listener");
376 }
377 return true;
378 }
379 }
380 }
381 }
382 if (DEBUG) {
383 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700384 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700385 }
386 }
387 return false;
388 }
389
RoboErika5b02322014-05-07 17:05:49 -0700390 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700391 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800392 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700393 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800394 }
395 }
396
RoboErik4646d282014-05-13 10:13:04 -0700397 /*
398 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700399 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700400 * 2. It needs to be added to all sessions.
401 * 3. It needs to be added to the priority stack.
402 * 4. It needs to be added to the relevant user record.
403 */
RoboErika5b02322014-05-07 17:05:49 -0700404 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
405 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700406
RoboErika5b02322014-05-07 17:05:49 -0700407 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
408 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800409 try {
410 cb.asBinder().linkToDeath(session, 0);
411 } catch (RemoteException e) {
412 throw new RuntimeException("Media Session owner died prematurely.", e);
413 }
RoboErik4646d282014-05-13 10:13:04 -0700414
415 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700416 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700417
418 UserRecord user = getOrCreateUser(userId);
419 user.addSessionLocked(session);
420
RoboErik01fe6612014-02-13 14:19:04 -0800421 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700422 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800423 }
424 return session;
425 }
426
RoboErik4646d282014-05-13 10:13:04 -0700427 private UserRecord getOrCreateUser(int userId) {
428 UserRecord user = mUserRecords.get(userId);
429 if (user == null) {
430 user = new UserRecord(getContext(), userId);
431 mUserRecords.put(userId, user);
432 }
433 return user;
434 }
435
RoboErika8f95142014-05-05 14:23:49 -0700436 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700437 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
438 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700439 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
440 return i;
441 }
442 }
443 return -1;
444 }
445
RoboErike7880d82014-04-30 12:48:25 -0700446 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700447 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700448 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700449 }
450
RoboErik07c70772014-03-20 13:33:52 -0700451 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
452 = new MediaRouteProviderProxy.RoutesListener() {
453 @Override
454 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
455 int reqId) {
456 // TODO for now select the first route to test, eventually add the
457 // new routes to the dialog if it is still open
458 synchronized (mLock) {
459 int index = findIndexOfSessionForIdLocked(sessionId);
460 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700461 MediaSessionRecord record = mAllSessions.get(index);
462 RouteInfo route = routes.get(0);
463 record.selectRoute(route);
464 UserRecord user = mUserRecords.get(record.getUserId());
465 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
466 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700467 }
468 }
469 }
470
471 @Override
472 public void onRouteConnected(String sessionId, RouteInfo route,
473 RouteRequest options, RouteConnectionRecord connection) {
474 synchronized (mLock) {
475 int index = findIndexOfSessionForIdLocked(sessionId);
476 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700477 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700478 session.setRouteConnected(route, options.getConnectionOptions(), connection);
479 }
480 }
481 }
482 };
483
RoboErik4646d282014-05-13 10:13:04 -0700484 /**
485 * Information about a particular user. The contents of this object is
486 * guarded by mLock.
487 */
488 final class UserRecord {
489 private final int mUserId;
490 private final MediaRouteProviderWatcher mRouteProviderWatcher;
491 private final ArrayList<MediaRouteProviderProxy> mProviders
492 = new ArrayList<MediaRouteProviderProxy>();
493 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
494
495 public UserRecord(Context context, int userId) {
496 mUserId = userId;
497 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
498 mProviderWatcherCallback, mHandler, userId);
499 }
500
501 public void startLocked() {
502 mRouteProviderWatcher.start();
503 }
504
505 public void stopLocked() {
506 mRouteProviderWatcher.stop();
507 updateInterestLocked();
508 }
509
510 public void destroyLocked() {
511 for (int i = mSessions.size() - 1; i >= 0; i--) {
512 MediaSessionRecord session = mSessions.get(i);
513 MediaSessionService.this.destroySessionLocked(session);
514 if (session.isConnected()) {
RoboErik42ea7ee2014-05-16 16:27:35 -0700515 session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
RoboErik4646d282014-05-13 10:13:04 -0700516 }
517 }
518 }
519
520 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
521 return mProviders;
522 }
523
524 public ArrayList<MediaSessionRecord> getSessionsLocked() {
525 return mSessions;
526 }
527
528 public void addSessionLocked(MediaSessionRecord session) {
529 mSessions.add(session);
530 updateInterestLocked();
531 }
532
533 public void removeSessionLocked(MediaSessionRecord session) {
534 mSessions.remove(session);
535 RouteInfo route = session.getRoute();
536 if (route != null) {
537 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
538 if (provider != null) {
539 provider.removeSession(session);
540 }
541 }
542 updateInterestLocked();
543 }
544
545 public void dumpLocked(PrintWriter pw, String prefix) {
546 pw.println(prefix + "Record for user " + mUserId);
547 String indent = prefix + " ";
548 int size = mProviders.size();
549 pw.println(indent + size + " Providers:");
550 for (int i = 0; i < size; i++) {
551 mProviders.get(i).dump(pw, indent);
552 }
553 pw.println();
554 size = mSessions.size();
555 pw.println(indent + size + " Sessions:");
556 for (int i = 0; i < size; i++) {
557 // Just print the session info, the full session dump will
558 // already be in the list of all sessions.
559 pw.println(indent + mSessions.get(i).getSessionInfo());
560 }
561 }
562
563 public void updateInterestLocked() {
564 // TODO go through the sessions and build up the set of interfaces
565 // we're interested in. Update the provider watcher.
566 // For now, just express interest in all providers for the current
567 // user
568 boolean interested = mUserId == mCurrentUserId;
569 for (int i = mProviders.size() - 1; i >= 0; i--) {
570 mProviders.get(i).setInterested(interested);
571 }
572 }
573
574 private MediaRouteProviderProxy getProviderLocked(String providerId) {
575 for (int i = mProviders.size() - 1; i >= 0; i--) {
576 MediaRouteProviderProxy provider = mProviders.get(i);
577 if (TextUtils.equals(providerId, provider.getId())) {
578 return provider;
579 }
580 }
581 return null;
582 }
583
584 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
585 = new MediaRouteProviderWatcher.Callback() {
586 @Override
587 public void removeProvider(MediaRouteProviderProxy provider) {
588 synchronized (mLock) {
589 mProviders.remove(provider);
590 provider.setRoutesListener(null);
591 provider.setInterested(false);
592 }
593 }
594
595 @Override
596 public void addProvider(MediaRouteProviderProxy provider) {
597 synchronized (mLock) {
598 mProviders.add(provider);
599 provider.setRoutesListener(mRoutesCallback);
600 provider.setInterested(true);
601 }
602 }
603 };
604 }
605
RoboErik07c70772014-03-20 13:33:52 -0700606 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700607 private static final String EXTRA_WAKELOCK_ACQUIRED =
608 "android.media.AudioService.WAKELOCK_ACQUIRED";
609 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
610
RoboErik9a9d0b52014-05-20 14:53:39 -0700611 private boolean mVoiceButtonDown = false;
612 private boolean mVoiceButtonHandled = false;
613
RoboErik07c70772014-03-20 13:33:52 -0700614 @Override
RoboErika5b02322014-05-07 17:05:49 -0700615 public ISession createSession(String packageName, ISessionCallback cb, String tag,
616 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800617 final int pid = Binder.getCallingPid();
618 final int uid = Binder.getCallingUid();
619 final long token = Binder.clearCallingIdentity();
620 try {
621 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700622 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
623 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800624 if (cb == null) {
625 throw new IllegalArgumentException("Controller callback cannot be null");
626 }
RoboErika5b02322014-05-07 17:05:49 -0700627 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
628 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700629 } finally {
630 Binder.restoreCallingIdentity(token);
631 }
632 }
633
634 @Override
RoboErika5b02322014-05-07 17:05:49 -0700635 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700636 final int pid = Binder.getCallingPid();
637 final int uid = Binder.getCallingUid();
638 final long token = Binder.clearCallingIdentity();
639
640 try {
RoboErika5b02322014-05-07 17:05:49 -0700641 String packageName = null;
RoboErike7880d82014-04-30 12:48:25 -0700642 if (componentName != null) {
643 // If they gave us a component name verify they own the
644 // package
RoboErika5b02322014-05-07 17:05:49 -0700645 packageName = componentName.getPackageName();
646 enforcePackageName(packageName, uid);
RoboErike7880d82014-04-30 12:48:25 -0700647 }
RoboErika5b02322014-05-07 17:05:49 -0700648 // Check that they can make calls on behalf of the user and
649 // get the final user id
650 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
651 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
652 // Check if they have the permissions or their component is
653 // enabled for the user they're calling from.
654 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
RoboErike7880d82014-04-30 12:48:25 -0700655 ArrayList<IBinder> binders = new ArrayList<IBinder>();
656 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700657 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700658 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700659 int size = records.size();
660 for (int i = 0; i < size; i++) {
661 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700662 }
663 }
664 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800665 } finally {
666 Binder.restoreCallingIdentity(token);
667 }
668 }
RoboErika278ea72014-04-24 14:49:01 -0700669
RoboErik8a2cfc32014-05-16 11:19:38 -0700670 /**
671 * Handles the dispatching of the media button events to one of the
672 * registered listeners, or if there was none, broadcast an
673 * ACTION_MEDIA_BUTTON intent to the rest of the system.
674 *
675 * @param keyEvent a non-null KeyEvent whose key code is one of the
676 * supported media buttons
677 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
678 * while this key event is dispatched.
679 */
680 @Override
681 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
682 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
683 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
684 return;
685 }
686 final int pid = Binder.getCallingPid();
687 final int uid = Binder.getCallingUid();
688 final long token = Binder.clearCallingIdentity();
689
690 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700691 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700692 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700693 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700694 if (isVoiceKey(keyEvent.getKeyCode())) {
695 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700696 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700697 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700698 }
699 }
700 } finally {
701 Binder.restoreCallingIdentity(token);
702 }
703 }
704
RoboErika278ea72014-04-24 14:49:01 -0700705 @Override
706 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
707 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
708 != PackageManager.PERMISSION_GRANTED) {
709 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
710 + Binder.getCallingPid()
711 + ", uid=" + Binder.getCallingUid());
712 return;
713 }
714
715 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
716 pw.println();
717
718 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700719 pw.println("Session for calls:" + mPrioritySession);
720 if (mPrioritySession != null) {
721 mPrioritySession.dump(pw, "");
722 }
RoboErik4646d282014-05-13 10:13:04 -0700723 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700724 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700725 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700726 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700727 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700728 }
RoboErika5b02322014-05-07 17:05:49 -0700729 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700730
RoboErik4646d282014-05-13 10:13:04 -0700731 pw.println("User Records:");
732 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700733 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700734 UserRecord user = mUserRecords.get(i);
735 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700736 }
737 }
738 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700739
RoboErik9a9d0b52014-05-20 14:53:39 -0700740 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
741 MediaSessionRecord session) {
742 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
743 // If the phone app has priority just give it the event
744 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
745 return;
746 }
747 int action = keyEvent.getAction();
748 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
749 if (action == KeyEvent.ACTION_DOWN) {
750 if (keyEvent.getRepeatCount() == 0) {
751 mVoiceButtonDown = true;
752 mVoiceButtonHandled = false;
753 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
754 mVoiceButtonHandled = true;
755 startVoiceInput(needWakeLock);
756 }
757 } else if (action == KeyEvent.ACTION_UP) {
758 if (mVoiceButtonDown) {
759 mVoiceButtonDown = false;
760 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
761 // Resend the down then send this event through
762 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
763 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
764 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
765 }
766 }
767 }
768 }
769
770 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
771 MediaSessionRecord session) {
772 if (session != null) {
773 if (DEBUG) {
774 Log.d(TAG, "Sending media key to " + session.getSessionInfo());
775 }
776 if (needWakeLock) {
777 mKeyEventReceiver.aquireWakeLockLocked();
778 }
779 // If we don't need a wakelock use -1 as the id so we
780 // won't release it later
781 session.sendMediaButton(keyEvent,
782 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
783 mKeyEventReceiver);
784 } else {
785 if (needWakeLock) {
786 mMediaEventWakeLock.acquire();
787 }
788 if (DEBUG) {
789 Log.d(TAG, "Sending media key ordered broadcast");
790 }
791 // Fallback to legacy behavior
792 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
793 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
794 if (needWakeLock) {
795 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
796 WAKELOCK_RELEASE_ON_FINISHED);
797 }
798 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
799 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
800 }
801 }
802
803 private void startVoiceInput(boolean needWakeLock) {
804 Intent voiceIntent = null;
805 // select which type of search to launch:
806 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
807 // - device locked or screen off: action is
808 // ACTION_VOICE_SEARCH_HANDS_FREE
809 // with EXTRA_SECURE set to true if the device is securely locked
810 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
811 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
812 if (!isLocked && pm.isScreenOn()) {
813 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
814 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
815 } else {
816 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
817 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
818 isLocked && mKeyguardManager.isKeyguardSecure());
819 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
820 }
821 // start the search activity
822 if (needWakeLock) {
823 mMediaEventWakeLock.acquire();
824 }
825 try {
826 if (voiceIntent != null) {
827 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
828 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
829 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
830 }
831 } catch (ActivityNotFoundException e) {
832 Log.w(TAG, "No activity for search: " + e);
833 } finally {
834 if (needWakeLock) {
835 mMediaEventWakeLock.release();
836 }
837 }
838 }
839
840 private boolean isVoiceKey(int keyCode) {
841 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
842 }
843
RoboErik418c10c2014-05-19 09:25:25 -0700844 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
845
846 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
847 private final Handler mHandler;
848 private int mRefCount = 0;
849 private int mLastTimeoutId = 0;
850
851 public KeyEventWakeLockReceiver(Handler handler) {
852 super(handler);
853 mHandler = handler;
854 }
855
856 public void onTimeout() {
857 synchronized (mLock) {
858 if (mRefCount == 0) {
859 // We've already released it, so just return
860 return;
861 }
862 mLastTimeoutId++;
863 mRefCount = 0;
864 releaseWakeLockLocked();
865 }
866 }
867
868 public void aquireWakeLockLocked() {
869 if (mRefCount == 0) {
870 mMediaEventWakeLock.acquire();
871 }
872 mRefCount++;
873 mHandler.removeCallbacks(this);
874 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
875
876 }
877
878 @Override
879 public void run() {
880 onTimeout();
881 }
882
RoboErik8a2cfc32014-05-16 11:19:38 -0700883 @Override
884 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -0700885 if (resultCode < mLastTimeoutId) {
886 // Ignore results from calls that were before the last
887 // timeout, just in case.
888 return;
889 } else {
890 synchronized (mLock) {
891 if (mRefCount > 0) {
892 mRefCount--;
893 if (mRefCount == 0) {
894 releaseWakeLockLocked();
895 }
896 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700897 }
898 }
899 }
RoboErik418c10c2014-05-19 09:25:25 -0700900
901 private void releaseWakeLockLocked() {
902 mMediaEventWakeLock.release();
903 mHandler.removeCallbacks(this);
904 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700905 };
906
907 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
908 @Override
909 public void onReceive(Context context, Intent intent) {
910 if (intent == null) {
911 return;
912 }
913 Bundle extras = intent.getExtras();
914 if (extras == null) {
915 return;
916 }
917 synchronized (mLock) {
918 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
919 && mMediaEventWakeLock.isHeld()) {
920 mMediaEventWakeLock.release();
921 }
922 }
923 }
924 };
RoboErik01fe6612014-02-13 14:19:04 -0800925 }
926
927}