blob: 4d1829d3b23b26e32093d0173cc6424923db00c6 [file] [log] [blame]
RoboErik01fe6612014-02-13 14:19:04 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.media;
18
RoboErika278ea72014-04-24 14:49:01 -070019import android.Manifest;
RoboErik8a2cfc32014-05-16 11:19:38 -070020import android.app.Activity;
RoboErike7880d82014-04-30 12:48:25 -070021import android.app.ActivityManager;
RoboErik9a9d0b52014-05-20 14:53:39 -070022import android.app.KeyguardManager;
23import android.content.ActivityNotFoundException;
RoboErik8a2cfc32014-05-16 11:19:38 -070024import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070025import android.content.ComponentName;
RoboErik01fe6612014-02-13 14:19:04 -080026import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070027import android.content.Intent;
RoboErika278ea72014-04-24 14:49:01 -070028import android.content.pm.PackageManager;
RoboErikb69ffd42014-05-30 14:57:59 -070029import android.media.AudioManager;
30import android.media.IAudioService;
RoboErik07c70772014-03-20 13:33:52 -070031import android.media.routeprovider.RouteRequest;
RoboErik2e7a9162014-06-04 16:53:45 -070032import android.media.session.IActiveSessionsListener;
RoboErik07c70772014-03-20 13:33:52 -070033import android.media.session.ISession;
34import android.media.session.ISessionCallback;
35import android.media.session.ISessionManager;
RoboErik2e7a9162014-06-04 16:53:45 -070036import android.media.session.MediaSessionToken;
RoboErik07c70772014-03-20 13:33:52 -070037import android.media.session.RouteInfo;
38import android.media.session.RouteOptions;
RoboErik42ea7ee2014-05-16 16:27:35 -070039import android.media.session.MediaSession;
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;
RoboErik9a9d0b52014-05-20 14:53:39 -070091
RoboErike7880d82014-04-30 12:48:25 -070092 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070093 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070094
RoboErik07c70772014-03-20 13:33:52 -070095 // Used to keep track of the current request to show routes for a specific
96 // session so we drop late callbacks properly.
97 private int mShowRoutesRequestId = 0;
98
RoboErika5b02322014-05-07 17:05:49 -070099 // TODO refactor to have per user state for providers. See
100 // MediaRouterService for an example
RoboErik07c70772014-03-20 13:33:52 -0700101
RoboErik01fe6612014-02-13 14:19:04 -0800102 public MediaSessionService(Context context) {
103 super(context);
104 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700105 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700106 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
107 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -0800108 }
109
110 @Override
111 public void onStart() {
112 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700113 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700114 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700115 mKeyguardManager =
116 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700117 mAudioService = getAudioService();
118 }
119
120 private IAudioService getAudioService() {
121 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
122 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700123 }
124
125 /**
126 * Should trigger showing the Media route picker dialog. Right now it just
127 * kicks off a query to all the providers to get routes.
128 *
129 * @param record The session to show the picker for.
130 */
131 public void showRoutePickerForSession(MediaSessionRecord record) {
132 // TODO for now just toggle the route to test (we will only have one
133 // match for now)
RoboErik4646d282014-05-13 10:13:04 -0700134 synchronized (mLock) {
135 if (!mAllSessions.contains(record)) {
136 Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
137 return;
138 }
139 RouteInfo current = record.getRoute();
140 UserRecord user = mUserRecords.get(record.getUserId());
141 if (current != null) {
142 // For now send null to mean the local route
143 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
144 if (proxy != null) {
145 proxy.removeSession(record);
146 }
147 record.selectRoute(null);
148 return;
149 }
150 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
151 mShowRoutesRequestId++;
152 for (int i = providers.size() - 1; i >= 0; i--) {
153 MediaRouteProviderProxy provider = providers.get(i);
154 provider.getRoutes(record, mShowRoutesRequestId);
155 }
RoboErik07c70772014-03-20 13:33:52 -0700156 }
157 }
158
159 /**
160 * Connect a session to the given route.
161 *
162 * @param session The session to connect.
163 * @param route The route to connect to.
164 * @param options The options to use for the connection.
165 */
166 public void connectToRoute(MediaSessionRecord session, RouteInfo route,
167 RouteOptions options) {
168 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700169 if (!mAllSessions.contains(session)) {
170 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
171 return;
172 }
173 UserRecord user = mUserRecords.get(session.getUserId());
174 if (user == null) {
175 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
176 return;
177 }
178 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
RoboErik07c70772014-03-20 13:33:52 -0700179 if (proxy == null) {
180 Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
181 return;
182 }
183 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
RoboErik07c70772014-03-20 13:33:52 -0700184 proxy.connectToRoute(session, route, request);
185 }
RoboErik01fe6612014-02-13 14:19:04 -0800186 }
187
RoboErika8f95142014-05-05 14:23:49 -0700188 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700189 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700190 if (!mAllSessions.contains(record)) {
191 Log.d(TAG, "Unknown session updated. Ignoring.");
192 return;
193 }
RoboErika8f95142014-05-05 14:23:49 -0700194 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700195 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700196 if (record.isActive()) {
197 if (mPrioritySession != null) {
198 Log.w(TAG, "Replacing existing priority session with a new session");
199 }
200 mPrioritySession = record;
201 } else {
202 if (mPrioritySession == record) {
203 mPrioritySession = null;
204 }
RoboErike7880d82014-04-30 12:48:25 -0700205 }
RoboErike7880d82014-04-30 12:48:25 -0700206 }
207 }
RoboErik2e7a9162014-06-04 16:53:45 -0700208 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700209 }
210
RoboErika8f95142014-05-05 14:23:49 -0700211 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700212 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700213 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700214 if (!mAllSessions.contains(record)) {
215 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
216 return;
217 }
RoboErik2e7a9162014-06-04 16:53:45 -0700218 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
219 }
220 if (updateSessions) {
221 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700222 }
223 }
224
RoboErika278ea72014-04-24 14:49:01 -0700225 @Override
RoboErik4646d282014-05-13 10:13:04 -0700226 public void onStartUser(int userHandle) {
227 updateUser();
228 }
229
230 @Override
231 public void onSwitchUser(int userHandle) {
232 updateUser();
233 }
234
235 @Override
236 public void onStopUser(int userHandle) {
237 synchronized (mLock) {
238 UserRecord user = mUserRecords.get(userHandle);
239 if (user != null) {
240 destroyUserLocked(user);
241 }
242 }
243 }
244
245 @Override
RoboErika278ea72014-04-24 14:49:01 -0700246 public void monitor() {
247 synchronized (mLock) {
248 // Check for deadlock
249 }
250 }
251
RoboErik4646d282014-05-13 10:13:04 -0700252 protected void enforcePhoneStatePermission(int pid, int uid) {
253 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
254 != PackageManager.PERMISSION_GRANTED) {
255 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
256 }
257 }
258
RoboErik01fe6612014-02-13 14:19:04 -0800259 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700260 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800261 destroySessionLocked(session);
262 }
263 }
264
265 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700266 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800267 destroySessionLocked(session);
268 }
269 }
270
RoboErik4646d282014-05-13 10:13:04 -0700271 private void updateUser() {
272 synchronized (mLock) {
273 int userId = ActivityManager.getCurrentUser();
274 if (mCurrentUserId != userId) {
275 final int oldUserId = mCurrentUserId;
276 mCurrentUserId = userId; // do this first
277
278 UserRecord oldUser = mUserRecords.get(oldUserId);
279 if (oldUser != null) {
280 oldUser.stopLocked();
281 }
282
283 UserRecord newUser = getOrCreateUser(userId);
284 newUser.startLocked();
285 }
286 }
287 }
288
289 /**
290 * Stop the user and unbind from everything.
291 *
292 * @param user The user to dispose of
293 */
294 private void destroyUserLocked(UserRecord user) {
295 user.stopLocked();
296 user.destroyLocked();
297 mUserRecords.remove(user.mUserId);
298 }
299
300 /*
301 * When a session is removed several things need to happen.
302 * 1. We need to remove it from the relevant user.
303 * 2. We need to remove it from the priority stack.
304 * 3. We need to remove it from all sessions.
305 * 4. If this is the system priority session we need to clear it.
306 * 5. We need to unlink to death from the cb binder
307 * 6. We need to tell the session to do any final cleanup (onDestroy)
308 */
RoboErik01fe6612014-02-13 14:19:04 -0800309 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700310 int userId = session.getUserId();
311 UserRecord user = mUserRecords.get(userId);
312 if (user != null) {
313 user.removeSessionLocked(session);
314 }
315
RoboErika8f95142014-05-05 14:23:49 -0700316 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700317 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700318 if (session == mPrioritySession) {
319 mPrioritySession = null;
320 }
RoboErik4646d282014-05-13 10:13:04 -0700321
322 try {
323 session.getCallback().asBinder().unlinkToDeath(session, 0);
324 } catch (Exception e) {
325 // ignore exceptions while destroying a session.
326 }
327 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700328
329 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800330 }
331
332 private void enforcePackageName(String packageName, int uid) {
333 if (TextUtils.isEmpty(packageName)) {
334 throw new IllegalArgumentException("packageName may not be empty");
335 }
336 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
337 final int packageCount = packages.length;
338 for (int i = 0; i < packageCount; i++) {
339 if (packageName.equals(packages[i])) {
340 return;
341 }
342 }
343 throw new IllegalArgumentException("packageName is not owned by the calling process");
344 }
345
RoboErike7880d82014-04-30 12:48:25 -0700346 /**
347 * Checks a caller's authorization to register an IRemoteControlDisplay.
348 * Authorization is granted if one of the following is true:
349 * <ul>
350 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
351 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700352 * <li>the caller's listener is one of the enabled notification listeners
353 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700354 * </ul>
355 */
RoboErika5b02322014-05-07 17:05:49 -0700356 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
357 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700358 if (getContext()
359 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
360 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700361 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
362 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700363 throw new SecurityException("Missing permission to control media.");
364 }
365 }
366
RoboErika5b02322014-05-07 17:05:49 -0700367 /**
368 * This checks if the component is an enabled notification listener for the
369 * specified user. Enabled components may only operate on behalf of the user
370 * they're running as.
371 *
372 * @param compName The component that is enabled.
373 * @param userId The user id of the caller.
374 * @param forUserId The user id they're making the request on behalf of.
375 * @return True if the component is enabled, false otherwise
376 */
377 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
378 int forUserId) {
379 if (userId != forUserId) {
380 // You may not access another user's content as an enabled listener.
381 return false;
382 }
RoboErike7880d82014-04-30 12:48:25 -0700383 if (compName != null) {
RoboErike7880d82014-04-30 12:48:25 -0700384 final String enabledNotifListeners = Settings.Secure.getStringForUser(
385 getContext().getContentResolver(),
386 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700387 userId);
RoboErike7880d82014-04-30 12:48:25 -0700388 if (enabledNotifListeners != null) {
389 final String[] components = enabledNotifListeners.split(":");
390 for (int i = 0; i < components.length; i++) {
391 final ComponentName component =
392 ComponentName.unflattenFromString(components[i]);
393 if (component != null) {
394 if (compName.equals(component)) {
395 if (DEBUG) {
396 Log.d(TAG, "ok to get sessions: " + component +
397 " is authorized notification listener");
398 }
399 return true;
400 }
401 }
402 }
403 }
404 if (DEBUG) {
405 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700406 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700407 }
408 }
409 return false;
410 }
411
RoboErika5b02322014-05-07 17:05:49 -0700412 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700413 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800414 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700415 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800416 }
417 }
418
RoboErik4646d282014-05-13 10:13:04 -0700419 /*
420 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700421 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700422 * 2. It needs to be added to all sessions.
423 * 3. It needs to be added to the priority stack.
424 * 4. It needs to be added to the relevant user record.
425 */
RoboErika5b02322014-05-07 17:05:49 -0700426 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
427 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700428
RoboErika5b02322014-05-07 17:05:49 -0700429 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
430 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800431 try {
432 cb.asBinder().linkToDeath(session, 0);
433 } catch (RemoteException e) {
434 throw new RuntimeException("Media Session owner died prematurely.", e);
435 }
RoboErik4646d282014-05-13 10:13:04 -0700436
437 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700438 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700439
440 UserRecord user = getOrCreateUser(userId);
441 user.addSessionLocked(session);
442
RoboErik2e7a9162014-06-04 16:53:45 -0700443 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
444
RoboErik01fe6612014-02-13 14:19:04 -0800445 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700446 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800447 }
448 return session;
449 }
450
RoboErik4646d282014-05-13 10:13:04 -0700451 private UserRecord getOrCreateUser(int userId) {
452 UserRecord user = mUserRecords.get(userId);
453 if (user == null) {
454 user = new UserRecord(getContext(), userId);
455 mUserRecords.put(userId, user);
456 }
457 return user;
458 }
459
RoboErika8f95142014-05-05 14:23:49 -0700460 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700461 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
462 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700463 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
464 return i;
465 }
466 }
467 return -1;
468 }
469
RoboErik2e7a9162014-06-04 16:53:45 -0700470 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
471 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
472 if (mSessionsListeners.get(i).mListener == listener) {
473 return i;
474 }
475 }
476 return -1;
477 }
478
RoboErike7880d82014-04-30 12:48:25 -0700479 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700480 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700481 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700482 }
483
RoboErik2e7a9162014-06-04 16:53:45 -0700484 private void pushSessionsChanged(int userId) {
485 synchronized (mLock) {
486 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
487 int size = records.size();
488 ArrayList<MediaSessionToken> tokens = new ArrayList<MediaSessionToken>();
489 for (int i = 0; i < size; i++) {
490 tokens.add(new MediaSessionToken(records.get(i).getControllerBinder()));
491 }
492 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
493 SessionsListenerRecord record = mSessionsListeners.get(i);
494 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
495 try {
496 record.mListener.onActiveSessionsChanged(tokens);
497 } catch (RemoteException e) {
498 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
499 e);
500 mSessionsListeners.remove(i);
501 }
502 }
503 }
504 }
505 }
506
RoboErik07c70772014-03-20 13:33:52 -0700507 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
508 = new MediaRouteProviderProxy.RoutesListener() {
509 @Override
510 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
511 int reqId) {
512 // TODO for now select the first route to test, eventually add the
513 // new routes to the dialog if it is still open
514 synchronized (mLock) {
515 int index = findIndexOfSessionForIdLocked(sessionId);
516 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700517 MediaSessionRecord record = mAllSessions.get(index);
518 RouteInfo route = routes.get(0);
519 record.selectRoute(route);
520 UserRecord user = mUserRecords.get(record.getUserId());
521 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
522 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700523 }
524 }
525 }
526
527 @Override
528 public void onRouteConnected(String sessionId, RouteInfo route,
529 RouteRequest options, RouteConnectionRecord connection) {
530 synchronized (mLock) {
531 int index = findIndexOfSessionForIdLocked(sessionId);
532 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700533 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700534 session.setRouteConnected(route, options.getConnectionOptions(), connection);
535 }
536 }
537 }
538 };
539
RoboErik4646d282014-05-13 10:13:04 -0700540 /**
541 * Information about a particular user. The contents of this object is
542 * guarded by mLock.
543 */
544 final class UserRecord {
545 private final int mUserId;
546 private final MediaRouteProviderWatcher mRouteProviderWatcher;
547 private final ArrayList<MediaRouteProviderProxy> mProviders
548 = new ArrayList<MediaRouteProviderProxy>();
549 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
550
551 public UserRecord(Context context, int userId) {
552 mUserId = userId;
553 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
554 mProviderWatcherCallback, mHandler, userId);
555 }
556
557 public void startLocked() {
558 mRouteProviderWatcher.start();
559 }
560
561 public void stopLocked() {
562 mRouteProviderWatcher.stop();
563 updateInterestLocked();
564 }
565
566 public void destroyLocked() {
567 for (int i = mSessions.size() - 1; i >= 0; i--) {
568 MediaSessionRecord session = mSessions.get(i);
569 MediaSessionService.this.destroySessionLocked(session);
570 if (session.isConnected()) {
RoboErik42ea7ee2014-05-16 16:27:35 -0700571 session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
RoboErik4646d282014-05-13 10:13:04 -0700572 }
573 }
574 }
575
576 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
577 return mProviders;
578 }
579
580 public ArrayList<MediaSessionRecord> getSessionsLocked() {
581 return mSessions;
582 }
583
584 public void addSessionLocked(MediaSessionRecord session) {
585 mSessions.add(session);
586 updateInterestLocked();
587 }
588
589 public void removeSessionLocked(MediaSessionRecord session) {
590 mSessions.remove(session);
591 RouteInfo route = session.getRoute();
592 if (route != null) {
593 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
594 if (provider != null) {
595 provider.removeSession(session);
596 }
597 }
598 updateInterestLocked();
599 }
600
601 public void dumpLocked(PrintWriter pw, String prefix) {
602 pw.println(prefix + "Record for user " + mUserId);
603 String indent = prefix + " ";
604 int size = mProviders.size();
605 pw.println(indent + size + " Providers:");
606 for (int i = 0; i < size; i++) {
607 mProviders.get(i).dump(pw, indent);
608 }
609 pw.println();
610 size = mSessions.size();
611 pw.println(indent + size + " Sessions:");
612 for (int i = 0; i < size; i++) {
613 // Just print the session info, the full session dump will
614 // already be in the list of all sessions.
615 pw.println(indent + mSessions.get(i).getSessionInfo());
616 }
617 }
618
619 public void updateInterestLocked() {
620 // TODO go through the sessions and build up the set of interfaces
621 // we're interested in. Update the provider watcher.
622 // For now, just express interest in all providers for the current
623 // user
624 boolean interested = mUserId == mCurrentUserId;
625 for (int i = mProviders.size() - 1; i >= 0; i--) {
626 mProviders.get(i).setInterested(interested);
627 }
628 }
629
630 private MediaRouteProviderProxy getProviderLocked(String providerId) {
631 for (int i = mProviders.size() - 1; i >= 0; i--) {
632 MediaRouteProviderProxy provider = mProviders.get(i);
633 if (TextUtils.equals(providerId, provider.getId())) {
634 return provider;
635 }
636 }
637 return null;
638 }
639
640 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
641 = new MediaRouteProviderWatcher.Callback() {
642 @Override
643 public void removeProvider(MediaRouteProviderProxy provider) {
644 synchronized (mLock) {
645 mProviders.remove(provider);
646 provider.setRoutesListener(null);
647 provider.setInterested(false);
648 }
649 }
650
651 @Override
652 public void addProvider(MediaRouteProviderProxy provider) {
653 synchronized (mLock) {
654 mProviders.add(provider);
655 provider.setRoutesListener(mRoutesCallback);
656 provider.setInterested(true);
657 }
658 }
659 };
660 }
661
RoboErik2e7a9162014-06-04 16:53:45 -0700662 final class SessionsListenerRecord implements IBinder.DeathRecipient {
663 private final IActiveSessionsListener mListener;
664 private final int mUserId;
665
666 public SessionsListenerRecord(IActiveSessionsListener listener, int userId) {
667 mListener = listener;
668 mUserId = userId;
669 }
670
671 @Override
672 public void binderDied() {
673 synchronized (mLock) {
674 mSessionsListeners.remove(this);
675 }
676 }
677 }
678
RoboErik07c70772014-03-20 13:33:52 -0700679 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700680 private static final String EXTRA_WAKELOCK_ACQUIRED =
681 "android.media.AudioService.WAKELOCK_ACQUIRED";
682 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
683
RoboErik9a9d0b52014-05-20 14:53:39 -0700684 private boolean mVoiceButtonDown = false;
685 private boolean mVoiceButtonHandled = false;
686
RoboErik07c70772014-03-20 13:33:52 -0700687 @Override
RoboErika5b02322014-05-07 17:05:49 -0700688 public ISession createSession(String packageName, ISessionCallback cb, String tag,
689 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800690 final int pid = Binder.getCallingPid();
691 final int uid = Binder.getCallingUid();
692 final long token = Binder.clearCallingIdentity();
693 try {
694 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700695 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
696 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800697 if (cb == null) {
698 throw new IllegalArgumentException("Controller callback cannot be null");
699 }
RoboErika5b02322014-05-07 17:05:49 -0700700 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
701 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700702 } finally {
703 Binder.restoreCallingIdentity(token);
704 }
705 }
706
707 @Override
RoboErika5b02322014-05-07 17:05:49 -0700708 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700709 final int pid = Binder.getCallingPid();
710 final int uid = Binder.getCallingUid();
711 final long token = Binder.clearCallingIdentity();
712
713 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700714 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700715 ArrayList<IBinder> binders = new ArrayList<IBinder>();
716 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700717 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700718 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700719 int size = records.size();
720 for (int i = 0; i < size; i++) {
721 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700722 }
723 }
724 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800725 } finally {
726 Binder.restoreCallingIdentity(token);
727 }
728 }
RoboErika278ea72014-04-24 14:49:01 -0700729
RoboErik2e7a9162014-06-04 16:53:45 -0700730 @Override
731 public void addSessionsListener(IActiveSessionsListener listener,
732 ComponentName componentName, int userId) throws RemoteException {
733 final int pid = Binder.getCallingPid();
734 final int uid = Binder.getCallingUid();
735 final long token = Binder.clearCallingIdentity();
736
737 try {
738 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
739 synchronized (mLock) {
740 int index = findIndexOfSessionsListenerLocked(listener);
741 if (index != -1) {
742 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
743 return;
744 }
745 SessionsListenerRecord record = new SessionsListenerRecord(listener,
746 resolvedUserId);
747 try {
748 listener.asBinder().linkToDeath(record, 0);
749 } catch (RemoteException e) {
750 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
751 return;
752 }
753 mSessionsListeners.add(record);
754 }
755 } finally {
756 Binder.restoreCallingIdentity(token);
757 }
758 }
759
760 @Override
761 public void removeSessionsListener(IActiveSessionsListener listener)
762 throws RemoteException {
763 synchronized (mLock) {
764 int index = findIndexOfSessionsListenerLocked(listener);
765 if (index != -1) {
766 SessionsListenerRecord record = mSessionsListeners.remove(index);
767 try {
768 record.mListener.asBinder().unlinkToDeath(record, 0);
769 } catch (Exception e) {
770 // ignore exceptions, the record is being removed
771 }
772 }
773 }
774 }
775
RoboErik8a2cfc32014-05-16 11:19:38 -0700776 /**
777 * Handles the dispatching of the media button events to one of the
778 * registered listeners, or if there was none, broadcast an
779 * ACTION_MEDIA_BUTTON intent to the rest of the system.
780 *
781 * @param keyEvent a non-null KeyEvent whose key code is one of the
782 * supported media buttons
783 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
784 * while this key event is dispatched.
785 */
786 @Override
787 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
788 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
789 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
790 return;
791 }
792 final int pid = Binder.getCallingPid();
793 final int uid = Binder.getCallingUid();
794 final long token = Binder.clearCallingIdentity();
795
796 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700797 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700798 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700799 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700800 if (isVoiceKey(keyEvent.getKeyCode())) {
801 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700802 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700803 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700804 }
805 }
806 } finally {
807 Binder.restoreCallingIdentity(token);
808 }
809 }
810
RoboErika278ea72014-04-24 14:49:01 -0700811 @Override
RoboErikb69ffd42014-05-30 14:57:59 -0700812 public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
813 throws RemoteException {
814 final int pid = Binder.getCallingPid();
815 final int uid = Binder.getCallingUid();
816 final long token = Binder.clearCallingIdentity();
817 try {
818 synchronized (mLock) {
819 MediaSessionRecord session = mPriorityStack
820 .getDefaultVolumeSession(mCurrentUserId);
821 dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
822 }
823 } finally {
824 Binder.restoreCallingIdentity(token);
825 }
826 }
827
828 @Override
RoboErika278ea72014-04-24 14:49:01 -0700829 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
830 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
831 != PackageManager.PERMISSION_GRANTED) {
832 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
833 + Binder.getCallingPid()
834 + ", uid=" + Binder.getCallingUid());
835 return;
836 }
837
838 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
839 pw.println();
840
841 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700842 pw.println("Session for calls:" + mPrioritySession);
843 if (mPrioritySession != null) {
844 mPrioritySession.dump(pw, "");
845 }
RoboErik4646d282014-05-13 10:13:04 -0700846 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700847 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700848 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700849 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700850 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700851 }
RoboErika5b02322014-05-07 17:05:49 -0700852 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700853
RoboErik4646d282014-05-13 10:13:04 -0700854 pw.println("User Records:");
855 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700856 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700857 UserRecord user = mUserRecords.get(i);
858 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700859 }
860 }
861 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700862
RoboErik2e7a9162014-06-04 16:53:45 -0700863 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
864 final int uid) {
865 String packageName = null;
866 if (componentName != null) {
867 // If they gave us a component name verify they own the
868 // package
869 packageName = componentName.getPackageName();
870 enforcePackageName(packageName, uid);
871 }
872 // Check that they can make calls on behalf of the user and
873 // get the final user id
874 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
875 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
876 // Check if they have the permissions or their component is
877 // enabled for the user they're calling from.
878 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
879 return resolvedUserId;
880 }
881
RoboErikb69ffd42014-05-30 14:57:59 -0700882 private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
883 MediaSessionRecord session) {
884 int direction = 0;
885 int steps = delta;
886 if (delta > 0) {
887 direction = 1;
888 } else if (delta < 0) {
889 direction = -1;
890 steps = -delta;
891 }
892 if (DEBUG) {
893 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
894 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
895 + ", suggestedStream=" + suggestedStream);
896
897 }
898 if (session == null) {
899 for (int i = 0; i < steps; i++) {
900 try {
901 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
902 flags, getContext().getOpPackageName());
903 } catch (RemoteException e) {
904 Log.e(TAG, "Error adjusting default volume.", e);
905 }
906 }
907 } else {
908 if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
909 for (int i = 0; i < steps; i++) {
910 try {
911 mAudioService.adjustSuggestedStreamVolume(direction,
912 session.getAudioStream(), flags,
913 getContext().getOpPackageName());
914 } catch (RemoteException e) {
915 Log.e(TAG, "Error adjusting volume for stream "
916 + session.getAudioStream(), e);
917 }
918 }
919 } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) {
920 session.adjustVolumeBy(delta);
921 }
922 }
923 }
924
RoboErik9a9d0b52014-05-20 14:53:39 -0700925 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
926 MediaSessionRecord session) {
927 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
928 // If the phone app has priority just give it the event
929 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
930 return;
931 }
932 int action = keyEvent.getAction();
933 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
934 if (action == KeyEvent.ACTION_DOWN) {
935 if (keyEvent.getRepeatCount() == 0) {
936 mVoiceButtonDown = true;
937 mVoiceButtonHandled = false;
938 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
939 mVoiceButtonHandled = true;
940 startVoiceInput(needWakeLock);
941 }
942 } else if (action == KeyEvent.ACTION_UP) {
943 if (mVoiceButtonDown) {
944 mVoiceButtonDown = false;
945 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
946 // Resend the down then send this event through
947 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
948 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
949 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
950 }
951 }
952 }
953 }
954
955 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
956 MediaSessionRecord session) {
957 if (session != null) {
958 if (DEBUG) {
959 Log.d(TAG, "Sending media key to " + session.getSessionInfo());
960 }
961 if (needWakeLock) {
962 mKeyEventReceiver.aquireWakeLockLocked();
963 }
964 // If we don't need a wakelock use -1 as the id so we
965 // won't release it later
966 session.sendMediaButton(keyEvent,
967 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
968 mKeyEventReceiver);
969 } else {
970 if (needWakeLock) {
971 mMediaEventWakeLock.acquire();
972 }
973 if (DEBUG) {
974 Log.d(TAG, "Sending media key ordered broadcast");
975 }
976 // Fallback to legacy behavior
977 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
978 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
979 if (needWakeLock) {
980 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
981 WAKELOCK_RELEASE_ON_FINISHED);
982 }
983 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
984 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
985 }
986 }
987
988 private void startVoiceInput(boolean needWakeLock) {
989 Intent voiceIntent = null;
990 // select which type of search to launch:
991 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
992 // - device locked or screen off: action is
993 // ACTION_VOICE_SEARCH_HANDS_FREE
994 // with EXTRA_SECURE set to true if the device is securely locked
995 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
996 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
997 if (!isLocked && pm.isScreenOn()) {
998 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
999 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1000 } else {
1001 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1002 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1003 isLocked && mKeyguardManager.isKeyguardSecure());
1004 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1005 }
1006 // start the search activity
1007 if (needWakeLock) {
1008 mMediaEventWakeLock.acquire();
1009 }
1010 try {
1011 if (voiceIntent != null) {
1012 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1013 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1014 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1015 }
1016 } catch (ActivityNotFoundException e) {
1017 Log.w(TAG, "No activity for search: " + e);
1018 } finally {
1019 if (needWakeLock) {
1020 mMediaEventWakeLock.release();
1021 }
1022 }
1023 }
1024
1025 private boolean isVoiceKey(int keyCode) {
1026 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
1027 }
1028
RoboErik418c10c2014-05-19 09:25:25 -07001029 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1030
1031 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
1032 private final Handler mHandler;
1033 private int mRefCount = 0;
1034 private int mLastTimeoutId = 0;
1035
1036 public KeyEventWakeLockReceiver(Handler handler) {
1037 super(handler);
1038 mHandler = handler;
1039 }
1040
1041 public void onTimeout() {
1042 synchronized (mLock) {
1043 if (mRefCount == 0) {
1044 // We've already released it, so just return
1045 return;
1046 }
1047 mLastTimeoutId++;
1048 mRefCount = 0;
1049 releaseWakeLockLocked();
1050 }
1051 }
1052
1053 public void aquireWakeLockLocked() {
1054 if (mRefCount == 0) {
1055 mMediaEventWakeLock.acquire();
1056 }
1057 mRefCount++;
1058 mHandler.removeCallbacks(this);
1059 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1060
1061 }
1062
1063 @Override
1064 public void run() {
1065 onTimeout();
1066 }
1067
RoboErik8a2cfc32014-05-16 11:19:38 -07001068 @Override
1069 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001070 if (resultCode < mLastTimeoutId) {
1071 // Ignore results from calls that were before the last
1072 // timeout, just in case.
1073 return;
1074 } else {
1075 synchronized (mLock) {
1076 if (mRefCount > 0) {
1077 mRefCount--;
1078 if (mRefCount == 0) {
1079 releaseWakeLockLocked();
1080 }
1081 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001082 }
1083 }
1084 }
RoboErik418c10c2014-05-19 09:25:25 -07001085
1086 private void releaseWakeLockLocked() {
1087 mMediaEventWakeLock.release();
1088 mHandler.removeCallbacks(this);
1089 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001090 };
1091
1092 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1093 @Override
1094 public void onReceive(Context context, Intent intent) {
1095 if (intent == null) {
1096 return;
1097 }
1098 Bundle extras = intent.getExtras();
1099 if (extras == null) {
1100 return;
1101 }
1102 synchronized (mLock) {
1103 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1104 && mMediaEventWakeLock.isHeld()) {
1105 mMediaEventWakeLock.release();
1106 }
1107 }
1108 }
1109 };
RoboErik01fe6612014-02-13 14:19:04 -08001110 }
1111
RoboErik2e7a9162014-06-04 16:53:45 -07001112 final class MessageHandler extends Handler {
1113 private static final int MSG_SESSIONS_CHANGED = 1;
1114
1115 @Override
1116 public void handleMessage(Message msg) {
1117 switch (msg.what) {
1118 case MSG_SESSIONS_CHANGED:
1119 pushSessionsChanged(msg.arg1);
1120 break;
1121 }
1122 }
1123
1124 public void post(int what, int arg1, int arg2) {
1125 obtainMessage(what, arg1, arg2).sendToTarget();
1126 }
1127 }
RoboErik01fe6612014-02-13 14:19:04 -08001128}