blob: 5738a05c327d98f1091630059bd6ce32ac42982d [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;
Jeff Browndba34ba2014-06-24 20:46:03 -070037import android.media.session.MediaSession;
RoboErik07c70772014-03-20 13:33:52 -070038import android.media.session.RouteInfo;
39import android.media.session.RouteOptions;
RoboErik01fe6612014-02-13 14:19:04 -080040import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070041import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080042import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070043import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070044import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070045import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080046import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070047import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070048import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070049import android.os.UserHandle;
50import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070051import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080052import android.text.TextUtils;
53import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070054import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070055import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080056
57import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070058import com.android.server.Watchdog;
59import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080060
RoboErika278ea72014-04-24 14:49:01 -070061import java.io.FileDescriptor;
62import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080063import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070064import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080065
66/**
67 * System implementation of MediaSessionManager
68 */
RoboErika278ea72014-04-24 14:49:01 -070069public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080070 private static final String TAG = "MediaSessionService";
71 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
72
RoboErik418c10c2014-05-19 09:25:25 -070073 private static final int WAKELOCK_TIMEOUT = 5000;
74
RoboErik01fe6612014-02-13 14:19:04 -080075 private final SessionManagerImpl mSessionManagerImpl;
RoboErik4646d282014-05-13 10:13:04 -070076 // private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErika8f95142014-05-05 14:23:49 -070077 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080078
RoboErik4646d282014-05-13 10:13:04 -070079 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
80 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070081 private final ArrayList<SessionsListenerRecord> mSessionsListeners
82 = new ArrayList<SessionsListenerRecord>();
RoboErik4646d282014-05-13 10:13:04 -070083 // private final ArrayList<MediaRouteProviderProxy> mProviders
84 // = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080085 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070086 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070087 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080088
RoboErik9a9d0b52014-05-20 14:53:39 -070089 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070090 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070091 private ContentResolver mContentResolver;
RoboErik9a9d0b52014-05-20 14:53:39 -070092
RoboErike7880d82014-04-30 12:48:25 -070093 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070094 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070095
RoboErik07c70772014-03-20 13:33:52 -070096 // Used to keep track of the current request to show routes for a specific
97 // session so we drop late callbacks properly.
98 private int mShowRoutesRequestId = 0;
99
RoboErik19c95182014-06-23 15:38:48 -0700100 // Used to notify system UI when remote volume was changed. TODO find a
101 // better way to handle this.
102 private IRemoteVolumeController mRvc;
103
RoboErik01fe6612014-02-13 14:19:04 -0800104 public MediaSessionService(Context context) {
105 super(context);
106 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700107 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700108 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
109 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -0800110 }
111
112 @Override
113 public void onStart() {
114 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700115 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700116 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700117 mKeyguardManager =
118 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700119 mAudioService = getAudioService();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700120 mContentResolver = getContext().getContentResolver();
RoboErikb69ffd42014-05-30 14:57:59 -0700121 }
122
123 private IAudioService getAudioService() {
124 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
125 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700126 }
127
128 /**
129 * Should trigger showing the Media route picker dialog. Right now it just
130 * kicks off a query to all the providers to get routes.
131 *
132 * @param record The session to show the picker for.
133 */
134 public void showRoutePickerForSession(MediaSessionRecord record) {
135 // TODO for now just toggle the route to test (we will only have one
136 // match for now)
RoboErik4646d282014-05-13 10:13:04 -0700137 synchronized (mLock) {
138 if (!mAllSessions.contains(record)) {
139 Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
140 return;
141 }
142 RouteInfo current = record.getRoute();
143 UserRecord user = mUserRecords.get(record.getUserId());
144 if (current != null) {
145 // For now send null to mean the local route
146 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
147 if (proxy != null) {
148 proxy.removeSession(record);
149 }
150 record.selectRoute(null);
151 return;
152 }
153 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
154 mShowRoutesRequestId++;
155 for (int i = providers.size() - 1; i >= 0; i--) {
156 MediaRouteProviderProxy provider = providers.get(i);
157 provider.getRoutes(record, mShowRoutesRequestId);
158 }
RoboErik07c70772014-03-20 13:33:52 -0700159 }
160 }
161
162 /**
163 * Connect a session to the given route.
164 *
165 * @param session The session to connect.
166 * @param route The route to connect to.
167 * @param options The options to use for the connection.
168 */
169 public void connectToRoute(MediaSessionRecord session, RouteInfo route,
170 RouteOptions options) {
171 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700172 if (!mAllSessions.contains(session)) {
173 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
174 return;
175 }
176 UserRecord user = mUserRecords.get(session.getUserId());
177 if (user == null) {
178 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
179 return;
180 }
181 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
RoboErik07c70772014-03-20 13:33:52 -0700182 if (proxy == null) {
183 Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
184 return;
185 }
186 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
RoboErik07c70772014-03-20 13:33:52 -0700187 proxy.connectToRoute(session, route, request);
188 }
RoboErik01fe6612014-02-13 14:19:04 -0800189 }
190
RoboErika8f95142014-05-05 14:23:49 -0700191 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700192 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700193 if (!mAllSessions.contains(record)) {
194 Log.d(TAG, "Unknown session updated. Ignoring.");
195 return;
196 }
RoboErika8f95142014-05-05 14:23:49 -0700197 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700198 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700199 if (record.isActive()) {
200 if (mPrioritySession != null) {
201 Log.w(TAG, "Replacing existing priority session with a new session");
202 }
203 mPrioritySession = record;
204 } else {
205 if (mPrioritySession == record) {
206 mPrioritySession = null;
207 }
RoboErike7880d82014-04-30 12:48:25 -0700208 }
RoboErike7880d82014-04-30 12:48:25 -0700209 }
210 }
RoboErik2e7a9162014-06-04 16:53:45 -0700211 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700212 }
213
RoboErika8f95142014-05-05 14:23:49 -0700214 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700215 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700216 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700217 if (!mAllSessions.contains(record)) {
218 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
219 return;
220 }
RoboErik2e7a9162014-06-04 16:53:45 -0700221 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
222 }
223 if (updateSessions) {
224 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700225 }
226 }
227
RoboErik19c95182014-06-23 15:38:48 -0700228 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
229 synchronized (mLock) {
230 if (!mAllSessions.contains(record)) {
231 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
232 return;
233 }
234 pushRemoteVolumeUpdateLocked(record.getUserId());
235 }
236 }
237
RoboErika278ea72014-04-24 14:49:01 -0700238 @Override
RoboErik4646d282014-05-13 10:13:04 -0700239 public void onStartUser(int userHandle) {
240 updateUser();
241 }
242
243 @Override
244 public void onSwitchUser(int userHandle) {
245 updateUser();
246 }
247
248 @Override
249 public void onStopUser(int userHandle) {
250 synchronized (mLock) {
251 UserRecord user = mUserRecords.get(userHandle);
252 if (user != null) {
253 destroyUserLocked(user);
254 }
255 }
256 }
257
258 @Override
RoboErika278ea72014-04-24 14:49:01 -0700259 public void monitor() {
260 synchronized (mLock) {
261 // Check for deadlock
262 }
263 }
264
RoboErik4646d282014-05-13 10:13:04 -0700265 protected void enforcePhoneStatePermission(int pid, int uid) {
266 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
267 != PackageManager.PERMISSION_GRANTED) {
268 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
269 }
270 }
271
RoboErik01fe6612014-02-13 14:19:04 -0800272 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700273 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800274 destroySessionLocked(session);
275 }
276 }
277
278 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700279 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800280 destroySessionLocked(session);
281 }
282 }
283
RoboErik4646d282014-05-13 10:13:04 -0700284 private void updateUser() {
285 synchronized (mLock) {
286 int userId = ActivityManager.getCurrentUser();
287 if (mCurrentUserId != userId) {
288 final int oldUserId = mCurrentUserId;
289 mCurrentUserId = userId; // do this first
290
291 UserRecord oldUser = mUserRecords.get(oldUserId);
292 if (oldUser != null) {
293 oldUser.stopLocked();
294 }
295
296 UserRecord newUser = getOrCreateUser(userId);
297 newUser.startLocked();
298 }
299 }
300 }
301
302 /**
303 * Stop the user and unbind from everything.
304 *
305 * @param user The user to dispose of
306 */
307 private void destroyUserLocked(UserRecord user) {
308 user.stopLocked();
309 user.destroyLocked();
310 mUserRecords.remove(user.mUserId);
311 }
312
313 /*
314 * When a session is removed several things need to happen.
315 * 1. We need to remove it from the relevant user.
316 * 2. We need to remove it from the priority stack.
317 * 3. We need to remove it from all sessions.
318 * 4. If this is the system priority session we need to clear it.
319 * 5. We need to unlink to death from the cb binder
320 * 6. We need to tell the session to do any final cleanup (onDestroy)
321 */
RoboErik01fe6612014-02-13 14:19:04 -0800322 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700323 int userId = session.getUserId();
324 UserRecord user = mUserRecords.get(userId);
325 if (user != null) {
326 user.removeSessionLocked(session);
327 }
328
RoboErika8f95142014-05-05 14:23:49 -0700329 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700330 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700331 if (session == mPrioritySession) {
332 mPrioritySession = null;
333 }
RoboErik4646d282014-05-13 10:13:04 -0700334
335 try {
336 session.getCallback().asBinder().unlinkToDeath(session, 0);
337 } catch (Exception e) {
338 // ignore exceptions while destroying a session.
339 }
340 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700341
342 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800343 }
344
345 private void enforcePackageName(String packageName, int uid) {
346 if (TextUtils.isEmpty(packageName)) {
347 throw new IllegalArgumentException("packageName may not be empty");
348 }
349 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
350 final int packageCount = packages.length;
351 for (int i = 0; i < packageCount; i++) {
352 if (packageName.equals(packages[i])) {
353 return;
354 }
355 }
356 throw new IllegalArgumentException("packageName is not owned by the calling process");
357 }
358
RoboErike7880d82014-04-30 12:48:25 -0700359 /**
360 * Checks a caller's authorization to register an IRemoteControlDisplay.
361 * Authorization is granted if one of the following is true:
362 * <ul>
363 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
364 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700365 * <li>the caller's listener is one of the enabled notification listeners
366 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700367 * </ul>
368 */
RoboErika5b02322014-05-07 17:05:49 -0700369 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
370 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700371 if (getContext()
372 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
373 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700374 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
375 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700376 throw new SecurityException("Missing permission to control media.");
377 }
378 }
379
RoboErik19c95182014-06-23 15:38:48 -0700380 private void enforceStatusBarPermission(String action, int pid, int uid) {
381 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
382 pid, uid) != PackageManager.PERMISSION_GRANTED) {
383 throw new SecurityException("Only system ui may " + action);
384 }
385 }
386
RoboErika5b02322014-05-07 17:05:49 -0700387 /**
388 * This checks if the component is an enabled notification listener for the
389 * specified user. Enabled components may only operate on behalf of the user
390 * they're running as.
391 *
392 * @param compName The component that is enabled.
393 * @param userId The user id of the caller.
394 * @param forUserId The user id they're making the request on behalf of.
395 * @return True if the component is enabled, false otherwise
396 */
397 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
398 int forUserId) {
399 if (userId != forUserId) {
400 // You may not access another user's content as an enabled listener.
401 return false;
402 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700403 if (DEBUG) {
404 Log.d(TAG, "Checking if enabled notification listener " + compName);
405 }
RoboErike7880d82014-04-30 12:48:25 -0700406 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700407 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700408 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700409 userId);
RoboErike7880d82014-04-30 12:48:25 -0700410 if (enabledNotifListeners != null) {
411 final String[] components = enabledNotifListeners.split(":");
412 for (int i = 0; i < components.length; i++) {
413 final ComponentName component =
414 ComponentName.unflattenFromString(components[i]);
415 if (component != null) {
416 if (compName.equals(component)) {
417 if (DEBUG) {
418 Log.d(TAG, "ok to get sessions: " + component +
419 " is authorized notification listener");
420 }
421 return true;
422 }
423 }
424 }
425 }
426 if (DEBUG) {
427 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700428 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700429 }
430 }
431 return false;
432 }
433
RoboErika5b02322014-05-07 17:05:49 -0700434 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700435 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800436 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700437 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800438 }
439 }
440
RoboErik4646d282014-05-13 10:13:04 -0700441 /*
442 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700443 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700444 * 2. It needs to be added to all sessions.
445 * 3. It needs to be added to the priority stack.
446 * 4. It needs to be added to the relevant user record.
447 */
RoboErika5b02322014-05-07 17:05:49 -0700448 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
449 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700450
RoboErika5b02322014-05-07 17:05:49 -0700451 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
452 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800453 try {
454 cb.asBinder().linkToDeath(session, 0);
455 } catch (RemoteException e) {
456 throw new RuntimeException("Media Session owner died prematurely.", e);
457 }
RoboErik4646d282014-05-13 10:13:04 -0700458
459 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700460 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700461
462 UserRecord user = getOrCreateUser(userId);
463 user.addSessionLocked(session);
464
RoboErik2e7a9162014-06-04 16:53:45 -0700465 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
466
RoboErik01fe6612014-02-13 14:19:04 -0800467 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700468 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800469 }
470 return session;
471 }
472
RoboErik4646d282014-05-13 10:13:04 -0700473 private UserRecord getOrCreateUser(int userId) {
474 UserRecord user = mUserRecords.get(userId);
475 if (user == null) {
476 user = new UserRecord(getContext(), userId);
477 mUserRecords.put(userId, user);
478 }
479 return user;
480 }
481
RoboErika8f95142014-05-05 14:23:49 -0700482 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700483 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
484 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700485 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
486 return i;
487 }
488 }
489 return -1;
490 }
491
RoboErik2e7a9162014-06-04 16:53:45 -0700492 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
493 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
494 if (mSessionsListeners.get(i).mListener == listener) {
495 return i;
496 }
497 }
498 return -1;
499 }
500
RoboErike7880d82014-04-30 12:48:25 -0700501 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700502 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700503 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700504 }
505
RoboErik2e7a9162014-06-04 16:53:45 -0700506 private void pushSessionsChanged(int userId) {
507 synchronized (mLock) {
508 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
509 int size = records.size();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700510 if (size > 0) {
511 persistMediaButtonReceiverLocked(records.get(0));
512 }
Jeff Browndba34ba2014-06-24 20:46:03 -0700513 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700514 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700515 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700516 }
RoboErik19c95182014-06-23 15:38:48 -0700517 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700518 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
519 SessionsListenerRecord record = mSessionsListeners.get(i);
520 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
521 try {
522 record.mListener.onActiveSessionsChanged(tokens);
523 } catch (RemoteException e) {
524 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
525 e);
526 mSessionsListeners.remove(i);
527 }
528 }
529 }
530 }
531 }
532
RoboErik19c95182014-06-23 15:38:48 -0700533 private void pushRemoteVolumeUpdateLocked(int userId) {
534 if (mRvc != null) {
535 try {
536 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
537 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
538 } catch (RemoteException e) {
539 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
540 }
541 }
542 }
543
RoboErik6f0e4dd2014-06-17 16:56:27 -0700544 private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
545 ComponentName receiver = record.getMediaButtonReceiver();
546 if (receiver != null) {
547 Settings.System.putStringForUser(mContentResolver,
548 Settings.System.MEDIA_BUTTON_RECEIVER,
549 receiver == null ? "" : receiver.flattenToString(),
550 UserHandle.USER_CURRENT);
551 }
552 }
553
RoboErik07c70772014-03-20 13:33:52 -0700554 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
555 = new MediaRouteProviderProxy.RoutesListener() {
556 @Override
557 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
558 int reqId) {
559 // TODO for now select the first route to test, eventually add the
560 // new routes to the dialog if it is still open
561 synchronized (mLock) {
562 int index = findIndexOfSessionForIdLocked(sessionId);
563 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700564 MediaSessionRecord record = mAllSessions.get(index);
565 RouteInfo route = routes.get(0);
566 record.selectRoute(route);
567 UserRecord user = mUserRecords.get(record.getUserId());
568 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
569 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700570 }
571 }
572 }
573
574 @Override
575 public void onRouteConnected(String sessionId, RouteInfo route,
576 RouteRequest options, RouteConnectionRecord connection) {
577 synchronized (mLock) {
578 int index = findIndexOfSessionForIdLocked(sessionId);
579 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700580 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700581 session.setRouteConnected(route, options.getConnectionOptions(), connection);
582 }
583 }
584 }
585 };
586
RoboErik4646d282014-05-13 10:13:04 -0700587 /**
588 * Information about a particular user. The contents of this object is
589 * guarded by mLock.
590 */
591 final class UserRecord {
592 private final int mUserId;
593 private final MediaRouteProviderWatcher mRouteProviderWatcher;
594 private final ArrayList<MediaRouteProviderProxy> mProviders
595 = new ArrayList<MediaRouteProviderProxy>();
596 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
597
598 public UserRecord(Context context, int userId) {
599 mUserId = userId;
600 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
601 mProviderWatcherCallback, mHandler, userId);
602 }
603
604 public void startLocked() {
605 mRouteProviderWatcher.start();
606 }
607
608 public void stopLocked() {
609 mRouteProviderWatcher.stop();
610 updateInterestLocked();
611 }
612
613 public void destroyLocked() {
614 for (int i = mSessions.size() - 1; i >= 0; i--) {
615 MediaSessionRecord session = mSessions.get(i);
616 MediaSessionService.this.destroySessionLocked(session);
617 if (session.isConnected()) {
RoboErik42ea7ee2014-05-16 16:27:35 -0700618 session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
RoboErik4646d282014-05-13 10:13:04 -0700619 }
620 }
621 }
622
623 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
624 return mProviders;
625 }
626
627 public ArrayList<MediaSessionRecord> getSessionsLocked() {
628 return mSessions;
629 }
630
631 public void addSessionLocked(MediaSessionRecord session) {
632 mSessions.add(session);
633 updateInterestLocked();
634 }
635
636 public void removeSessionLocked(MediaSessionRecord session) {
637 mSessions.remove(session);
638 RouteInfo route = session.getRoute();
639 if (route != null) {
640 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
641 if (provider != null) {
642 provider.removeSession(session);
643 }
644 }
645 updateInterestLocked();
646 }
647
648 public void dumpLocked(PrintWriter pw, String prefix) {
649 pw.println(prefix + "Record for user " + mUserId);
650 String indent = prefix + " ";
651 int size = mProviders.size();
652 pw.println(indent + size + " Providers:");
653 for (int i = 0; i < size; i++) {
654 mProviders.get(i).dump(pw, indent);
655 }
656 pw.println();
657 size = mSessions.size();
658 pw.println(indent + size + " Sessions:");
659 for (int i = 0; i < size; i++) {
660 // Just print the session info, the full session dump will
661 // already be in the list of all sessions.
662 pw.println(indent + mSessions.get(i).getSessionInfo());
663 }
664 }
665
666 public void updateInterestLocked() {
667 // TODO go through the sessions and build up the set of interfaces
668 // we're interested in. Update the provider watcher.
669 // For now, just express interest in all providers for the current
670 // user
671 boolean interested = mUserId == mCurrentUserId;
672 for (int i = mProviders.size() - 1; i >= 0; i--) {
673 mProviders.get(i).setInterested(interested);
674 }
675 }
676
677 private MediaRouteProviderProxy getProviderLocked(String providerId) {
678 for (int i = mProviders.size() - 1; i >= 0; i--) {
679 MediaRouteProviderProxy provider = mProviders.get(i);
680 if (TextUtils.equals(providerId, provider.getId())) {
681 return provider;
682 }
683 }
684 return null;
685 }
686
687 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
688 = new MediaRouteProviderWatcher.Callback() {
689 @Override
690 public void removeProvider(MediaRouteProviderProxy provider) {
691 synchronized (mLock) {
692 mProviders.remove(provider);
693 provider.setRoutesListener(null);
694 provider.setInterested(false);
695 }
696 }
697
698 @Override
699 public void addProvider(MediaRouteProviderProxy provider) {
700 synchronized (mLock) {
701 mProviders.add(provider);
702 provider.setRoutesListener(mRoutesCallback);
703 provider.setInterested(true);
704 }
705 }
706 };
707 }
708
RoboErik2e7a9162014-06-04 16:53:45 -0700709 final class SessionsListenerRecord implements IBinder.DeathRecipient {
710 private final IActiveSessionsListener mListener;
711 private final int mUserId;
712
713 public SessionsListenerRecord(IActiveSessionsListener listener, int userId) {
714 mListener = listener;
715 mUserId = userId;
716 }
717
718 @Override
719 public void binderDied() {
720 synchronized (mLock) {
721 mSessionsListeners.remove(this);
722 }
723 }
724 }
725
RoboErik07c70772014-03-20 13:33:52 -0700726 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700727 private static final String EXTRA_WAKELOCK_ACQUIRED =
728 "android.media.AudioService.WAKELOCK_ACQUIRED";
729 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
730
RoboErik9a9d0b52014-05-20 14:53:39 -0700731 private boolean mVoiceButtonDown = false;
732 private boolean mVoiceButtonHandled = false;
733
RoboErik07c70772014-03-20 13:33:52 -0700734 @Override
RoboErika5b02322014-05-07 17:05:49 -0700735 public ISession createSession(String packageName, ISessionCallback cb, String tag,
736 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800737 final int pid = Binder.getCallingPid();
738 final int uid = Binder.getCallingUid();
739 final long token = Binder.clearCallingIdentity();
740 try {
741 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700742 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
743 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800744 if (cb == null) {
745 throw new IllegalArgumentException("Controller callback cannot be null");
746 }
RoboErika5b02322014-05-07 17:05:49 -0700747 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
748 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700749 } finally {
750 Binder.restoreCallingIdentity(token);
751 }
752 }
753
754 @Override
RoboErika5b02322014-05-07 17:05:49 -0700755 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700756 final int pid = Binder.getCallingPid();
757 final int uid = Binder.getCallingUid();
758 final long token = Binder.clearCallingIdentity();
759
760 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700761 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700762 ArrayList<IBinder> binders = new ArrayList<IBinder>();
763 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700764 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700765 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700766 int size = records.size();
767 for (int i = 0; i < size; i++) {
768 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700769 }
770 }
771 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800772 } finally {
773 Binder.restoreCallingIdentity(token);
774 }
775 }
RoboErika278ea72014-04-24 14:49:01 -0700776
RoboErik2e7a9162014-06-04 16:53:45 -0700777 @Override
778 public void addSessionsListener(IActiveSessionsListener listener,
779 ComponentName componentName, int userId) throws RemoteException {
780 final int pid = Binder.getCallingPid();
781 final int uid = Binder.getCallingUid();
782 final long token = Binder.clearCallingIdentity();
783
784 try {
785 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
786 synchronized (mLock) {
787 int index = findIndexOfSessionsListenerLocked(listener);
788 if (index != -1) {
789 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
790 return;
791 }
792 SessionsListenerRecord record = new SessionsListenerRecord(listener,
793 resolvedUserId);
794 try {
795 listener.asBinder().linkToDeath(record, 0);
796 } catch (RemoteException e) {
797 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
798 return;
799 }
800 mSessionsListeners.add(record);
801 }
802 } finally {
803 Binder.restoreCallingIdentity(token);
804 }
805 }
806
807 @Override
808 public void removeSessionsListener(IActiveSessionsListener listener)
809 throws RemoteException {
810 synchronized (mLock) {
811 int index = findIndexOfSessionsListenerLocked(listener);
812 if (index != -1) {
813 SessionsListenerRecord record = mSessionsListeners.remove(index);
814 try {
815 record.mListener.asBinder().unlinkToDeath(record, 0);
816 } catch (Exception e) {
817 // ignore exceptions, the record is being removed
818 }
819 }
820 }
821 }
822
RoboErik8a2cfc32014-05-16 11:19:38 -0700823 /**
824 * Handles the dispatching of the media button events to one of the
825 * registered listeners, or if there was none, broadcast an
826 * ACTION_MEDIA_BUTTON intent to the rest of the system.
827 *
828 * @param keyEvent a non-null KeyEvent whose key code is one of the
829 * supported media buttons
830 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
831 * while this key event is dispatched.
832 */
833 @Override
834 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
835 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
836 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
837 return;
838 }
839 final int pid = Binder.getCallingPid();
840 final int uid = Binder.getCallingUid();
841 final long token = Binder.clearCallingIdentity();
842
843 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700844 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700845 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700846 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700847 if (isVoiceKey(keyEvent.getKeyCode())) {
848 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700849 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700850 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700851 }
852 }
853 } finally {
854 Binder.restoreCallingIdentity(token);
855 }
856 }
857
RoboErika278ea72014-04-24 14:49:01 -0700858 @Override
RoboErikb69ffd42014-05-30 14:57:59 -0700859 public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
860 throws RemoteException {
861 final int pid = Binder.getCallingPid();
862 final int uid = Binder.getCallingUid();
863 final long token = Binder.clearCallingIdentity();
864 try {
865 synchronized (mLock) {
866 MediaSessionRecord session = mPriorityStack
867 .getDefaultVolumeSession(mCurrentUserId);
868 dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
869 }
870 } finally {
871 Binder.restoreCallingIdentity(token);
872 }
873 }
874
875 @Override
RoboErik19c95182014-06-23 15:38:48 -0700876 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
877 final int pid = Binder.getCallingPid();
878 final int uid = Binder.getCallingUid();
879 final long token = Binder.clearCallingIdentity();
880 try {
881 enforceStatusBarPermission("listen for volume changes", pid, uid);
882 mRvc = rvc;
883 } finally {
884 Binder.restoreCallingIdentity(token);
885 }
886 }
887
888 @Override
RoboErika278ea72014-04-24 14:49:01 -0700889 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
890 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
891 != PackageManager.PERMISSION_GRANTED) {
892 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
893 + Binder.getCallingPid()
894 + ", uid=" + Binder.getCallingUid());
895 return;
896 }
897
898 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
899 pw.println();
900
901 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700902 pw.println("Session for calls:" + mPrioritySession);
903 if (mPrioritySession != null) {
904 mPrioritySession.dump(pw, "");
905 }
RoboErik4646d282014-05-13 10:13:04 -0700906 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700907 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700908 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700909 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700910 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700911 }
RoboErika5b02322014-05-07 17:05:49 -0700912 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700913
RoboErik4646d282014-05-13 10:13:04 -0700914 pw.println("User Records:");
915 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700916 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700917 UserRecord user = mUserRecords.get(i);
918 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700919 }
920 }
921 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700922
RoboErik2e7a9162014-06-04 16:53:45 -0700923 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
924 final int uid) {
925 String packageName = null;
926 if (componentName != null) {
927 // If they gave us a component name verify they own the
928 // package
929 packageName = componentName.getPackageName();
930 enforcePackageName(packageName, uid);
931 }
932 // Check that they can make calls on behalf of the user and
933 // get the final user id
934 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
935 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
936 // Check if they have the permissions or their component is
937 // enabled for the user they're calling from.
938 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
939 return resolvedUserId;
940 }
941
RoboErikb69ffd42014-05-30 14:57:59 -0700942 private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
943 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700944 if (DEBUG) {
945 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
946 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
947 + ", suggestedStream=" + suggestedStream);
948
949 }
950 if (session == null) {
RoboErik0791e172014-06-08 10:52:32 -0700951 try {
952 if (delta == 0) {
953 mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags,
954 getContext().getOpPackageName());
955 } else {
RoboErikef3c9e92014-06-19 16:07:28 -0700956 int direction = 0;
957 int steps = delta;
958 if (delta > 0) {
959 direction = 1;
960 } else if (delta < 0) {
961 direction = -1;
962 steps = -delta;
963 }
RoboErik0791e172014-06-08 10:52:32 -0700964 for (int i = 0; i < steps; i++) {
965 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
966 flags, getContext().getOpPackageName());
967 }
RoboErikb69ffd42014-05-30 14:57:59 -0700968 }
RoboErik0791e172014-06-08 10:52:32 -0700969 } catch (RemoteException e) {
970 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700971 }
972 } else {
RoboErikef3c9e92014-06-19 16:07:28 -0700973 session.adjustVolumeBy(delta, flags);
RoboErik19c95182014-06-23 15:38:48 -0700974 if (mRvc != null) {
975 try {
976 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
977 } catch (Exception e) {
978 Log.wtf(TAG, "Error sending volume change to system UI.", e);
979 }
980 }
RoboErikb69ffd42014-05-30 14:57:59 -0700981 }
982 }
983
RoboErik9a9d0b52014-05-20 14:53:39 -0700984 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
985 MediaSessionRecord session) {
986 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
987 // If the phone app has priority just give it the event
988 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
989 return;
990 }
991 int action = keyEvent.getAction();
992 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
993 if (action == KeyEvent.ACTION_DOWN) {
994 if (keyEvent.getRepeatCount() == 0) {
995 mVoiceButtonDown = true;
996 mVoiceButtonHandled = false;
997 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
998 mVoiceButtonHandled = true;
999 startVoiceInput(needWakeLock);
1000 }
1001 } else if (action == KeyEvent.ACTION_UP) {
1002 if (mVoiceButtonDown) {
1003 mVoiceButtonDown = false;
1004 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1005 // Resend the down then send this event through
1006 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
1007 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
1008 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
1009 }
1010 }
1011 }
1012 }
1013
1014 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
1015 MediaSessionRecord session) {
1016 if (session != null) {
1017 if (DEBUG) {
1018 Log.d(TAG, "Sending media key to " + session.getSessionInfo());
1019 }
1020 if (needWakeLock) {
1021 mKeyEventReceiver.aquireWakeLockLocked();
1022 }
1023 // If we don't need a wakelock use -1 as the id so we
1024 // won't release it later
1025 session.sendMediaButton(keyEvent,
1026 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1027 mKeyEventReceiver);
1028 } else {
1029 if (needWakeLock) {
1030 mMediaEventWakeLock.acquire();
1031 }
1032 if (DEBUG) {
1033 Log.d(TAG, "Sending media key ordered broadcast");
1034 }
1035 // Fallback to legacy behavior
1036 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
1037 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1038 if (needWakeLock) {
1039 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
1040 WAKELOCK_RELEASE_ON_FINISHED);
1041 }
1042 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
1043 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
1044 }
1045 }
1046
1047 private void startVoiceInput(boolean needWakeLock) {
1048 Intent voiceIntent = null;
1049 // select which type of search to launch:
1050 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1051 // - device locked or screen off: action is
1052 // ACTION_VOICE_SEARCH_HANDS_FREE
1053 // with EXTRA_SECURE set to true if the device is securely locked
1054 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1055 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1056 if (!isLocked && pm.isScreenOn()) {
1057 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1058 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1059 } else {
1060 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1061 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1062 isLocked && mKeyguardManager.isKeyguardSecure());
1063 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1064 }
1065 // start the search activity
1066 if (needWakeLock) {
1067 mMediaEventWakeLock.acquire();
1068 }
1069 try {
1070 if (voiceIntent != null) {
1071 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1072 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1073 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1074 }
1075 } catch (ActivityNotFoundException e) {
1076 Log.w(TAG, "No activity for search: " + e);
1077 } finally {
1078 if (needWakeLock) {
1079 mMediaEventWakeLock.release();
1080 }
1081 }
1082 }
1083
1084 private boolean isVoiceKey(int keyCode) {
1085 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
1086 }
1087
RoboErik418c10c2014-05-19 09:25:25 -07001088 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1089
1090 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
1091 private final Handler mHandler;
1092 private int mRefCount = 0;
1093 private int mLastTimeoutId = 0;
1094
1095 public KeyEventWakeLockReceiver(Handler handler) {
1096 super(handler);
1097 mHandler = handler;
1098 }
1099
1100 public void onTimeout() {
1101 synchronized (mLock) {
1102 if (mRefCount == 0) {
1103 // We've already released it, so just return
1104 return;
1105 }
1106 mLastTimeoutId++;
1107 mRefCount = 0;
1108 releaseWakeLockLocked();
1109 }
1110 }
1111
1112 public void aquireWakeLockLocked() {
1113 if (mRefCount == 0) {
1114 mMediaEventWakeLock.acquire();
1115 }
1116 mRefCount++;
1117 mHandler.removeCallbacks(this);
1118 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1119
1120 }
1121
1122 @Override
1123 public void run() {
1124 onTimeout();
1125 }
1126
RoboErik8a2cfc32014-05-16 11:19:38 -07001127 @Override
1128 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001129 if (resultCode < mLastTimeoutId) {
1130 // Ignore results from calls that were before the last
1131 // timeout, just in case.
1132 return;
1133 } else {
1134 synchronized (mLock) {
1135 if (mRefCount > 0) {
1136 mRefCount--;
1137 if (mRefCount == 0) {
1138 releaseWakeLockLocked();
1139 }
1140 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001141 }
1142 }
1143 }
RoboErik418c10c2014-05-19 09:25:25 -07001144
1145 private void releaseWakeLockLocked() {
1146 mMediaEventWakeLock.release();
1147 mHandler.removeCallbacks(this);
1148 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001149 };
1150
1151 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1152 @Override
1153 public void onReceive(Context context, Intent intent) {
1154 if (intent == null) {
1155 return;
1156 }
1157 Bundle extras = intent.getExtras();
1158 if (extras == null) {
1159 return;
1160 }
1161 synchronized (mLock) {
1162 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1163 && mMediaEventWakeLock.isHeld()) {
1164 mMediaEventWakeLock.release();
1165 }
1166 }
1167 }
1168 };
RoboErik01fe6612014-02-13 14:19:04 -08001169 }
1170
RoboErik2e7a9162014-06-04 16:53:45 -07001171 final class MessageHandler extends Handler {
1172 private static final int MSG_SESSIONS_CHANGED = 1;
1173
1174 @Override
1175 public void handleMessage(Message msg) {
1176 switch (msg.what) {
1177 case MSG_SESSIONS_CHANGED:
1178 pushSessionsChanged(msg.arg1);
1179 break;
1180 }
1181 }
1182
1183 public void post(int what, int arg1, int arg2) {
1184 obtainMessage(what, arg1, arg2).sendToTarget();
1185 }
1186 }
RoboErik01fe6612014-02-13 14:19:04 -08001187}