blob: 87665e103fcc50191bff32e463cf2159e34dce05 [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;
RoboErikb69ffd42014-05-30 14:57:59 -070029import android.media.AudioManager;
30import android.media.IAudioService;
RoboErik07c70772014-03-20 13:33:52 -070031import android.media.routeprovider.RouteRequest;
32import android.media.session.ISession;
33import android.media.session.ISessionCallback;
34import android.media.session.ISessionManager;
35import android.media.session.RouteInfo;
36import android.media.session.RouteOptions;
RoboErik42ea7ee2014-05-16 16:27:35 -070037import android.media.session.MediaSession;
RoboErik01fe6612014-02-13 14:19:04 -080038import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070039import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080040import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070041import android.os.IBinder;
RoboErik8a2cfc32014-05-16 11:19:38 -070042import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080043import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070044import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070045import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070046import android.os.UserHandle;
47import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070048import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080049import android.text.TextUtils;
50import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070051import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070052import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080053
54import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070055import com.android.server.Watchdog;
56import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080057
RoboErika278ea72014-04-24 14:49:01 -070058import java.io.FileDescriptor;
59import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080060import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070061import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080062
63/**
64 * System implementation of MediaSessionManager
65 */
RoboErika278ea72014-04-24 14:49:01 -070066public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080067 private static final String TAG = "MediaSessionService";
68 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
69
RoboErik418c10c2014-05-19 09:25:25 -070070 private static final int WAKELOCK_TIMEOUT = 5000;
71
RoboErik01fe6612014-02-13 14:19:04 -080072 private final SessionManagerImpl mSessionManagerImpl;
RoboErik4646d282014-05-13 10:13:04 -070073 // private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErika8f95142014-05-05 14:23:49 -070074 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080075
RoboErik4646d282014-05-13 10:13:04 -070076 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
77 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
78 // private final ArrayList<MediaRouteProviderProxy> mProviders
79 // = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080080 private final Object mLock = new Object();
RoboErik8ae0f342014-02-24 18:02:08 -080081 private final Handler mHandler = new Handler();
RoboErik8a2cfc32014-05-16 11:19:38 -070082 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080083
RoboErik9a9d0b52014-05-20 14:53:39 -070084 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070085 private IAudioService mAudioService;
RoboErik9a9d0b52014-05-20 14:53:39 -070086
RoboErike7880d82014-04-30 12:48:25 -070087 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070088 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070089
RoboErik07c70772014-03-20 13:33:52 -070090 // Used to keep track of the current request to show routes for a specific
91 // session so we drop late callbacks properly.
92 private int mShowRoutesRequestId = 0;
93
RoboErika5b02322014-05-07 17:05:49 -070094 // TODO refactor to have per user state for providers. See
95 // MediaRouterService for an example
RoboErik07c70772014-03-20 13:33:52 -070096
RoboErik01fe6612014-02-13 14:19:04 -080097 public MediaSessionService(Context context) {
98 super(context);
99 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700100 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700101 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
102 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -0800103 }
104
105 @Override
106 public void onStart() {
107 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700108 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700109 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700110 mKeyguardManager =
111 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700112 mAudioService = getAudioService();
113 }
114
115 private IAudioService getAudioService() {
116 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
117 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700118 }
119
120 /**
121 * Should trigger showing the Media route picker dialog. Right now it just
122 * kicks off a query to all the providers to get routes.
123 *
124 * @param record The session to show the picker for.
125 */
126 public void showRoutePickerForSession(MediaSessionRecord record) {
127 // TODO for now just toggle the route to test (we will only have one
128 // match for now)
RoboErik4646d282014-05-13 10:13:04 -0700129 synchronized (mLock) {
130 if (!mAllSessions.contains(record)) {
131 Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
132 return;
133 }
134 RouteInfo current = record.getRoute();
135 UserRecord user = mUserRecords.get(record.getUserId());
136 if (current != null) {
137 // For now send null to mean the local route
138 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
139 if (proxy != null) {
140 proxy.removeSession(record);
141 }
142 record.selectRoute(null);
143 return;
144 }
145 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
146 mShowRoutesRequestId++;
147 for (int i = providers.size() - 1; i >= 0; i--) {
148 MediaRouteProviderProxy provider = providers.get(i);
149 provider.getRoutes(record, mShowRoutesRequestId);
150 }
RoboErik07c70772014-03-20 13:33:52 -0700151 }
152 }
153
154 /**
155 * Connect a session to the given route.
156 *
157 * @param session The session to connect.
158 * @param route The route to connect to.
159 * @param options The options to use for the connection.
160 */
161 public void connectToRoute(MediaSessionRecord session, RouteInfo route,
162 RouteOptions options) {
163 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700164 if (!mAllSessions.contains(session)) {
165 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
166 return;
167 }
168 UserRecord user = mUserRecords.get(session.getUserId());
169 if (user == null) {
170 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
171 return;
172 }
173 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
RoboErik07c70772014-03-20 13:33:52 -0700174 if (proxy == null) {
175 Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
176 return;
177 }
178 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
RoboErik07c70772014-03-20 13:33:52 -0700179 proxy.connectToRoute(session, route, request);
180 }
RoboErik01fe6612014-02-13 14:19:04 -0800181 }
182
RoboErika8f95142014-05-05 14:23:49 -0700183 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700184 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700185 if (!mAllSessions.contains(record)) {
186 Log.d(TAG, "Unknown session updated. Ignoring.");
187 return;
188 }
RoboErika8f95142014-05-05 14:23:49 -0700189 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700190 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700191 if (record.isActive()) {
192 if (mPrioritySession != null) {
193 Log.w(TAG, "Replacing existing priority session with a new session");
194 }
195 mPrioritySession = record;
196 } else {
197 if (mPrioritySession == record) {
198 mPrioritySession = null;
199 }
RoboErike7880d82014-04-30 12:48:25 -0700200 }
RoboErike7880d82014-04-30 12:48:25 -0700201 }
202 }
203 }
204
RoboErika8f95142014-05-05 14:23:49 -0700205 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
206 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700207 if (!mAllSessions.contains(record)) {
208 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
209 return;
210 }
RoboErika8f95142014-05-05 14:23:49 -0700211 mPriorityStack.onPlaystateChange(record, oldState, newState);
212 }
213 }
214
RoboErika278ea72014-04-24 14:49:01 -0700215 @Override
RoboErik4646d282014-05-13 10:13:04 -0700216 public void onStartUser(int userHandle) {
217 updateUser();
218 }
219
220 @Override
221 public void onSwitchUser(int userHandle) {
222 updateUser();
223 }
224
225 @Override
226 public void onStopUser(int userHandle) {
227 synchronized (mLock) {
228 UserRecord user = mUserRecords.get(userHandle);
229 if (user != null) {
230 destroyUserLocked(user);
231 }
232 }
233 }
234
235 @Override
RoboErika278ea72014-04-24 14:49:01 -0700236 public void monitor() {
237 synchronized (mLock) {
238 // Check for deadlock
239 }
240 }
241
RoboErik4646d282014-05-13 10:13:04 -0700242 protected void enforcePhoneStatePermission(int pid, int uid) {
243 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
244 != PackageManager.PERMISSION_GRANTED) {
245 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
246 }
247 }
248
RoboErik01fe6612014-02-13 14:19:04 -0800249 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700250 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800251 destroySessionLocked(session);
252 }
253 }
254
255 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700256 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800257 destroySessionLocked(session);
258 }
259 }
260
RoboErik4646d282014-05-13 10:13:04 -0700261 private void updateUser() {
262 synchronized (mLock) {
263 int userId = ActivityManager.getCurrentUser();
264 if (mCurrentUserId != userId) {
265 final int oldUserId = mCurrentUserId;
266 mCurrentUserId = userId; // do this first
267
268 UserRecord oldUser = mUserRecords.get(oldUserId);
269 if (oldUser != null) {
270 oldUser.stopLocked();
271 }
272
273 UserRecord newUser = getOrCreateUser(userId);
274 newUser.startLocked();
275 }
276 }
277 }
278
279 /**
280 * Stop the user and unbind from everything.
281 *
282 * @param user The user to dispose of
283 */
284 private void destroyUserLocked(UserRecord user) {
285 user.stopLocked();
286 user.destroyLocked();
287 mUserRecords.remove(user.mUserId);
288 }
289
290 /*
291 * When a session is removed several things need to happen.
292 * 1. We need to remove it from the relevant user.
293 * 2. We need to remove it from the priority stack.
294 * 3. We need to remove it from all sessions.
295 * 4. If this is the system priority session we need to clear it.
296 * 5. We need to unlink to death from the cb binder
297 * 6. We need to tell the session to do any final cleanup (onDestroy)
298 */
RoboErik01fe6612014-02-13 14:19:04 -0800299 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700300 int userId = session.getUserId();
301 UserRecord user = mUserRecords.get(userId);
302 if (user != null) {
303 user.removeSessionLocked(session);
304 }
305
RoboErika8f95142014-05-05 14:23:49 -0700306 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700307 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700308 if (session == mPrioritySession) {
309 mPrioritySession = null;
310 }
RoboErik4646d282014-05-13 10:13:04 -0700311
312 try {
313 session.getCallback().asBinder().unlinkToDeath(session, 0);
314 } catch (Exception e) {
315 // ignore exceptions while destroying a session.
316 }
317 session.onDestroy();
RoboErik01fe6612014-02-13 14:19:04 -0800318 }
319
320 private void enforcePackageName(String packageName, int uid) {
321 if (TextUtils.isEmpty(packageName)) {
322 throw new IllegalArgumentException("packageName may not be empty");
323 }
324 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
325 final int packageCount = packages.length;
326 for (int i = 0; i < packageCount; i++) {
327 if (packageName.equals(packages[i])) {
328 return;
329 }
330 }
331 throw new IllegalArgumentException("packageName is not owned by the calling process");
332 }
333
RoboErike7880d82014-04-30 12:48:25 -0700334 /**
335 * Checks a caller's authorization to register an IRemoteControlDisplay.
336 * Authorization is granted if one of the following is true:
337 * <ul>
338 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
339 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700340 * <li>the caller's listener is one of the enabled notification listeners
341 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700342 * </ul>
343 */
RoboErika5b02322014-05-07 17:05:49 -0700344 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
345 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700346 if (getContext()
347 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
348 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700349 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
350 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700351 throw new SecurityException("Missing permission to control media.");
352 }
353 }
354
RoboErika5b02322014-05-07 17:05:49 -0700355 /**
356 * This checks if the component is an enabled notification listener for the
357 * specified user. Enabled components may only operate on behalf of the user
358 * they're running as.
359 *
360 * @param compName The component that is enabled.
361 * @param userId The user id of the caller.
362 * @param forUserId The user id they're making the request on behalf of.
363 * @return True if the component is enabled, false otherwise
364 */
365 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
366 int forUserId) {
367 if (userId != forUserId) {
368 // You may not access another user's content as an enabled listener.
369 return false;
370 }
RoboErike7880d82014-04-30 12:48:25 -0700371 if (compName != null) {
RoboErike7880d82014-04-30 12:48:25 -0700372 final String enabledNotifListeners = Settings.Secure.getStringForUser(
373 getContext().getContentResolver(),
374 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700375 userId);
RoboErike7880d82014-04-30 12:48:25 -0700376 if (enabledNotifListeners != null) {
377 final String[] components = enabledNotifListeners.split(":");
378 for (int i = 0; i < components.length; i++) {
379 final ComponentName component =
380 ComponentName.unflattenFromString(components[i]);
381 if (component != null) {
382 if (compName.equals(component)) {
383 if (DEBUG) {
384 Log.d(TAG, "ok to get sessions: " + component +
385 " is authorized notification listener");
386 }
387 return true;
388 }
389 }
390 }
391 }
392 if (DEBUG) {
393 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700394 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700395 }
396 }
397 return false;
398 }
399
RoboErika5b02322014-05-07 17:05:49 -0700400 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700401 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800402 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700403 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800404 }
405 }
406
RoboErik4646d282014-05-13 10:13:04 -0700407 /*
408 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700409 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700410 * 2. It needs to be added to all sessions.
411 * 3. It needs to be added to the priority stack.
412 * 4. It needs to be added to the relevant user record.
413 */
RoboErika5b02322014-05-07 17:05:49 -0700414 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
415 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700416
RoboErika5b02322014-05-07 17:05:49 -0700417 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
418 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800419 try {
420 cb.asBinder().linkToDeath(session, 0);
421 } catch (RemoteException e) {
422 throw new RuntimeException("Media Session owner died prematurely.", e);
423 }
RoboErik4646d282014-05-13 10:13:04 -0700424
425 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700426 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700427
428 UserRecord user = getOrCreateUser(userId);
429 user.addSessionLocked(session);
430
RoboErik01fe6612014-02-13 14:19:04 -0800431 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700432 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800433 }
434 return session;
435 }
436
RoboErik4646d282014-05-13 10:13:04 -0700437 private UserRecord getOrCreateUser(int userId) {
438 UserRecord user = mUserRecords.get(userId);
439 if (user == null) {
440 user = new UserRecord(getContext(), userId);
441 mUserRecords.put(userId, user);
442 }
443 return user;
444 }
445
RoboErika8f95142014-05-05 14:23:49 -0700446 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700447 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
448 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700449 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
450 return i;
451 }
452 }
453 return -1;
454 }
455
RoboErike7880d82014-04-30 12:48:25 -0700456 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700457 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700458 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700459 }
460
RoboErik07c70772014-03-20 13:33:52 -0700461 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
462 = new MediaRouteProviderProxy.RoutesListener() {
463 @Override
464 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
465 int reqId) {
466 // TODO for now select the first route to test, eventually add the
467 // new routes to the dialog if it is still open
468 synchronized (mLock) {
469 int index = findIndexOfSessionForIdLocked(sessionId);
470 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700471 MediaSessionRecord record = mAllSessions.get(index);
472 RouteInfo route = routes.get(0);
473 record.selectRoute(route);
474 UserRecord user = mUserRecords.get(record.getUserId());
475 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
476 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700477 }
478 }
479 }
480
481 @Override
482 public void onRouteConnected(String sessionId, RouteInfo route,
483 RouteRequest options, RouteConnectionRecord connection) {
484 synchronized (mLock) {
485 int index = findIndexOfSessionForIdLocked(sessionId);
486 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700487 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700488 session.setRouteConnected(route, options.getConnectionOptions(), connection);
489 }
490 }
491 }
492 };
493
RoboErik4646d282014-05-13 10:13:04 -0700494 /**
495 * Information about a particular user. The contents of this object is
496 * guarded by mLock.
497 */
498 final class UserRecord {
499 private final int mUserId;
500 private final MediaRouteProviderWatcher mRouteProviderWatcher;
501 private final ArrayList<MediaRouteProviderProxy> mProviders
502 = new ArrayList<MediaRouteProviderProxy>();
503 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
504
505 public UserRecord(Context context, int userId) {
506 mUserId = userId;
507 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
508 mProviderWatcherCallback, mHandler, userId);
509 }
510
511 public void startLocked() {
512 mRouteProviderWatcher.start();
513 }
514
515 public void stopLocked() {
516 mRouteProviderWatcher.stop();
517 updateInterestLocked();
518 }
519
520 public void destroyLocked() {
521 for (int i = mSessions.size() - 1; i >= 0; i--) {
522 MediaSessionRecord session = mSessions.get(i);
523 MediaSessionService.this.destroySessionLocked(session);
524 if (session.isConnected()) {
RoboErik42ea7ee2014-05-16 16:27:35 -0700525 session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
RoboErik4646d282014-05-13 10:13:04 -0700526 }
527 }
528 }
529
530 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
531 return mProviders;
532 }
533
534 public ArrayList<MediaSessionRecord> getSessionsLocked() {
535 return mSessions;
536 }
537
538 public void addSessionLocked(MediaSessionRecord session) {
539 mSessions.add(session);
540 updateInterestLocked();
541 }
542
543 public void removeSessionLocked(MediaSessionRecord session) {
544 mSessions.remove(session);
545 RouteInfo route = session.getRoute();
546 if (route != null) {
547 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
548 if (provider != null) {
549 provider.removeSession(session);
550 }
551 }
552 updateInterestLocked();
553 }
554
555 public void dumpLocked(PrintWriter pw, String prefix) {
556 pw.println(prefix + "Record for user " + mUserId);
557 String indent = prefix + " ";
558 int size = mProviders.size();
559 pw.println(indent + size + " Providers:");
560 for (int i = 0; i < size; i++) {
561 mProviders.get(i).dump(pw, indent);
562 }
563 pw.println();
564 size = mSessions.size();
565 pw.println(indent + size + " Sessions:");
566 for (int i = 0; i < size; i++) {
567 // Just print the session info, the full session dump will
568 // already be in the list of all sessions.
569 pw.println(indent + mSessions.get(i).getSessionInfo());
570 }
571 }
572
573 public void updateInterestLocked() {
574 // TODO go through the sessions and build up the set of interfaces
575 // we're interested in. Update the provider watcher.
576 // For now, just express interest in all providers for the current
577 // user
578 boolean interested = mUserId == mCurrentUserId;
579 for (int i = mProviders.size() - 1; i >= 0; i--) {
580 mProviders.get(i).setInterested(interested);
581 }
582 }
583
584 private MediaRouteProviderProxy getProviderLocked(String providerId) {
585 for (int i = mProviders.size() - 1; i >= 0; i--) {
586 MediaRouteProviderProxy provider = mProviders.get(i);
587 if (TextUtils.equals(providerId, provider.getId())) {
588 return provider;
589 }
590 }
591 return null;
592 }
593
594 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
595 = new MediaRouteProviderWatcher.Callback() {
596 @Override
597 public void removeProvider(MediaRouteProviderProxy provider) {
598 synchronized (mLock) {
599 mProviders.remove(provider);
600 provider.setRoutesListener(null);
601 provider.setInterested(false);
602 }
603 }
604
605 @Override
606 public void addProvider(MediaRouteProviderProxy provider) {
607 synchronized (mLock) {
608 mProviders.add(provider);
609 provider.setRoutesListener(mRoutesCallback);
610 provider.setInterested(true);
611 }
612 }
613 };
614 }
615
RoboErik07c70772014-03-20 13:33:52 -0700616 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700617 private static final String EXTRA_WAKELOCK_ACQUIRED =
618 "android.media.AudioService.WAKELOCK_ACQUIRED";
619 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
620
RoboErik9a9d0b52014-05-20 14:53:39 -0700621 private boolean mVoiceButtonDown = false;
622 private boolean mVoiceButtonHandled = false;
623
RoboErik07c70772014-03-20 13:33:52 -0700624 @Override
RoboErika5b02322014-05-07 17:05:49 -0700625 public ISession createSession(String packageName, ISessionCallback cb, String tag,
626 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800627 final int pid = Binder.getCallingPid();
628 final int uid = Binder.getCallingUid();
629 final long token = Binder.clearCallingIdentity();
630 try {
631 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700632 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
633 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800634 if (cb == null) {
635 throw new IllegalArgumentException("Controller callback cannot be null");
636 }
RoboErika5b02322014-05-07 17:05:49 -0700637 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
638 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700639 } finally {
640 Binder.restoreCallingIdentity(token);
641 }
642 }
643
644 @Override
RoboErika5b02322014-05-07 17:05:49 -0700645 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700646 final int pid = Binder.getCallingPid();
647 final int uid = Binder.getCallingUid();
648 final long token = Binder.clearCallingIdentity();
649
650 try {
RoboErika5b02322014-05-07 17:05:49 -0700651 String packageName = null;
RoboErike7880d82014-04-30 12:48:25 -0700652 if (componentName != null) {
653 // If they gave us a component name verify they own the
654 // package
RoboErika5b02322014-05-07 17:05:49 -0700655 packageName = componentName.getPackageName();
656 enforcePackageName(packageName, uid);
RoboErike7880d82014-04-30 12:48:25 -0700657 }
RoboErika5b02322014-05-07 17:05:49 -0700658 // Check that they can make calls on behalf of the user and
659 // get the final user id
660 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
661 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
662 // Check if they have the permissions or their component is
663 // enabled for the user they're calling from.
664 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
RoboErike7880d82014-04-30 12:48:25 -0700665 ArrayList<IBinder> binders = new ArrayList<IBinder>();
666 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700667 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700668 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700669 int size = records.size();
670 for (int i = 0; i < size; i++) {
671 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700672 }
673 }
674 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800675 } finally {
676 Binder.restoreCallingIdentity(token);
677 }
678 }
RoboErika278ea72014-04-24 14:49:01 -0700679
RoboErik8a2cfc32014-05-16 11:19:38 -0700680 /**
681 * Handles the dispatching of the media button events to one of the
682 * registered listeners, or if there was none, broadcast an
683 * ACTION_MEDIA_BUTTON intent to the rest of the system.
684 *
685 * @param keyEvent a non-null KeyEvent whose key code is one of the
686 * supported media buttons
687 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
688 * while this key event is dispatched.
689 */
690 @Override
691 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
692 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
693 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
694 return;
695 }
696 final int pid = Binder.getCallingPid();
697 final int uid = Binder.getCallingUid();
698 final long token = Binder.clearCallingIdentity();
699
700 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700701 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700702 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700703 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700704 if (isVoiceKey(keyEvent.getKeyCode())) {
705 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700706 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700707 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700708 }
709 }
710 } finally {
711 Binder.restoreCallingIdentity(token);
712 }
713 }
714
RoboErika278ea72014-04-24 14:49:01 -0700715 @Override
RoboErikb69ffd42014-05-30 14:57:59 -0700716 public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
717 throws RemoteException {
718 final int pid = Binder.getCallingPid();
719 final int uid = Binder.getCallingUid();
720 final long token = Binder.clearCallingIdentity();
721 try {
722 synchronized (mLock) {
723 MediaSessionRecord session = mPriorityStack
724 .getDefaultVolumeSession(mCurrentUserId);
725 dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
726 }
727 } finally {
728 Binder.restoreCallingIdentity(token);
729 }
730 }
731
732 @Override
RoboErika278ea72014-04-24 14:49:01 -0700733 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
734 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
735 != PackageManager.PERMISSION_GRANTED) {
736 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
737 + Binder.getCallingPid()
738 + ", uid=" + Binder.getCallingUid());
739 return;
740 }
741
742 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
743 pw.println();
744
745 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700746 pw.println("Session for calls:" + mPrioritySession);
747 if (mPrioritySession != null) {
748 mPrioritySession.dump(pw, "");
749 }
RoboErik4646d282014-05-13 10:13:04 -0700750 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700751 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700752 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700753 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700754 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700755 }
RoboErika5b02322014-05-07 17:05:49 -0700756 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700757
RoboErik4646d282014-05-13 10:13:04 -0700758 pw.println("User Records:");
759 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700760 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700761 UserRecord user = mUserRecords.get(i);
762 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700763 }
764 }
765 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700766
RoboErikb69ffd42014-05-30 14:57:59 -0700767 private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
768 MediaSessionRecord session) {
769 int direction = 0;
770 int steps = delta;
771 if (delta > 0) {
772 direction = 1;
773 } else if (delta < 0) {
774 direction = -1;
775 steps = -delta;
776 }
777 if (DEBUG) {
778 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
779 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
780 + ", suggestedStream=" + suggestedStream);
781
782 }
783 if (session == null) {
784 for (int i = 0; i < steps; i++) {
785 try {
786 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
787 flags, getContext().getOpPackageName());
788 } catch (RemoteException e) {
789 Log.e(TAG, "Error adjusting default volume.", e);
790 }
791 }
792 } else {
793 if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
794 for (int i = 0; i < steps; i++) {
795 try {
796 mAudioService.adjustSuggestedStreamVolume(direction,
797 session.getAudioStream(), flags,
798 getContext().getOpPackageName());
799 } catch (RemoteException e) {
800 Log.e(TAG, "Error adjusting volume for stream "
801 + session.getAudioStream(), e);
802 }
803 }
804 } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) {
805 session.adjustVolumeBy(delta);
806 }
807 }
808 }
809
RoboErik9a9d0b52014-05-20 14:53:39 -0700810 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
811 MediaSessionRecord session) {
812 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
813 // If the phone app has priority just give it the event
814 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
815 return;
816 }
817 int action = keyEvent.getAction();
818 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
819 if (action == KeyEvent.ACTION_DOWN) {
820 if (keyEvent.getRepeatCount() == 0) {
821 mVoiceButtonDown = true;
822 mVoiceButtonHandled = false;
823 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
824 mVoiceButtonHandled = true;
825 startVoiceInput(needWakeLock);
826 }
827 } else if (action == KeyEvent.ACTION_UP) {
828 if (mVoiceButtonDown) {
829 mVoiceButtonDown = false;
830 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
831 // Resend the down then send this event through
832 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
833 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
834 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
835 }
836 }
837 }
838 }
839
840 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
841 MediaSessionRecord session) {
842 if (session != null) {
843 if (DEBUG) {
844 Log.d(TAG, "Sending media key to " + session.getSessionInfo());
845 }
846 if (needWakeLock) {
847 mKeyEventReceiver.aquireWakeLockLocked();
848 }
849 // If we don't need a wakelock use -1 as the id so we
850 // won't release it later
851 session.sendMediaButton(keyEvent,
852 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
853 mKeyEventReceiver);
854 } else {
855 if (needWakeLock) {
856 mMediaEventWakeLock.acquire();
857 }
858 if (DEBUG) {
859 Log.d(TAG, "Sending media key ordered broadcast");
860 }
861 // Fallback to legacy behavior
862 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
863 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
864 if (needWakeLock) {
865 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
866 WAKELOCK_RELEASE_ON_FINISHED);
867 }
868 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
869 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
870 }
871 }
872
873 private void startVoiceInput(boolean needWakeLock) {
874 Intent voiceIntent = null;
875 // select which type of search to launch:
876 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
877 // - device locked or screen off: action is
878 // ACTION_VOICE_SEARCH_HANDS_FREE
879 // with EXTRA_SECURE set to true if the device is securely locked
880 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
881 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
882 if (!isLocked && pm.isScreenOn()) {
883 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
884 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
885 } else {
886 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
887 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
888 isLocked && mKeyguardManager.isKeyguardSecure());
889 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
890 }
891 // start the search activity
892 if (needWakeLock) {
893 mMediaEventWakeLock.acquire();
894 }
895 try {
896 if (voiceIntent != null) {
897 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
898 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
899 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
900 }
901 } catch (ActivityNotFoundException e) {
902 Log.w(TAG, "No activity for search: " + e);
903 } finally {
904 if (needWakeLock) {
905 mMediaEventWakeLock.release();
906 }
907 }
908 }
909
910 private boolean isVoiceKey(int keyCode) {
911 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
912 }
913
RoboErik418c10c2014-05-19 09:25:25 -0700914 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
915
916 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
917 private final Handler mHandler;
918 private int mRefCount = 0;
919 private int mLastTimeoutId = 0;
920
921 public KeyEventWakeLockReceiver(Handler handler) {
922 super(handler);
923 mHandler = handler;
924 }
925
926 public void onTimeout() {
927 synchronized (mLock) {
928 if (mRefCount == 0) {
929 // We've already released it, so just return
930 return;
931 }
932 mLastTimeoutId++;
933 mRefCount = 0;
934 releaseWakeLockLocked();
935 }
936 }
937
938 public void aquireWakeLockLocked() {
939 if (mRefCount == 0) {
940 mMediaEventWakeLock.acquire();
941 }
942 mRefCount++;
943 mHandler.removeCallbacks(this);
944 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
945
946 }
947
948 @Override
949 public void run() {
950 onTimeout();
951 }
952
RoboErik8a2cfc32014-05-16 11:19:38 -0700953 @Override
954 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -0700955 if (resultCode < mLastTimeoutId) {
956 // Ignore results from calls that were before the last
957 // timeout, just in case.
958 return;
959 } else {
960 synchronized (mLock) {
961 if (mRefCount > 0) {
962 mRefCount--;
963 if (mRefCount == 0) {
964 releaseWakeLockLocked();
965 }
966 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700967 }
968 }
969 }
RoboErik418c10c2014-05-19 09:25:25 -0700970
971 private void releaseWakeLockLocked() {
972 mMediaEventWakeLock.release();
973 mHandler.removeCallbacks(this);
974 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700975 };
976
977 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
978 @Override
979 public void onReceive(Context context, Intent intent) {
980 if (intent == null) {
981 return;
982 }
983 Bundle extras = intent.getExtras();
984 if (extras == null) {
985 return;
986 }
987 synchronized (mLock) {
988 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
989 && mMediaEventWakeLock.isHeld()) {
990 mMediaEventWakeLock.release();
991 }
992 }
993 }
994 };
RoboErik01fe6612014-02-13 14:19:04 -0800995 }
996
997}