blob: ecdbb5e08fd7035dac95606579bbe5e655272b5a [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.AudioManager;
31import android.media.IAudioService;
RoboErik07c70772014-03-20 13:33:52 -070032import android.media.routeprovider.RouteRequest;
RoboErik2e7a9162014-06-04 16:53:45 -070033import android.media.session.IActiveSessionsListener;
RoboErik07c70772014-03-20 13:33:52 -070034import android.media.session.ISession;
35import android.media.session.ISessionCallback;
36import android.media.session.ISessionManager;
RoboErik2e7a9162014-06-04 16:53:45 -070037import android.media.session.MediaSessionToken;
RoboErik07c70772014-03-20 13:33:52 -070038import android.media.session.RouteInfo;
39import android.media.session.RouteOptions;
RoboErik42ea7ee2014-05-16 16:27:35 -070040import android.media.session.MediaSession;
RoboErik01fe6612014-02-13 14:19:04 -080041import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070042import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080043import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070044import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070045import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070046import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080047import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070048import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070049import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070050import android.os.UserHandle;
51import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070052import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080053import android.text.TextUtils;
54import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070055import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070056import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080057
58import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070059import com.android.server.Watchdog;
60import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080061
RoboErika278ea72014-04-24 14:49:01 -070062import java.io.FileDescriptor;
63import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080064import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070065import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080066
67/**
68 * System implementation of MediaSessionManager
69 */
RoboErika278ea72014-04-24 14:49:01 -070070public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080071 private static final String TAG = "MediaSessionService";
72 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
73
RoboErik418c10c2014-05-19 09:25:25 -070074 private static final int WAKELOCK_TIMEOUT = 5000;
75
RoboErik01fe6612014-02-13 14:19:04 -080076 private final SessionManagerImpl mSessionManagerImpl;
RoboErik4646d282014-05-13 10:13:04 -070077 // private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErika8f95142014-05-05 14:23:49 -070078 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080079
RoboErik4646d282014-05-13 10:13:04 -070080 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
81 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070082 private final ArrayList<SessionsListenerRecord> mSessionsListeners
83 = new ArrayList<SessionsListenerRecord>();
RoboErik4646d282014-05-13 10:13:04 -070084 // private final ArrayList<MediaRouteProviderProxy> mProviders
85 // = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080086 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070087 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070088 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080089
RoboErik9a9d0b52014-05-20 14:53:39 -070090 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070091 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070092 private ContentResolver mContentResolver;
RoboErik9a9d0b52014-05-20 14:53:39 -070093
RoboErike7880d82014-04-30 12:48:25 -070094 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070095 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070096
RoboErik07c70772014-03-20 13:33:52 -070097 // Used to keep track of the current request to show routes for a specific
98 // session so we drop late callbacks properly.
99 private int mShowRoutesRequestId = 0;
100
RoboErika5b02322014-05-07 17:05:49 -0700101 // TODO refactor to have per user state for providers. See
102 // MediaRouterService for an example
RoboErik07c70772014-03-20 13:33:52 -0700103
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
RoboErika278ea72014-04-24 14:49:01 -0700228 @Override
RoboErik4646d282014-05-13 10:13:04 -0700229 public void onStartUser(int userHandle) {
230 updateUser();
231 }
232
233 @Override
234 public void onSwitchUser(int userHandle) {
235 updateUser();
236 }
237
238 @Override
239 public void onStopUser(int userHandle) {
240 synchronized (mLock) {
241 UserRecord user = mUserRecords.get(userHandle);
242 if (user != null) {
243 destroyUserLocked(user);
244 }
245 }
246 }
247
248 @Override
RoboErika278ea72014-04-24 14:49:01 -0700249 public void monitor() {
250 synchronized (mLock) {
251 // Check for deadlock
252 }
253 }
254
RoboErik4646d282014-05-13 10:13:04 -0700255 protected void enforcePhoneStatePermission(int pid, int uid) {
256 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
257 != PackageManager.PERMISSION_GRANTED) {
258 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
259 }
260 }
261
RoboErik01fe6612014-02-13 14:19:04 -0800262 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700263 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800264 destroySessionLocked(session);
265 }
266 }
267
268 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700269 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800270 destroySessionLocked(session);
271 }
272 }
273
RoboErik4646d282014-05-13 10:13:04 -0700274 private void updateUser() {
275 synchronized (mLock) {
276 int userId = ActivityManager.getCurrentUser();
277 if (mCurrentUserId != userId) {
278 final int oldUserId = mCurrentUserId;
279 mCurrentUserId = userId; // do this first
280
281 UserRecord oldUser = mUserRecords.get(oldUserId);
282 if (oldUser != null) {
283 oldUser.stopLocked();
284 }
285
286 UserRecord newUser = getOrCreateUser(userId);
287 newUser.startLocked();
288 }
289 }
290 }
291
292 /**
293 * Stop the user and unbind from everything.
294 *
295 * @param user The user to dispose of
296 */
297 private void destroyUserLocked(UserRecord user) {
298 user.stopLocked();
299 user.destroyLocked();
300 mUserRecords.remove(user.mUserId);
301 }
302
303 /*
304 * When a session is removed several things need to happen.
305 * 1. We need to remove it from the relevant user.
306 * 2. We need to remove it from the priority stack.
307 * 3. We need to remove it from all sessions.
308 * 4. If this is the system priority session we need to clear it.
309 * 5. We need to unlink to death from the cb binder
310 * 6. We need to tell the session to do any final cleanup (onDestroy)
311 */
RoboErik01fe6612014-02-13 14:19:04 -0800312 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700313 int userId = session.getUserId();
314 UserRecord user = mUserRecords.get(userId);
315 if (user != null) {
316 user.removeSessionLocked(session);
317 }
318
RoboErika8f95142014-05-05 14:23:49 -0700319 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700320 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700321 if (session == mPrioritySession) {
322 mPrioritySession = null;
323 }
RoboErik4646d282014-05-13 10:13:04 -0700324
325 try {
326 session.getCallback().asBinder().unlinkToDeath(session, 0);
327 } catch (Exception e) {
328 // ignore exceptions while destroying a session.
329 }
330 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700331
332 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800333 }
334
335 private void enforcePackageName(String packageName, int uid) {
336 if (TextUtils.isEmpty(packageName)) {
337 throw new IllegalArgumentException("packageName may not be empty");
338 }
339 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
340 final int packageCount = packages.length;
341 for (int i = 0; i < packageCount; i++) {
342 if (packageName.equals(packages[i])) {
343 return;
344 }
345 }
346 throw new IllegalArgumentException("packageName is not owned by the calling process");
347 }
348
RoboErike7880d82014-04-30 12:48:25 -0700349 /**
350 * Checks a caller's authorization to register an IRemoteControlDisplay.
351 * Authorization is granted if one of the following is true:
352 * <ul>
353 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
354 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700355 * <li>the caller's listener is one of the enabled notification listeners
356 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700357 * </ul>
358 */
RoboErika5b02322014-05-07 17:05:49 -0700359 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
360 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700361 if (getContext()
362 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
363 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700364 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
365 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700366 throw new SecurityException("Missing permission to control media.");
367 }
368 }
369
RoboErika5b02322014-05-07 17:05:49 -0700370 /**
371 * This checks if the component is an enabled notification listener for the
372 * specified user. Enabled components may only operate on behalf of the user
373 * they're running as.
374 *
375 * @param compName The component that is enabled.
376 * @param userId The user id of the caller.
377 * @param forUserId The user id they're making the request on behalf of.
378 * @return True if the component is enabled, false otherwise
379 */
380 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
381 int forUserId) {
382 if (userId != forUserId) {
383 // You may not access another user's content as an enabled listener.
384 return false;
385 }
RoboErike7880d82014-04-30 12:48:25 -0700386 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700387 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700388 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700389 userId);
RoboErike7880d82014-04-30 12:48:25 -0700390 if (enabledNotifListeners != null) {
391 final String[] components = enabledNotifListeners.split(":");
392 for (int i = 0; i < components.length; i++) {
393 final ComponentName component =
394 ComponentName.unflattenFromString(components[i]);
395 if (component != null) {
396 if (compName.equals(component)) {
397 if (DEBUG) {
398 Log.d(TAG, "ok to get sessions: " + component +
399 " is authorized notification listener");
400 }
401 return true;
402 }
403 }
404 }
405 }
406 if (DEBUG) {
407 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700408 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700409 }
410 }
411 return false;
412 }
413
RoboErika5b02322014-05-07 17:05:49 -0700414 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700415 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800416 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700417 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800418 }
419 }
420
RoboErik4646d282014-05-13 10:13:04 -0700421 /*
422 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700423 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700424 * 2. It needs to be added to all sessions.
425 * 3. It needs to be added to the priority stack.
426 * 4. It needs to be added to the relevant user record.
427 */
RoboErika5b02322014-05-07 17:05:49 -0700428 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
429 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700430
RoboErika5b02322014-05-07 17:05:49 -0700431 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
432 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800433 try {
434 cb.asBinder().linkToDeath(session, 0);
435 } catch (RemoteException e) {
436 throw new RuntimeException("Media Session owner died prematurely.", e);
437 }
RoboErik4646d282014-05-13 10:13:04 -0700438
439 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700440 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700441
442 UserRecord user = getOrCreateUser(userId);
443 user.addSessionLocked(session);
444
RoboErik2e7a9162014-06-04 16:53:45 -0700445 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
446
RoboErik01fe6612014-02-13 14:19:04 -0800447 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700448 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800449 }
450 return session;
451 }
452
RoboErik4646d282014-05-13 10:13:04 -0700453 private UserRecord getOrCreateUser(int userId) {
454 UserRecord user = mUserRecords.get(userId);
455 if (user == null) {
456 user = new UserRecord(getContext(), userId);
457 mUserRecords.put(userId, user);
458 }
459 return user;
460 }
461
RoboErika8f95142014-05-05 14:23:49 -0700462 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700463 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
464 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700465 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
466 return i;
467 }
468 }
469 return -1;
470 }
471
RoboErik2e7a9162014-06-04 16:53:45 -0700472 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
473 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
474 if (mSessionsListeners.get(i).mListener == listener) {
475 return i;
476 }
477 }
478 return -1;
479 }
480
RoboErike7880d82014-04-30 12:48:25 -0700481 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700482 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700483 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700484 }
485
RoboErik2e7a9162014-06-04 16:53:45 -0700486 private void pushSessionsChanged(int userId) {
487 synchronized (mLock) {
488 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
489 int size = records.size();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700490 if (size > 0) {
491 persistMediaButtonReceiverLocked(records.get(0));
492 }
RoboErik2e7a9162014-06-04 16:53:45 -0700493 ArrayList<MediaSessionToken> tokens = new ArrayList<MediaSessionToken>();
494 for (int i = 0; i < size; i++) {
495 tokens.add(new MediaSessionToken(records.get(i).getControllerBinder()));
496 }
497 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
498 SessionsListenerRecord record = mSessionsListeners.get(i);
499 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
500 try {
501 record.mListener.onActiveSessionsChanged(tokens);
502 } catch (RemoteException e) {
503 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
504 e);
505 mSessionsListeners.remove(i);
506 }
507 }
508 }
509 }
510 }
511
RoboErik6f0e4dd2014-06-17 16:56:27 -0700512 private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
513 ComponentName receiver = record.getMediaButtonReceiver();
514 if (receiver != null) {
515 Settings.System.putStringForUser(mContentResolver,
516 Settings.System.MEDIA_BUTTON_RECEIVER,
517 receiver == null ? "" : receiver.flattenToString(),
518 UserHandle.USER_CURRENT);
519 }
520 }
521
RoboErik07c70772014-03-20 13:33:52 -0700522 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
523 = new MediaRouteProviderProxy.RoutesListener() {
524 @Override
525 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
526 int reqId) {
527 // TODO for now select the first route to test, eventually add the
528 // new routes to the dialog if it is still open
529 synchronized (mLock) {
530 int index = findIndexOfSessionForIdLocked(sessionId);
531 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700532 MediaSessionRecord record = mAllSessions.get(index);
533 RouteInfo route = routes.get(0);
534 record.selectRoute(route);
535 UserRecord user = mUserRecords.get(record.getUserId());
536 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
537 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700538 }
539 }
540 }
541
542 @Override
543 public void onRouteConnected(String sessionId, RouteInfo route,
544 RouteRequest options, RouteConnectionRecord connection) {
545 synchronized (mLock) {
546 int index = findIndexOfSessionForIdLocked(sessionId);
547 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700548 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700549 session.setRouteConnected(route, options.getConnectionOptions(), connection);
550 }
551 }
552 }
553 };
554
RoboErik4646d282014-05-13 10:13:04 -0700555 /**
556 * Information about a particular user. The contents of this object is
557 * guarded by mLock.
558 */
559 final class UserRecord {
560 private final int mUserId;
561 private final MediaRouteProviderWatcher mRouteProviderWatcher;
562 private final ArrayList<MediaRouteProviderProxy> mProviders
563 = new ArrayList<MediaRouteProviderProxy>();
564 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
565
566 public UserRecord(Context context, int userId) {
567 mUserId = userId;
568 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
569 mProviderWatcherCallback, mHandler, userId);
570 }
571
572 public void startLocked() {
573 mRouteProviderWatcher.start();
574 }
575
576 public void stopLocked() {
577 mRouteProviderWatcher.stop();
578 updateInterestLocked();
579 }
580
581 public void destroyLocked() {
582 for (int i = mSessions.size() - 1; i >= 0; i--) {
583 MediaSessionRecord session = mSessions.get(i);
584 MediaSessionService.this.destroySessionLocked(session);
585 if (session.isConnected()) {
RoboErik42ea7ee2014-05-16 16:27:35 -0700586 session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
RoboErik4646d282014-05-13 10:13:04 -0700587 }
588 }
589 }
590
591 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
592 return mProviders;
593 }
594
595 public ArrayList<MediaSessionRecord> getSessionsLocked() {
596 return mSessions;
597 }
598
599 public void addSessionLocked(MediaSessionRecord session) {
600 mSessions.add(session);
601 updateInterestLocked();
602 }
603
604 public void removeSessionLocked(MediaSessionRecord session) {
605 mSessions.remove(session);
606 RouteInfo route = session.getRoute();
607 if (route != null) {
608 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
609 if (provider != null) {
610 provider.removeSession(session);
611 }
612 }
613 updateInterestLocked();
614 }
615
616 public void dumpLocked(PrintWriter pw, String prefix) {
617 pw.println(prefix + "Record for user " + mUserId);
618 String indent = prefix + " ";
619 int size = mProviders.size();
620 pw.println(indent + size + " Providers:");
621 for (int i = 0; i < size; i++) {
622 mProviders.get(i).dump(pw, indent);
623 }
624 pw.println();
625 size = mSessions.size();
626 pw.println(indent + size + " Sessions:");
627 for (int i = 0; i < size; i++) {
628 // Just print the session info, the full session dump will
629 // already be in the list of all sessions.
630 pw.println(indent + mSessions.get(i).getSessionInfo());
631 }
632 }
633
634 public void updateInterestLocked() {
635 // TODO go through the sessions and build up the set of interfaces
636 // we're interested in. Update the provider watcher.
637 // For now, just express interest in all providers for the current
638 // user
639 boolean interested = mUserId == mCurrentUserId;
640 for (int i = mProviders.size() - 1; i >= 0; i--) {
641 mProviders.get(i).setInterested(interested);
642 }
643 }
644
645 private MediaRouteProviderProxy getProviderLocked(String providerId) {
646 for (int i = mProviders.size() - 1; i >= 0; i--) {
647 MediaRouteProviderProxy provider = mProviders.get(i);
648 if (TextUtils.equals(providerId, provider.getId())) {
649 return provider;
650 }
651 }
652 return null;
653 }
654
655 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
656 = new MediaRouteProviderWatcher.Callback() {
657 @Override
658 public void removeProvider(MediaRouteProviderProxy provider) {
659 synchronized (mLock) {
660 mProviders.remove(provider);
661 provider.setRoutesListener(null);
662 provider.setInterested(false);
663 }
664 }
665
666 @Override
667 public void addProvider(MediaRouteProviderProxy provider) {
668 synchronized (mLock) {
669 mProviders.add(provider);
670 provider.setRoutesListener(mRoutesCallback);
671 provider.setInterested(true);
672 }
673 }
674 };
675 }
676
RoboErik2e7a9162014-06-04 16:53:45 -0700677 final class SessionsListenerRecord implements IBinder.DeathRecipient {
678 private final IActiveSessionsListener mListener;
679 private final int mUserId;
680
681 public SessionsListenerRecord(IActiveSessionsListener listener, int userId) {
682 mListener = listener;
683 mUserId = userId;
684 }
685
686 @Override
687 public void binderDied() {
688 synchronized (mLock) {
689 mSessionsListeners.remove(this);
690 }
691 }
692 }
693
RoboErik07c70772014-03-20 13:33:52 -0700694 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700695 private static final String EXTRA_WAKELOCK_ACQUIRED =
696 "android.media.AudioService.WAKELOCK_ACQUIRED";
697 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
698
RoboErik9a9d0b52014-05-20 14:53:39 -0700699 private boolean mVoiceButtonDown = false;
700 private boolean mVoiceButtonHandled = false;
701
RoboErik07c70772014-03-20 13:33:52 -0700702 @Override
RoboErika5b02322014-05-07 17:05:49 -0700703 public ISession createSession(String packageName, ISessionCallback cb, String tag,
704 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800705 final int pid = Binder.getCallingPid();
706 final int uid = Binder.getCallingUid();
707 final long token = Binder.clearCallingIdentity();
708 try {
709 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700710 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
711 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800712 if (cb == null) {
713 throw new IllegalArgumentException("Controller callback cannot be null");
714 }
RoboErika5b02322014-05-07 17:05:49 -0700715 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
716 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700717 } finally {
718 Binder.restoreCallingIdentity(token);
719 }
720 }
721
722 @Override
RoboErika5b02322014-05-07 17:05:49 -0700723 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700724 final int pid = Binder.getCallingPid();
725 final int uid = Binder.getCallingUid();
726 final long token = Binder.clearCallingIdentity();
727
728 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700729 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700730 ArrayList<IBinder> binders = new ArrayList<IBinder>();
731 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700732 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700733 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700734 int size = records.size();
735 for (int i = 0; i < size; i++) {
736 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700737 }
738 }
739 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800740 } finally {
741 Binder.restoreCallingIdentity(token);
742 }
743 }
RoboErika278ea72014-04-24 14:49:01 -0700744
RoboErik2e7a9162014-06-04 16:53:45 -0700745 @Override
746 public void addSessionsListener(IActiveSessionsListener listener,
747 ComponentName componentName, int userId) throws RemoteException {
748 final int pid = Binder.getCallingPid();
749 final int uid = Binder.getCallingUid();
750 final long token = Binder.clearCallingIdentity();
751
752 try {
753 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
754 synchronized (mLock) {
755 int index = findIndexOfSessionsListenerLocked(listener);
756 if (index != -1) {
757 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
758 return;
759 }
760 SessionsListenerRecord record = new SessionsListenerRecord(listener,
761 resolvedUserId);
762 try {
763 listener.asBinder().linkToDeath(record, 0);
764 } catch (RemoteException e) {
765 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
766 return;
767 }
768 mSessionsListeners.add(record);
769 }
770 } finally {
771 Binder.restoreCallingIdentity(token);
772 }
773 }
774
775 @Override
776 public void removeSessionsListener(IActiveSessionsListener listener)
777 throws RemoteException {
778 synchronized (mLock) {
779 int index = findIndexOfSessionsListenerLocked(listener);
780 if (index != -1) {
781 SessionsListenerRecord record = mSessionsListeners.remove(index);
782 try {
783 record.mListener.asBinder().unlinkToDeath(record, 0);
784 } catch (Exception e) {
785 // ignore exceptions, the record is being removed
786 }
787 }
788 }
789 }
790
RoboErik8a2cfc32014-05-16 11:19:38 -0700791 /**
792 * Handles the dispatching of the media button events to one of the
793 * registered listeners, or if there was none, broadcast an
794 * ACTION_MEDIA_BUTTON intent to the rest of the system.
795 *
796 * @param keyEvent a non-null KeyEvent whose key code is one of the
797 * supported media buttons
798 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
799 * while this key event is dispatched.
800 */
801 @Override
802 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
803 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
804 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
805 return;
806 }
807 final int pid = Binder.getCallingPid();
808 final int uid = Binder.getCallingUid();
809 final long token = Binder.clearCallingIdentity();
810
811 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700812 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700813 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700814 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700815 if (isVoiceKey(keyEvent.getKeyCode())) {
816 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700817 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700818 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700819 }
820 }
821 } finally {
822 Binder.restoreCallingIdentity(token);
823 }
824 }
825
RoboErika278ea72014-04-24 14:49:01 -0700826 @Override
RoboErikb69ffd42014-05-30 14:57:59 -0700827 public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
828 throws RemoteException {
829 final int pid = Binder.getCallingPid();
830 final int uid = Binder.getCallingUid();
831 final long token = Binder.clearCallingIdentity();
832 try {
833 synchronized (mLock) {
834 MediaSessionRecord session = mPriorityStack
835 .getDefaultVolumeSession(mCurrentUserId);
836 dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
837 }
838 } finally {
839 Binder.restoreCallingIdentity(token);
840 }
841 }
842
843 @Override
RoboErika278ea72014-04-24 14:49:01 -0700844 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
845 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
846 != PackageManager.PERMISSION_GRANTED) {
847 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
848 + Binder.getCallingPid()
849 + ", uid=" + Binder.getCallingUid());
850 return;
851 }
852
853 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
854 pw.println();
855
856 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700857 pw.println("Session for calls:" + mPrioritySession);
858 if (mPrioritySession != null) {
859 mPrioritySession.dump(pw, "");
860 }
RoboErik4646d282014-05-13 10:13:04 -0700861 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700862 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700863 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700864 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700865 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700866 }
RoboErika5b02322014-05-07 17:05:49 -0700867 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700868
RoboErik4646d282014-05-13 10:13:04 -0700869 pw.println("User Records:");
870 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700871 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700872 UserRecord user = mUserRecords.get(i);
873 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700874 }
875 }
876 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700877
RoboErik2e7a9162014-06-04 16:53:45 -0700878 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
879 final int uid) {
880 String packageName = null;
881 if (componentName != null) {
882 // If they gave us a component name verify they own the
883 // package
884 packageName = componentName.getPackageName();
885 enforcePackageName(packageName, uid);
886 }
887 // Check that they can make calls on behalf of the user and
888 // get the final user id
889 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
890 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
891 // Check if they have the permissions or their component is
892 // enabled for the user they're calling from.
893 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
894 return resolvedUserId;
895 }
896
RoboErikb69ffd42014-05-30 14:57:59 -0700897 private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
898 MediaSessionRecord session) {
899 int direction = 0;
900 int steps = delta;
901 if (delta > 0) {
902 direction = 1;
903 } else if (delta < 0) {
904 direction = -1;
905 steps = -delta;
906 }
907 if (DEBUG) {
908 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
909 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
910 + ", suggestedStream=" + suggestedStream);
911
912 }
913 if (session == null) {
RoboErik0791e172014-06-08 10:52:32 -0700914 try {
915 if (delta == 0) {
916 mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags,
917 getContext().getOpPackageName());
918 } else {
919 for (int i = 0; i < steps; i++) {
920 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
921 flags, getContext().getOpPackageName());
922 }
RoboErikb69ffd42014-05-30 14:57:59 -0700923 }
RoboErik0791e172014-06-08 10:52:32 -0700924 } catch (RemoteException e) {
925 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700926 }
927 } else {
928 if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
RoboErik0791e172014-06-08 10:52:32 -0700929 try {
930 if (delta == 0) {
931 mAudioService.adjustSuggestedStreamVolume(delta,
RoboErikb69ffd42014-05-30 14:57:59 -0700932 session.getAudioStream(), flags,
933 getContext().getOpPackageName());
RoboErik0791e172014-06-08 10:52:32 -0700934 } else {
935 for (int i = 0; i < steps; i++) {
936 mAudioService.adjustSuggestedStreamVolume(direction,
937 session.getAudioStream(), flags,
938 getContext().getOpPackageName());
939 }
RoboErikb69ffd42014-05-30 14:57:59 -0700940 }
RoboErik0791e172014-06-08 10:52:32 -0700941 } catch (RemoteException e) {
942 Log.e(TAG, "Error adjusting volume for stream "
943 + session.getAudioStream(), e);
RoboErikb69ffd42014-05-30 14:57:59 -0700944 }
945 } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) {
946 session.adjustVolumeBy(delta);
947 }
948 }
949 }
950
RoboErik9a9d0b52014-05-20 14:53:39 -0700951 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
952 MediaSessionRecord session) {
953 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
954 // If the phone app has priority just give it the event
955 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
956 return;
957 }
958 int action = keyEvent.getAction();
959 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
960 if (action == KeyEvent.ACTION_DOWN) {
961 if (keyEvent.getRepeatCount() == 0) {
962 mVoiceButtonDown = true;
963 mVoiceButtonHandled = false;
964 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
965 mVoiceButtonHandled = true;
966 startVoiceInput(needWakeLock);
967 }
968 } else if (action == KeyEvent.ACTION_UP) {
969 if (mVoiceButtonDown) {
970 mVoiceButtonDown = false;
971 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
972 // Resend the down then send this event through
973 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
974 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
975 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
976 }
977 }
978 }
979 }
980
981 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
982 MediaSessionRecord session) {
983 if (session != null) {
984 if (DEBUG) {
985 Log.d(TAG, "Sending media key to " + session.getSessionInfo());
986 }
987 if (needWakeLock) {
988 mKeyEventReceiver.aquireWakeLockLocked();
989 }
990 // If we don't need a wakelock use -1 as the id so we
991 // won't release it later
992 session.sendMediaButton(keyEvent,
993 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
994 mKeyEventReceiver);
995 } else {
996 if (needWakeLock) {
997 mMediaEventWakeLock.acquire();
998 }
999 if (DEBUG) {
1000 Log.d(TAG, "Sending media key ordered broadcast");
1001 }
1002 // Fallback to legacy behavior
1003 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
1004 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1005 if (needWakeLock) {
1006 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
1007 WAKELOCK_RELEASE_ON_FINISHED);
1008 }
1009 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
1010 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
1011 }
1012 }
1013
1014 private void startVoiceInput(boolean needWakeLock) {
1015 Intent voiceIntent = null;
1016 // select which type of search to launch:
1017 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1018 // - device locked or screen off: action is
1019 // ACTION_VOICE_SEARCH_HANDS_FREE
1020 // with EXTRA_SECURE set to true if the device is securely locked
1021 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1022 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1023 if (!isLocked && pm.isScreenOn()) {
1024 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1025 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1026 } else {
1027 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1028 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1029 isLocked && mKeyguardManager.isKeyguardSecure());
1030 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1031 }
1032 // start the search activity
1033 if (needWakeLock) {
1034 mMediaEventWakeLock.acquire();
1035 }
1036 try {
1037 if (voiceIntent != null) {
1038 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1039 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1040 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1041 }
1042 } catch (ActivityNotFoundException e) {
1043 Log.w(TAG, "No activity for search: " + e);
1044 } finally {
1045 if (needWakeLock) {
1046 mMediaEventWakeLock.release();
1047 }
1048 }
1049 }
1050
1051 private boolean isVoiceKey(int keyCode) {
1052 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
1053 }
1054
RoboErik418c10c2014-05-19 09:25:25 -07001055 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1056
1057 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
1058 private final Handler mHandler;
1059 private int mRefCount = 0;
1060 private int mLastTimeoutId = 0;
1061
1062 public KeyEventWakeLockReceiver(Handler handler) {
1063 super(handler);
1064 mHandler = handler;
1065 }
1066
1067 public void onTimeout() {
1068 synchronized (mLock) {
1069 if (mRefCount == 0) {
1070 // We've already released it, so just return
1071 return;
1072 }
1073 mLastTimeoutId++;
1074 mRefCount = 0;
1075 releaseWakeLockLocked();
1076 }
1077 }
1078
1079 public void aquireWakeLockLocked() {
1080 if (mRefCount == 0) {
1081 mMediaEventWakeLock.acquire();
1082 }
1083 mRefCount++;
1084 mHandler.removeCallbacks(this);
1085 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1086
1087 }
1088
1089 @Override
1090 public void run() {
1091 onTimeout();
1092 }
1093
RoboErik8a2cfc32014-05-16 11:19:38 -07001094 @Override
1095 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001096 if (resultCode < mLastTimeoutId) {
1097 // Ignore results from calls that were before the last
1098 // timeout, just in case.
1099 return;
1100 } else {
1101 synchronized (mLock) {
1102 if (mRefCount > 0) {
1103 mRefCount--;
1104 if (mRefCount == 0) {
1105 releaseWakeLockLocked();
1106 }
1107 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001108 }
1109 }
1110 }
RoboErik418c10c2014-05-19 09:25:25 -07001111
1112 private void releaseWakeLockLocked() {
1113 mMediaEventWakeLock.release();
1114 mHandler.removeCallbacks(this);
1115 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001116 };
1117
1118 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1119 @Override
1120 public void onReceive(Context context, Intent intent) {
1121 if (intent == null) {
1122 return;
1123 }
1124 Bundle extras = intent.getExtras();
1125 if (extras == null) {
1126 return;
1127 }
1128 synchronized (mLock) {
1129 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1130 && mMediaEventWakeLock.isHeld()) {
1131 mMediaEventWakeLock.release();
1132 }
1133 }
1134 }
1135 };
RoboErik01fe6612014-02-13 14:19:04 -08001136 }
1137
RoboErik2e7a9162014-06-04 16:53:45 -07001138 final class MessageHandler extends Handler {
1139 private static final int MSG_SESSIONS_CHANGED = 1;
1140
1141 @Override
1142 public void handleMessage(Message msg) {
1143 switch (msg.what) {
1144 case MSG_SESSIONS_CHANGED:
1145 pushSessionsChanged(msg.arg1);
1146 break;
1147 }
1148 }
1149
1150 public void post(int what, int arg1, int arg2) {
1151 obtainMessage(what, arg1, arg2).sendToTarget();
1152 }
1153 }
RoboErik01fe6612014-02-13 14:19:04 -08001154}