blob: 873ed71472e286b2dafd285b120d8e81f88edcdd [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;
RoboErik6f0e4dd2014-06-17 16:56:27 -070026import android.content.ContentResolver;
RoboErik01fe6612014-02-13 14:19:04 -080027import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070028import android.content.Intent;
RoboErika278ea72014-04-24 14:49:01 -070029import android.content.pm.PackageManager;
RoboErikb69ffd42014-05-30 14:57:59 -070030import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070031import android.media.IRemoteVolumeController;
RoboErik07c70772014-03-20 13:33:52 -070032import android.media.routeprovider.RouteRequest;
RoboErik2e7a9162014-06-04 16:53:45 -070033import android.media.session.IActiveSessionsListener;
RoboErik07c70772014-03-20 13:33:52 -070034import android.media.session.ISession;
35import android.media.session.ISessionCallback;
36import android.media.session.ISessionManager;
RoboErik2e7a9162014-06-04 16:53:45 -070037import android.media.session.MediaSessionToken;
RoboErik07c70772014-03-20 13:33:52 -070038import android.media.session.RouteInfo;
39import android.media.session.RouteOptions;
RoboErik42ea7ee2014-05-16 16:27:35 -070040import android.media.session.MediaSession;
RoboErik01fe6612014-02-13 14:19:04 -080041import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070042import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080043import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070044import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070045import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070046import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080047import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070048import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070049import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070050import android.os.UserHandle;
51import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070052import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080053import android.text.TextUtils;
54import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070055import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070056import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080057
58import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070059import com.android.server.Watchdog;
60import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080061
RoboErika278ea72014-04-24 14:49:01 -070062import java.io.FileDescriptor;
63import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080064import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070065import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080066
67/**
68 * System implementation of MediaSessionManager
69 */
RoboErika278ea72014-04-24 14:49:01 -070070public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080071 private static final String TAG = "MediaSessionService";
72 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
73
RoboErik418c10c2014-05-19 09:25:25 -070074 private static final int WAKELOCK_TIMEOUT = 5000;
75
RoboErik01fe6612014-02-13 14:19:04 -080076 private final SessionManagerImpl mSessionManagerImpl;
RoboErik4646d282014-05-13 10:13:04 -070077 // private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErika8f95142014-05-05 14:23:49 -070078 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080079
RoboErik4646d282014-05-13 10:13:04 -070080 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
81 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070082 private final ArrayList<SessionsListenerRecord> mSessionsListeners
83 = new ArrayList<SessionsListenerRecord>();
RoboErik4646d282014-05-13 10:13:04 -070084 // private final ArrayList<MediaRouteProviderProxy> mProviders
85 // = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080086 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070087 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070088 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080089
RoboErik9a9d0b52014-05-20 14:53:39 -070090 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070091 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070092 private ContentResolver mContentResolver;
RoboErik9a9d0b52014-05-20 14:53:39 -070093
RoboErike7880d82014-04-30 12:48:25 -070094 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070095 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070096
RoboErik07c70772014-03-20 13:33:52 -070097 // Used to keep track of the current request to show routes for a specific
98 // session so we drop late callbacks properly.
99 private int mShowRoutesRequestId = 0;
100
RoboErik19c95182014-06-23 15:38:48 -0700101 // Used to notify system UI when remote volume was changed. TODO find a
102 // better way to handle this.
103 private IRemoteVolumeController mRvc;
104
RoboErika5b02322014-05-07 17:05:49 -0700105 // TODO refactor to have per user state for providers. See
106 // MediaRouterService for an example
RoboErik07c70772014-03-20 13:33:52 -0700107
RoboErik01fe6612014-02-13 14:19:04 -0800108 public MediaSessionService(Context context) {
109 super(context);
110 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700111 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700112 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
113 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -0800114 }
115
116 @Override
117 public void onStart() {
118 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700119 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700120 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700121 mKeyguardManager =
122 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700123 mAudioService = getAudioService();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700124 mContentResolver = getContext().getContentResolver();
RoboErikb69ffd42014-05-30 14:57:59 -0700125 }
126
127 private IAudioService getAudioService() {
128 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
129 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700130 }
131
132 /**
133 * Should trigger showing the Media route picker dialog. Right now it just
134 * kicks off a query to all the providers to get routes.
135 *
136 * @param record The session to show the picker for.
137 */
138 public void showRoutePickerForSession(MediaSessionRecord record) {
139 // TODO for now just toggle the route to test (we will only have one
140 // match for now)
RoboErik4646d282014-05-13 10:13:04 -0700141 synchronized (mLock) {
142 if (!mAllSessions.contains(record)) {
143 Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
144 return;
145 }
146 RouteInfo current = record.getRoute();
147 UserRecord user = mUserRecords.get(record.getUserId());
148 if (current != null) {
149 // For now send null to mean the local route
150 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
151 if (proxy != null) {
152 proxy.removeSession(record);
153 }
154 record.selectRoute(null);
155 return;
156 }
157 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
158 mShowRoutesRequestId++;
159 for (int i = providers.size() - 1; i >= 0; i--) {
160 MediaRouteProviderProxy provider = providers.get(i);
161 provider.getRoutes(record, mShowRoutesRequestId);
162 }
RoboErik07c70772014-03-20 13:33:52 -0700163 }
164 }
165
166 /**
167 * Connect a session to the given route.
168 *
169 * @param session The session to connect.
170 * @param route The route to connect to.
171 * @param options The options to use for the connection.
172 */
173 public void connectToRoute(MediaSessionRecord session, RouteInfo route,
174 RouteOptions options) {
175 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700176 if (!mAllSessions.contains(session)) {
177 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
178 return;
179 }
180 UserRecord user = mUserRecords.get(session.getUserId());
181 if (user == null) {
182 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
183 return;
184 }
185 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
RoboErik07c70772014-03-20 13:33:52 -0700186 if (proxy == null) {
187 Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
188 return;
189 }
190 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
RoboErik07c70772014-03-20 13:33:52 -0700191 proxy.connectToRoute(session, route, request);
192 }
RoboErik01fe6612014-02-13 14:19:04 -0800193 }
194
RoboErika8f95142014-05-05 14:23:49 -0700195 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700196 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700197 if (!mAllSessions.contains(record)) {
198 Log.d(TAG, "Unknown session updated. Ignoring.");
199 return;
200 }
RoboErika8f95142014-05-05 14:23:49 -0700201 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700202 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700203 if (record.isActive()) {
204 if (mPrioritySession != null) {
205 Log.w(TAG, "Replacing existing priority session with a new session");
206 }
207 mPrioritySession = record;
208 } else {
209 if (mPrioritySession == record) {
210 mPrioritySession = null;
211 }
RoboErike7880d82014-04-30 12:48:25 -0700212 }
RoboErike7880d82014-04-30 12:48:25 -0700213 }
214 }
RoboErik2e7a9162014-06-04 16:53:45 -0700215 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700216 }
217
RoboErika8f95142014-05-05 14:23:49 -0700218 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700219 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700220 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700221 if (!mAllSessions.contains(record)) {
222 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
223 return;
224 }
RoboErik2e7a9162014-06-04 16:53:45 -0700225 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
226 }
227 if (updateSessions) {
228 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700229 }
230 }
231
RoboErik19c95182014-06-23 15:38:48 -0700232 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
233 synchronized (mLock) {
234 if (!mAllSessions.contains(record)) {
235 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
236 return;
237 }
238 pushRemoteVolumeUpdateLocked(record.getUserId());
239 }
240 }
241
RoboErika278ea72014-04-24 14:49:01 -0700242 @Override
RoboErik4646d282014-05-13 10:13:04 -0700243 public void onStartUser(int userHandle) {
244 updateUser();
245 }
246
247 @Override
248 public void onSwitchUser(int userHandle) {
249 updateUser();
250 }
251
252 @Override
253 public void onStopUser(int userHandle) {
254 synchronized (mLock) {
255 UserRecord user = mUserRecords.get(userHandle);
256 if (user != null) {
257 destroyUserLocked(user);
258 }
259 }
260 }
261
262 @Override
RoboErika278ea72014-04-24 14:49:01 -0700263 public void monitor() {
264 synchronized (mLock) {
265 // Check for deadlock
266 }
267 }
268
RoboErik4646d282014-05-13 10:13:04 -0700269 protected void enforcePhoneStatePermission(int pid, int uid) {
270 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
271 != PackageManager.PERMISSION_GRANTED) {
272 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
273 }
274 }
275
RoboErik01fe6612014-02-13 14:19:04 -0800276 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700277 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800278 destroySessionLocked(session);
279 }
280 }
281
282 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700283 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800284 destroySessionLocked(session);
285 }
286 }
287
RoboErik4646d282014-05-13 10:13:04 -0700288 private void updateUser() {
289 synchronized (mLock) {
290 int userId = ActivityManager.getCurrentUser();
291 if (mCurrentUserId != userId) {
292 final int oldUserId = mCurrentUserId;
293 mCurrentUserId = userId; // do this first
294
295 UserRecord oldUser = mUserRecords.get(oldUserId);
296 if (oldUser != null) {
297 oldUser.stopLocked();
298 }
299
300 UserRecord newUser = getOrCreateUser(userId);
301 newUser.startLocked();
302 }
303 }
304 }
305
306 /**
307 * Stop the user and unbind from everything.
308 *
309 * @param user The user to dispose of
310 */
311 private void destroyUserLocked(UserRecord user) {
312 user.stopLocked();
313 user.destroyLocked();
314 mUserRecords.remove(user.mUserId);
315 }
316
317 /*
318 * When a session is removed several things need to happen.
319 * 1. We need to remove it from the relevant user.
320 * 2. We need to remove it from the priority stack.
321 * 3. We need to remove it from all sessions.
322 * 4. If this is the system priority session we need to clear it.
323 * 5. We need to unlink to death from the cb binder
324 * 6. We need to tell the session to do any final cleanup (onDestroy)
325 */
RoboErik01fe6612014-02-13 14:19:04 -0800326 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700327 int userId = session.getUserId();
328 UserRecord user = mUserRecords.get(userId);
329 if (user != null) {
330 user.removeSessionLocked(session);
331 }
332
RoboErika8f95142014-05-05 14:23:49 -0700333 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700334 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700335 if (session == mPrioritySession) {
336 mPrioritySession = null;
337 }
RoboErik4646d282014-05-13 10:13:04 -0700338
339 try {
340 session.getCallback().asBinder().unlinkToDeath(session, 0);
341 } catch (Exception e) {
342 // ignore exceptions while destroying a session.
343 }
344 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700345
346 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800347 }
348
349 private void enforcePackageName(String packageName, int uid) {
350 if (TextUtils.isEmpty(packageName)) {
351 throw new IllegalArgumentException("packageName may not be empty");
352 }
353 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
354 final int packageCount = packages.length;
355 for (int i = 0; i < packageCount; i++) {
356 if (packageName.equals(packages[i])) {
357 return;
358 }
359 }
360 throw new IllegalArgumentException("packageName is not owned by the calling process");
361 }
362
RoboErike7880d82014-04-30 12:48:25 -0700363 /**
364 * Checks a caller's authorization to register an IRemoteControlDisplay.
365 * Authorization is granted if one of the following is true:
366 * <ul>
367 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
368 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700369 * <li>the caller's listener is one of the enabled notification listeners
370 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700371 * </ul>
372 */
RoboErika5b02322014-05-07 17:05:49 -0700373 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
374 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700375 if (getContext()
376 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
377 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700378 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
379 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700380 throw new SecurityException("Missing permission to control media.");
381 }
382 }
383
RoboErik19c95182014-06-23 15:38:48 -0700384 private void enforceStatusBarPermission(String action, int pid, int uid) {
385 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
386 pid, uid) != PackageManager.PERMISSION_GRANTED) {
387 throw new SecurityException("Only system ui may " + action);
388 }
389 }
390
RoboErika5b02322014-05-07 17:05:49 -0700391 /**
392 * This checks if the component is an enabled notification listener for the
393 * specified user. Enabled components may only operate on behalf of the user
394 * they're running as.
395 *
396 * @param compName The component that is enabled.
397 * @param userId The user id of the caller.
398 * @param forUserId The user id they're making the request on behalf of.
399 * @return True if the component is enabled, false otherwise
400 */
401 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
402 int forUserId) {
403 if (userId != forUserId) {
404 // You may not access another user's content as an enabled listener.
405 return false;
406 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700407 if (DEBUG) {
408 Log.d(TAG, "Checking if enabled notification listener " + compName);
409 }
RoboErike7880d82014-04-30 12:48:25 -0700410 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700411 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700412 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700413 userId);
RoboErike7880d82014-04-30 12:48:25 -0700414 if (enabledNotifListeners != null) {
415 final String[] components = enabledNotifListeners.split(":");
416 for (int i = 0; i < components.length; i++) {
417 final ComponentName component =
418 ComponentName.unflattenFromString(components[i]);
419 if (component != null) {
420 if (compName.equals(component)) {
421 if (DEBUG) {
422 Log.d(TAG, "ok to get sessions: " + component +
423 " is authorized notification listener");
424 }
425 return true;
426 }
427 }
428 }
429 }
430 if (DEBUG) {
431 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700432 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700433 }
434 }
435 return false;
436 }
437
RoboErika5b02322014-05-07 17:05:49 -0700438 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700439 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800440 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700441 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800442 }
443 }
444
RoboErik4646d282014-05-13 10:13:04 -0700445 /*
446 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700447 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700448 * 2. It needs to be added to all sessions.
449 * 3. It needs to be added to the priority stack.
450 * 4. It needs to be added to the relevant user record.
451 */
RoboErika5b02322014-05-07 17:05:49 -0700452 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
453 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700454
RoboErika5b02322014-05-07 17:05:49 -0700455 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
456 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800457 try {
458 cb.asBinder().linkToDeath(session, 0);
459 } catch (RemoteException e) {
460 throw new RuntimeException("Media Session owner died prematurely.", e);
461 }
RoboErik4646d282014-05-13 10:13:04 -0700462
463 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700464 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700465
466 UserRecord user = getOrCreateUser(userId);
467 user.addSessionLocked(session);
468
RoboErik2e7a9162014-06-04 16:53:45 -0700469 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
470
RoboErik01fe6612014-02-13 14:19:04 -0800471 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700472 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800473 }
474 return session;
475 }
476
RoboErik4646d282014-05-13 10:13:04 -0700477 private UserRecord getOrCreateUser(int userId) {
478 UserRecord user = mUserRecords.get(userId);
479 if (user == null) {
480 user = new UserRecord(getContext(), userId);
481 mUserRecords.put(userId, user);
482 }
483 return user;
484 }
485
RoboErika8f95142014-05-05 14:23:49 -0700486 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700487 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
488 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700489 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
490 return i;
491 }
492 }
493 return -1;
494 }
495
RoboErik2e7a9162014-06-04 16:53:45 -0700496 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
497 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
498 if (mSessionsListeners.get(i).mListener == listener) {
499 return i;
500 }
501 }
502 return -1;
503 }
504
RoboErike7880d82014-04-30 12:48:25 -0700505 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700506 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700507 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700508 }
509
RoboErik2e7a9162014-06-04 16:53:45 -0700510 private void pushSessionsChanged(int userId) {
511 synchronized (mLock) {
512 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
513 int size = records.size();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700514 if (size > 0) {
515 persistMediaButtonReceiverLocked(records.get(0));
516 }
RoboErik2e7a9162014-06-04 16:53:45 -0700517 ArrayList<MediaSessionToken> tokens = new ArrayList<MediaSessionToken>();
518 for (int i = 0; i < size; i++) {
519 tokens.add(new MediaSessionToken(records.get(i).getControllerBinder()));
520 }
RoboErik19c95182014-06-23 15:38:48 -0700521 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700522 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
523 SessionsListenerRecord record = mSessionsListeners.get(i);
524 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
525 try {
526 record.mListener.onActiveSessionsChanged(tokens);
527 } catch (RemoteException e) {
528 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
529 e);
530 mSessionsListeners.remove(i);
531 }
532 }
533 }
534 }
535 }
536
RoboErik19c95182014-06-23 15:38:48 -0700537 private void pushRemoteVolumeUpdateLocked(int userId) {
538 if (mRvc != null) {
539 try {
540 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
541 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
542 } catch (RemoteException e) {
543 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
544 }
545 }
546 }
547
RoboErik6f0e4dd2014-06-17 16:56:27 -0700548 private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
549 ComponentName receiver = record.getMediaButtonReceiver();
550 if (receiver != null) {
551 Settings.System.putStringForUser(mContentResolver,
552 Settings.System.MEDIA_BUTTON_RECEIVER,
553 receiver == null ? "" : receiver.flattenToString(),
554 UserHandle.USER_CURRENT);
555 }
556 }
557
RoboErik07c70772014-03-20 13:33:52 -0700558 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
559 = new MediaRouteProviderProxy.RoutesListener() {
560 @Override
561 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
562 int reqId) {
563 // TODO for now select the first route to test, eventually add the
564 // new routes to the dialog if it is still open
565 synchronized (mLock) {
566 int index = findIndexOfSessionForIdLocked(sessionId);
567 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700568 MediaSessionRecord record = mAllSessions.get(index);
569 RouteInfo route = routes.get(0);
570 record.selectRoute(route);
571 UserRecord user = mUserRecords.get(record.getUserId());
572 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
573 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700574 }
575 }
576 }
577
578 @Override
579 public void onRouteConnected(String sessionId, RouteInfo route,
580 RouteRequest options, RouteConnectionRecord connection) {
581 synchronized (mLock) {
582 int index = findIndexOfSessionForIdLocked(sessionId);
583 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700584 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700585 session.setRouteConnected(route, options.getConnectionOptions(), connection);
586 }
587 }
588 }
589 };
590
RoboErik4646d282014-05-13 10:13:04 -0700591 /**
592 * Information about a particular user. The contents of this object is
593 * guarded by mLock.
594 */
595 final class UserRecord {
596 private final int mUserId;
597 private final MediaRouteProviderWatcher mRouteProviderWatcher;
598 private final ArrayList<MediaRouteProviderProxy> mProviders
599 = new ArrayList<MediaRouteProviderProxy>();
600 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
601
602 public UserRecord(Context context, int userId) {
603 mUserId = userId;
604 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
605 mProviderWatcherCallback, mHandler, userId);
606 }
607
608 public void startLocked() {
609 mRouteProviderWatcher.start();
610 }
611
612 public void stopLocked() {
613 mRouteProviderWatcher.stop();
614 updateInterestLocked();
615 }
616
617 public void destroyLocked() {
618 for (int i = mSessions.size() - 1; i >= 0; i--) {
619 MediaSessionRecord session = mSessions.get(i);
620 MediaSessionService.this.destroySessionLocked(session);
621 if (session.isConnected()) {
RoboErik42ea7ee2014-05-16 16:27:35 -0700622 session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
RoboErik4646d282014-05-13 10:13:04 -0700623 }
624 }
625 }
626
627 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
628 return mProviders;
629 }
630
631 public ArrayList<MediaSessionRecord> getSessionsLocked() {
632 return mSessions;
633 }
634
635 public void addSessionLocked(MediaSessionRecord session) {
636 mSessions.add(session);
637 updateInterestLocked();
638 }
639
640 public void removeSessionLocked(MediaSessionRecord session) {
641 mSessions.remove(session);
642 RouteInfo route = session.getRoute();
643 if (route != null) {
644 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
645 if (provider != null) {
646 provider.removeSession(session);
647 }
648 }
649 updateInterestLocked();
650 }
651
652 public void dumpLocked(PrintWriter pw, String prefix) {
653 pw.println(prefix + "Record for user " + mUserId);
654 String indent = prefix + " ";
655 int size = mProviders.size();
656 pw.println(indent + size + " Providers:");
657 for (int i = 0; i < size; i++) {
658 mProviders.get(i).dump(pw, indent);
659 }
660 pw.println();
661 size = mSessions.size();
662 pw.println(indent + size + " Sessions:");
663 for (int i = 0; i < size; i++) {
664 // Just print the session info, the full session dump will
665 // already be in the list of all sessions.
666 pw.println(indent + mSessions.get(i).getSessionInfo());
667 }
668 }
669
670 public void updateInterestLocked() {
671 // TODO go through the sessions and build up the set of interfaces
672 // we're interested in. Update the provider watcher.
673 // For now, just express interest in all providers for the current
674 // user
675 boolean interested = mUserId == mCurrentUserId;
676 for (int i = mProviders.size() - 1; i >= 0; i--) {
677 mProviders.get(i).setInterested(interested);
678 }
679 }
680
681 private MediaRouteProviderProxy getProviderLocked(String providerId) {
682 for (int i = mProviders.size() - 1; i >= 0; i--) {
683 MediaRouteProviderProxy provider = mProviders.get(i);
684 if (TextUtils.equals(providerId, provider.getId())) {
685 return provider;
686 }
687 }
688 return null;
689 }
690
691 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
692 = new MediaRouteProviderWatcher.Callback() {
693 @Override
694 public void removeProvider(MediaRouteProviderProxy provider) {
695 synchronized (mLock) {
696 mProviders.remove(provider);
697 provider.setRoutesListener(null);
698 provider.setInterested(false);
699 }
700 }
701
702 @Override
703 public void addProvider(MediaRouteProviderProxy provider) {
704 synchronized (mLock) {
705 mProviders.add(provider);
706 provider.setRoutesListener(mRoutesCallback);
707 provider.setInterested(true);
708 }
709 }
710 };
711 }
712
RoboErik2e7a9162014-06-04 16:53:45 -0700713 final class SessionsListenerRecord implements IBinder.DeathRecipient {
714 private final IActiveSessionsListener mListener;
715 private final int mUserId;
716
717 public SessionsListenerRecord(IActiveSessionsListener listener, int userId) {
718 mListener = listener;
719 mUserId = userId;
720 }
721
722 @Override
723 public void binderDied() {
724 synchronized (mLock) {
725 mSessionsListeners.remove(this);
726 }
727 }
728 }
729
RoboErik07c70772014-03-20 13:33:52 -0700730 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700731 private static final String EXTRA_WAKELOCK_ACQUIRED =
732 "android.media.AudioService.WAKELOCK_ACQUIRED";
733 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
734
RoboErik9a9d0b52014-05-20 14:53:39 -0700735 private boolean mVoiceButtonDown = false;
736 private boolean mVoiceButtonHandled = false;
737
RoboErik07c70772014-03-20 13:33:52 -0700738 @Override
RoboErika5b02322014-05-07 17:05:49 -0700739 public ISession createSession(String packageName, ISessionCallback cb, String tag,
740 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800741 final int pid = Binder.getCallingPid();
742 final int uid = Binder.getCallingUid();
743 final long token = Binder.clearCallingIdentity();
744 try {
745 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700746 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
747 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800748 if (cb == null) {
749 throw new IllegalArgumentException("Controller callback cannot be null");
750 }
RoboErika5b02322014-05-07 17:05:49 -0700751 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
752 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700753 } finally {
754 Binder.restoreCallingIdentity(token);
755 }
756 }
757
758 @Override
RoboErika5b02322014-05-07 17:05:49 -0700759 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700760 final int pid = Binder.getCallingPid();
761 final int uid = Binder.getCallingUid();
762 final long token = Binder.clearCallingIdentity();
763
764 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700765 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700766 ArrayList<IBinder> binders = new ArrayList<IBinder>();
767 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700768 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700769 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700770 int size = records.size();
771 for (int i = 0; i < size; i++) {
772 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700773 }
774 }
775 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800776 } finally {
777 Binder.restoreCallingIdentity(token);
778 }
779 }
RoboErika278ea72014-04-24 14:49:01 -0700780
RoboErik2e7a9162014-06-04 16:53:45 -0700781 @Override
782 public void addSessionsListener(IActiveSessionsListener listener,
783 ComponentName componentName, int userId) throws RemoteException {
784 final int pid = Binder.getCallingPid();
785 final int uid = Binder.getCallingUid();
786 final long token = Binder.clearCallingIdentity();
787
788 try {
789 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
790 synchronized (mLock) {
791 int index = findIndexOfSessionsListenerLocked(listener);
792 if (index != -1) {
793 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
794 return;
795 }
796 SessionsListenerRecord record = new SessionsListenerRecord(listener,
797 resolvedUserId);
798 try {
799 listener.asBinder().linkToDeath(record, 0);
800 } catch (RemoteException e) {
801 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
802 return;
803 }
804 mSessionsListeners.add(record);
805 }
806 } finally {
807 Binder.restoreCallingIdentity(token);
808 }
809 }
810
811 @Override
812 public void removeSessionsListener(IActiveSessionsListener listener)
813 throws RemoteException {
814 synchronized (mLock) {
815 int index = findIndexOfSessionsListenerLocked(listener);
816 if (index != -1) {
817 SessionsListenerRecord record = mSessionsListeners.remove(index);
818 try {
819 record.mListener.asBinder().unlinkToDeath(record, 0);
820 } catch (Exception e) {
821 // ignore exceptions, the record is being removed
822 }
823 }
824 }
825 }
826
RoboErik8a2cfc32014-05-16 11:19:38 -0700827 /**
828 * Handles the dispatching of the media button events to one of the
829 * registered listeners, or if there was none, broadcast an
830 * ACTION_MEDIA_BUTTON intent to the rest of the system.
831 *
832 * @param keyEvent a non-null KeyEvent whose key code is one of the
833 * supported media buttons
834 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
835 * while this key event is dispatched.
836 */
837 @Override
838 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
839 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
840 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
841 return;
842 }
843 final int pid = Binder.getCallingPid();
844 final int uid = Binder.getCallingUid();
845 final long token = Binder.clearCallingIdentity();
846
847 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700848 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700849 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700850 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700851 if (isVoiceKey(keyEvent.getKeyCode())) {
852 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700853 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700854 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700855 }
856 }
857 } finally {
858 Binder.restoreCallingIdentity(token);
859 }
860 }
861
RoboErika278ea72014-04-24 14:49:01 -0700862 @Override
RoboErikb69ffd42014-05-30 14:57:59 -0700863 public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
864 throws RemoteException {
865 final int pid = Binder.getCallingPid();
866 final int uid = Binder.getCallingUid();
867 final long token = Binder.clearCallingIdentity();
868 try {
869 synchronized (mLock) {
870 MediaSessionRecord session = mPriorityStack
871 .getDefaultVolumeSession(mCurrentUserId);
872 dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
873 }
874 } finally {
875 Binder.restoreCallingIdentity(token);
876 }
877 }
878
879 @Override
RoboErik19c95182014-06-23 15:38:48 -0700880 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
881 final int pid = Binder.getCallingPid();
882 final int uid = Binder.getCallingUid();
883 final long token = Binder.clearCallingIdentity();
884 try {
885 enforceStatusBarPermission("listen for volume changes", pid, uid);
886 mRvc = rvc;
887 } finally {
888 Binder.restoreCallingIdentity(token);
889 }
890 }
891
892 @Override
RoboErika278ea72014-04-24 14:49:01 -0700893 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
894 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
895 != PackageManager.PERMISSION_GRANTED) {
896 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
897 + Binder.getCallingPid()
898 + ", uid=" + Binder.getCallingUid());
899 return;
900 }
901
902 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
903 pw.println();
904
905 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700906 pw.println("Session for calls:" + mPrioritySession);
907 if (mPrioritySession != null) {
908 mPrioritySession.dump(pw, "");
909 }
RoboErik4646d282014-05-13 10:13:04 -0700910 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700911 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700912 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700913 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700914 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700915 }
RoboErika5b02322014-05-07 17:05:49 -0700916 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700917
RoboErik4646d282014-05-13 10:13:04 -0700918 pw.println("User Records:");
919 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700920 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700921 UserRecord user = mUserRecords.get(i);
922 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700923 }
924 }
925 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700926
RoboErik2e7a9162014-06-04 16:53:45 -0700927 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
928 final int uid) {
929 String packageName = null;
930 if (componentName != null) {
931 // If they gave us a component name verify they own the
932 // package
933 packageName = componentName.getPackageName();
934 enforcePackageName(packageName, uid);
935 }
936 // Check that they can make calls on behalf of the user and
937 // get the final user id
938 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
939 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
940 // Check if they have the permissions or their component is
941 // enabled for the user they're calling from.
942 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
943 return resolvedUserId;
944 }
945
RoboErikb69ffd42014-05-30 14:57:59 -0700946 private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
947 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700948 if (DEBUG) {
949 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
950 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
951 + ", suggestedStream=" + suggestedStream);
952
953 }
954 if (session == null) {
RoboErik0791e172014-06-08 10:52:32 -0700955 try {
956 if (delta == 0) {
957 mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags,
958 getContext().getOpPackageName());
959 } else {
RoboErikef3c9e92014-06-19 16:07:28 -0700960 int direction = 0;
961 int steps = delta;
962 if (delta > 0) {
963 direction = 1;
964 } else if (delta < 0) {
965 direction = -1;
966 steps = -delta;
967 }
RoboErik0791e172014-06-08 10:52:32 -0700968 for (int i = 0; i < steps; i++) {
969 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
970 flags, getContext().getOpPackageName());
971 }
RoboErikb69ffd42014-05-30 14:57:59 -0700972 }
RoboErik0791e172014-06-08 10:52:32 -0700973 } catch (RemoteException e) {
974 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700975 }
976 } else {
RoboErikef3c9e92014-06-19 16:07:28 -0700977 session.adjustVolumeBy(delta, flags);
RoboErik19c95182014-06-23 15:38:48 -0700978 if (mRvc != null) {
979 try {
980 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
981 } catch (Exception e) {
982 Log.wtf(TAG, "Error sending volume change to system UI.", e);
983 }
984 }
RoboErikb69ffd42014-05-30 14:57:59 -0700985 }
986 }
987
RoboErik9a9d0b52014-05-20 14:53:39 -0700988 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
989 MediaSessionRecord session) {
990 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
991 // If the phone app has priority just give it the event
992 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
993 return;
994 }
995 int action = keyEvent.getAction();
996 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
997 if (action == KeyEvent.ACTION_DOWN) {
998 if (keyEvent.getRepeatCount() == 0) {
999 mVoiceButtonDown = true;
1000 mVoiceButtonHandled = false;
1001 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
1002 mVoiceButtonHandled = true;
1003 startVoiceInput(needWakeLock);
1004 }
1005 } else if (action == KeyEvent.ACTION_UP) {
1006 if (mVoiceButtonDown) {
1007 mVoiceButtonDown = false;
1008 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1009 // Resend the down then send this event through
1010 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
1011 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
1012 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
1013 }
1014 }
1015 }
1016 }
1017
1018 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
1019 MediaSessionRecord session) {
1020 if (session != null) {
1021 if (DEBUG) {
1022 Log.d(TAG, "Sending media key to " + session.getSessionInfo());
1023 }
1024 if (needWakeLock) {
1025 mKeyEventReceiver.aquireWakeLockLocked();
1026 }
1027 // If we don't need a wakelock use -1 as the id so we
1028 // won't release it later
1029 session.sendMediaButton(keyEvent,
1030 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1031 mKeyEventReceiver);
1032 } else {
1033 if (needWakeLock) {
1034 mMediaEventWakeLock.acquire();
1035 }
1036 if (DEBUG) {
1037 Log.d(TAG, "Sending media key ordered broadcast");
1038 }
1039 // Fallback to legacy behavior
1040 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
1041 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1042 if (needWakeLock) {
1043 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
1044 WAKELOCK_RELEASE_ON_FINISHED);
1045 }
1046 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
1047 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
1048 }
1049 }
1050
1051 private void startVoiceInput(boolean needWakeLock) {
1052 Intent voiceIntent = null;
1053 // select which type of search to launch:
1054 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1055 // - device locked or screen off: action is
1056 // ACTION_VOICE_SEARCH_HANDS_FREE
1057 // with EXTRA_SECURE set to true if the device is securely locked
1058 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1059 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1060 if (!isLocked && pm.isScreenOn()) {
1061 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1062 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1063 } else {
1064 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1065 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1066 isLocked && mKeyguardManager.isKeyguardSecure());
1067 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1068 }
1069 // start the search activity
1070 if (needWakeLock) {
1071 mMediaEventWakeLock.acquire();
1072 }
1073 try {
1074 if (voiceIntent != null) {
1075 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1076 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1077 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1078 }
1079 } catch (ActivityNotFoundException e) {
1080 Log.w(TAG, "No activity for search: " + e);
1081 } finally {
1082 if (needWakeLock) {
1083 mMediaEventWakeLock.release();
1084 }
1085 }
1086 }
1087
1088 private boolean isVoiceKey(int keyCode) {
1089 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
1090 }
1091
RoboErik418c10c2014-05-19 09:25:25 -07001092 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1093
1094 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
1095 private final Handler mHandler;
1096 private int mRefCount = 0;
1097 private int mLastTimeoutId = 0;
1098
1099 public KeyEventWakeLockReceiver(Handler handler) {
1100 super(handler);
1101 mHandler = handler;
1102 }
1103
1104 public void onTimeout() {
1105 synchronized (mLock) {
1106 if (mRefCount == 0) {
1107 // We've already released it, so just return
1108 return;
1109 }
1110 mLastTimeoutId++;
1111 mRefCount = 0;
1112 releaseWakeLockLocked();
1113 }
1114 }
1115
1116 public void aquireWakeLockLocked() {
1117 if (mRefCount == 0) {
1118 mMediaEventWakeLock.acquire();
1119 }
1120 mRefCount++;
1121 mHandler.removeCallbacks(this);
1122 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1123
1124 }
1125
1126 @Override
1127 public void run() {
1128 onTimeout();
1129 }
1130
RoboErik8a2cfc32014-05-16 11:19:38 -07001131 @Override
1132 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001133 if (resultCode < mLastTimeoutId) {
1134 // Ignore results from calls that were before the last
1135 // timeout, just in case.
1136 return;
1137 } else {
1138 synchronized (mLock) {
1139 if (mRefCount > 0) {
1140 mRefCount--;
1141 if (mRefCount == 0) {
1142 releaseWakeLockLocked();
1143 }
1144 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001145 }
1146 }
1147 }
RoboErik418c10c2014-05-19 09:25:25 -07001148
1149 private void releaseWakeLockLocked() {
1150 mMediaEventWakeLock.release();
1151 mHandler.removeCallbacks(this);
1152 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001153 };
1154
1155 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1156 @Override
1157 public void onReceive(Context context, Intent intent) {
1158 if (intent == null) {
1159 return;
1160 }
1161 Bundle extras = intent.getExtras();
1162 if (extras == null) {
1163 return;
1164 }
1165 synchronized (mLock) {
1166 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1167 && mMediaEventWakeLock.isHeld()) {
1168 mMediaEventWakeLock.release();
1169 }
1170 }
1171 }
1172 };
RoboErik01fe6612014-02-13 14:19:04 -08001173 }
1174
RoboErik2e7a9162014-06-04 16:53:45 -07001175 final class MessageHandler extends Handler {
1176 private static final int MSG_SESSIONS_CHANGED = 1;
1177
1178 @Override
1179 public void handleMessage(Message msg) {
1180 switch (msg.what) {
1181 case MSG_SESSIONS_CHANGED:
1182 pushSessionsChanged(msg.arg1);
1183 break;
1184 }
1185 }
1186
1187 public void post(int what, int arg1, int arg2) {
1188 obtainMessage(what, arg1, arg2).sendToTarget();
1189 }
1190 }
RoboErik01fe6612014-02-13 14:19:04 -08001191}