blob: 310f3e998127577e8442ec753d20fe915961a291 [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;
RoboErik3c45c292014-07-08 16:47:31 -070030import android.media.AudioManager;
RoboErikb69ffd42014-05-30 14:57:59 -070031import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070032import android.media.IRemoteVolumeController;
RoboErik07c70772014-03-20 13:33:52 -070033import android.media.routeprovider.RouteRequest;
RoboErik2e7a9162014-06-04 16:53:45 -070034import android.media.session.IActiveSessionsListener;
RoboErik07c70772014-03-20 13:33:52 -070035import android.media.session.ISession;
36import android.media.session.ISessionCallback;
37import android.media.session.ISessionManager;
Jeff Browndba34ba2014-06-24 20:46:03 -070038import android.media.session.MediaSession;
RoboErik07c70772014-03-20 13:33:52 -070039import android.media.session.RouteInfo;
40import android.media.session.RouteOptions;
RoboErik01fe6612014-02-13 14:19:04 -080041import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070042import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080043import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070044import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070045import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070046import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080047import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070048import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070049import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070050import android.os.UserHandle;
51import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070052import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080053import android.text.TextUtils;
54import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070055import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070056import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080057
58import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070059import com.android.server.Watchdog;
60import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080061
RoboErika278ea72014-04-24 14:49:01 -070062import java.io.FileDescriptor;
63import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080064import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070065import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080066
67/**
68 * System implementation of MediaSessionManager
69 */
RoboErika278ea72014-04-24 14:49:01 -070070public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080071 private static final String TAG = "MediaSessionService";
72 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
73
RoboErik418c10c2014-05-19 09:25:25 -070074 private static final int WAKELOCK_TIMEOUT = 5000;
75
RoboErik01fe6612014-02-13 14:19:04 -080076 private final SessionManagerImpl mSessionManagerImpl;
RoboErik4646d282014-05-13 10:13:04 -070077 // private final MediaRouteProviderWatcher mRouteProviderWatcher;
RoboErika8f95142014-05-05 14:23:49 -070078 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080079
RoboErik4646d282014-05-13 10:13:04 -070080 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
81 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070082 private final ArrayList<SessionsListenerRecord> mSessionsListeners
83 = new ArrayList<SessionsListenerRecord>();
RoboErik4646d282014-05-13 10:13:04 -070084 // private final ArrayList<MediaRouteProviderProxy> mProviders
85 // = new ArrayList<MediaRouteProviderProxy>();
RoboErik01fe6612014-02-13 14:19:04 -080086 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070087 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070088 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080089
RoboErik9a9d0b52014-05-20 14:53:39 -070090 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070091 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070092 private ContentResolver mContentResolver;
RoboErik9a9d0b52014-05-20 14:53:39 -070093
RoboErike7880d82014-04-30 12:48:25 -070094 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070095 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070096
RoboErik07c70772014-03-20 13:33:52 -070097 // Used to keep track of the current request to show routes for a specific
98 // session so we drop late callbacks properly.
99 private int mShowRoutesRequestId = 0;
100
RoboErik19c95182014-06-23 15:38:48 -0700101 // Used to notify system UI when remote volume was changed. TODO find a
102 // better way to handle this.
103 private IRemoteVolumeController mRvc;
104
RoboErik01fe6612014-02-13 14:19:04 -0800105 public MediaSessionService(Context context) {
106 super(context);
107 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700108 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700109 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
110 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -0800111 }
112
113 @Override
114 public void onStart() {
115 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700116 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700117 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700118 mKeyguardManager =
119 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700120 mAudioService = getAudioService();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700121 mContentResolver = getContext().getContentResolver();
RoboErikb69ffd42014-05-30 14:57:59 -0700122 }
123
124 private IAudioService getAudioService() {
125 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
126 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700127 }
128
129 /**
130 * Should trigger showing the Media route picker dialog. Right now it just
131 * kicks off a query to all the providers to get routes.
132 *
133 * @param record The session to show the picker for.
134 */
135 public void showRoutePickerForSession(MediaSessionRecord record) {
136 // TODO for now just toggle the route to test (we will only have one
137 // match for now)
RoboErik4646d282014-05-13 10:13:04 -0700138 synchronized (mLock) {
139 if (!mAllSessions.contains(record)) {
140 Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
141 return;
142 }
143 RouteInfo current = record.getRoute();
144 UserRecord user = mUserRecords.get(record.getUserId());
145 if (current != null) {
146 // For now send null to mean the local route
147 MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
148 if (proxy != null) {
149 proxy.removeSession(record);
150 }
151 record.selectRoute(null);
152 return;
153 }
154 ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
155 mShowRoutesRequestId++;
156 for (int i = providers.size() - 1; i >= 0; i--) {
157 MediaRouteProviderProxy provider = providers.get(i);
158 provider.getRoutes(record, mShowRoutesRequestId);
159 }
RoboErik07c70772014-03-20 13:33:52 -0700160 }
161 }
162
163 /**
164 * Connect a session to the given route.
165 *
166 * @param session The session to connect.
167 * @param route The route to connect to.
168 * @param options The options to use for the connection.
169 */
170 public void connectToRoute(MediaSessionRecord session, RouteInfo route,
171 RouteOptions options) {
172 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700173 if (!mAllSessions.contains(session)) {
174 Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
175 return;
176 }
177 UserRecord user = mUserRecords.get(session.getUserId());
178 if (user == null) {
179 Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
180 return;
181 }
182 MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
RoboErik07c70772014-03-20 13:33:52 -0700183 if (proxy == null) {
184 Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
185 return;
186 }
187 RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
RoboErik07c70772014-03-20 13:33:52 -0700188 proxy.connectToRoute(session, route, request);
189 }
RoboErik01fe6612014-02-13 14:19:04 -0800190 }
191
RoboErika8f95142014-05-05 14:23:49 -0700192 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700193 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700194 if (!mAllSessions.contains(record)) {
195 Log.d(TAG, "Unknown session updated. Ignoring.");
196 return;
197 }
RoboErika8f95142014-05-05 14:23:49 -0700198 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700199 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700200 if (record.isActive()) {
201 if (mPrioritySession != null) {
202 Log.w(TAG, "Replacing existing priority session with a new session");
203 }
204 mPrioritySession = record;
205 } else {
206 if (mPrioritySession == record) {
207 mPrioritySession = null;
208 }
RoboErike7880d82014-04-30 12:48:25 -0700209 }
RoboErike7880d82014-04-30 12:48:25 -0700210 }
211 }
RoboErik2e7a9162014-06-04 16:53:45 -0700212 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700213 }
214
RoboErika8f95142014-05-05 14:23:49 -0700215 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700216 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700217 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700218 if (!mAllSessions.contains(record)) {
219 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
220 return;
221 }
RoboErik2e7a9162014-06-04 16:53:45 -0700222 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
223 }
224 if (updateSessions) {
225 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700226 }
227 }
228
RoboErik19c95182014-06-23 15:38:48 -0700229 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
230 synchronized (mLock) {
231 if (!mAllSessions.contains(record)) {
232 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
233 return;
234 }
235 pushRemoteVolumeUpdateLocked(record.getUserId());
236 }
237 }
238
RoboErika278ea72014-04-24 14:49:01 -0700239 @Override
RoboErik4646d282014-05-13 10:13:04 -0700240 public void onStartUser(int userHandle) {
241 updateUser();
242 }
243
244 @Override
245 public void onSwitchUser(int userHandle) {
246 updateUser();
247 }
248
249 @Override
250 public void onStopUser(int userHandle) {
251 synchronized (mLock) {
252 UserRecord user = mUserRecords.get(userHandle);
253 if (user != null) {
254 destroyUserLocked(user);
255 }
256 }
257 }
258
259 @Override
RoboErika278ea72014-04-24 14:49:01 -0700260 public void monitor() {
261 synchronized (mLock) {
262 // Check for deadlock
263 }
264 }
265
RoboErik4646d282014-05-13 10:13:04 -0700266 protected void enforcePhoneStatePermission(int pid, int uid) {
267 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
268 != PackageManager.PERMISSION_GRANTED) {
269 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
270 }
271 }
272
RoboErik01fe6612014-02-13 14:19:04 -0800273 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700274 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800275 destroySessionLocked(session);
276 }
277 }
278
279 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700280 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800281 destroySessionLocked(session);
282 }
283 }
284
RoboErik4646d282014-05-13 10:13:04 -0700285 private void updateUser() {
286 synchronized (mLock) {
287 int userId = ActivityManager.getCurrentUser();
288 if (mCurrentUserId != userId) {
289 final int oldUserId = mCurrentUserId;
290 mCurrentUserId = userId; // do this first
291
292 UserRecord oldUser = mUserRecords.get(oldUserId);
293 if (oldUser != null) {
294 oldUser.stopLocked();
295 }
296
297 UserRecord newUser = getOrCreateUser(userId);
298 newUser.startLocked();
299 }
300 }
301 }
302
303 /**
304 * Stop the user and unbind from everything.
305 *
306 * @param user The user to dispose of
307 */
308 private void destroyUserLocked(UserRecord user) {
309 user.stopLocked();
310 user.destroyLocked();
311 mUserRecords.remove(user.mUserId);
312 }
313
314 /*
315 * When a session is removed several things need to happen.
316 * 1. We need to remove it from the relevant user.
317 * 2. We need to remove it from the priority stack.
318 * 3. We need to remove it from all sessions.
319 * 4. If this is the system priority session we need to clear it.
320 * 5. We need to unlink to death from the cb binder
321 * 6. We need to tell the session to do any final cleanup (onDestroy)
322 */
RoboErik01fe6612014-02-13 14:19:04 -0800323 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700324 int userId = session.getUserId();
325 UserRecord user = mUserRecords.get(userId);
326 if (user != null) {
327 user.removeSessionLocked(session);
328 }
329
RoboErika8f95142014-05-05 14:23:49 -0700330 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700331 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700332 if (session == mPrioritySession) {
333 mPrioritySession = null;
334 }
RoboErik4646d282014-05-13 10:13:04 -0700335
336 try {
337 session.getCallback().asBinder().unlinkToDeath(session, 0);
338 } catch (Exception e) {
339 // ignore exceptions while destroying a session.
340 }
341 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700342
343 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800344 }
345
346 private void enforcePackageName(String packageName, int uid) {
347 if (TextUtils.isEmpty(packageName)) {
348 throw new IllegalArgumentException("packageName may not be empty");
349 }
350 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
351 final int packageCount = packages.length;
352 for (int i = 0; i < packageCount; i++) {
353 if (packageName.equals(packages[i])) {
354 return;
355 }
356 }
357 throw new IllegalArgumentException("packageName is not owned by the calling process");
358 }
359
RoboErike7880d82014-04-30 12:48:25 -0700360 /**
361 * Checks a caller's authorization to register an IRemoteControlDisplay.
362 * Authorization is granted if one of the following is true:
363 * <ul>
364 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
365 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700366 * <li>the caller's listener is one of the enabled notification listeners
367 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700368 * </ul>
369 */
RoboErika5b02322014-05-07 17:05:49 -0700370 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
371 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700372 if (getContext()
373 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
374 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700375 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
376 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700377 throw new SecurityException("Missing permission to control media.");
378 }
379 }
380
RoboErik19c95182014-06-23 15:38:48 -0700381 private void enforceStatusBarPermission(String action, int pid, int uid) {
382 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
383 pid, uid) != PackageManager.PERMISSION_GRANTED) {
384 throw new SecurityException("Only system ui may " + action);
385 }
386 }
387
RoboErika5b02322014-05-07 17:05:49 -0700388 /**
389 * This checks if the component is an enabled notification listener for the
390 * specified user. Enabled components may only operate on behalf of the user
391 * they're running as.
392 *
393 * @param compName The component that is enabled.
394 * @param userId The user id of the caller.
395 * @param forUserId The user id they're making the request on behalf of.
396 * @return True if the component is enabled, false otherwise
397 */
398 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
399 int forUserId) {
400 if (userId != forUserId) {
401 // You may not access another user's content as an enabled listener.
402 return false;
403 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700404 if (DEBUG) {
405 Log.d(TAG, "Checking if enabled notification listener " + compName);
406 }
RoboErike7880d82014-04-30 12:48:25 -0700407 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700408 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700409 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700410 userId);
RoboErike7880d82014-04-30 12:48:25 -0700411 if (enabledNotifListeners != null) {
412 final String[] components = enabledNotifListeners.split(":");
413 for (int i = 0; i < components.length; i++) {
414 final ComponentName component =
415 ComponentName.unflattenFromString(components[i]);
416 if (component != null) {
417 if (compName.equals(component)) {
418 if (DEBUG) {
419 Log.d(TAG, "ok to get sessions: " + component +
420 " is authorized notification listener");
421 }
422 return true;
423 }
424 }
425 }
426 }
427 if (DEBUG) {
428 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700429 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700430 }
431 }
432 return false;
433 }
434
RoboErika5b02322014-05-07 17:05:49 -0700435 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700436 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800437 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700438 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800439 }
440 }
441
RoboErik4646d282014-05-13 10:13:04 -0700442 /*
443 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700444 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700445 * 2. It needs to be added to all sessions.
446 * 3. It needs to be added to the priority stack.
447 * 4. It needs to be added to the relevant user record.
448 */
RoboErika5b02322014-05-07 17:05:49 -0700449 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
450 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700451
RoboErika5b02322014-05-07 17:05:49 -0700452 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
453 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800454 try {
455 cb.asBinder().linkToDeath(session, 0);
456 } catch (RemoteException e) {
457 throw new RuntimeException("Media Session owner died prematurely.", e);
458 }
RoboErik4646d282014-05-13 10:13:04 -0700459
460 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700461 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700462
463 UserRecord user = getOrCreateUser(userId);
464 user.addSessionLocked(session);
465
RoboErik2e7a9162014-06-04 16:53:45 -0700466 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
467
RoboErik01fe6612014-02-13 14:19:04 -0800468 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700469 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800470 }
471 return session;
472 }
473
RoboErik4646d282014-05-13 10:13:04 -0700474 private UserRecord getOrCreateUser(int userId) {
475 UserRecord user = mUserRecords.get(userId);
476 if (user == null) {
477 user = new UserRecord(getContext(), userId);
478 mUserRecords.put(userId, user);
479 }
480 return user;
481 }
482
RoboErika8f95142014-05-05 14:23:49 -0700483 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700484 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
485 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700486 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
487 return i;
488 }
489 }
490 return -1;
491 }
492
RoboErik2e7a9162014-06-04 16:53:45 -0700493 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
494 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
495 if (mSessionsListeners.get(i).mListener == listener) {
496 return i;
497 }
498 }
499 return -1;
500 }
501
RoboErike7880d82014-04-30 12:48:25 -0700502 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700503 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700504 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700505 }
506
RoboErik2e7a9162014-06-04 16:53:45 -0700507 private void pushSessionsChanged(int userId) {
508 synchronized (mLock) {
509 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
510 int size = records.size();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700511 if (size > 0) {
512 persistMediaButtonReceiverLocked(records.get(0));
513 }
Jeff Browndba34ba2014-06-24 20:46:03 -0700514 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700515 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700516 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700517 }
RoboErik19c95182014-06-23 15:38:48 -0700518 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700519 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
520 SessionsListenerRecord record = mSessionsListeners.get(i);
521 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
522 try {
523 record.mListener.onActiveSessionsChanged(tokens);
524 } catch (RemoteException e) {
525 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
526 e);
527 mSessionsListeners.remove(i);
528 }
529 }
530 }
531 }
532 }
533
RoboErik19c95182014-06-23 15:38:48 -0700534 private void pushRemoteVolumeUpdateLocked(int userId) {
535 if (mRvc != null) {
536 try {
537 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
538 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
539 } catch (RemoteException e) {
540 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
541 }
542 }
543 }
544
RoboErik6f0e4dd2014-06-17 16:56:27 -0700545 private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
546 ComponentName receiver = record.getMediaButtonReceiver();
547 if (receiver != null) {
548 Settings.System.putStringForUser(mContentResolver,
549 Settings.System.MEDIA_BUTTON_RECEIVER,
550 receiver == null ? "" : receiver.flattenToString(),
551 UserHandle.USER_CURRENT);
552 }
553 }
554
RoboErik07c70772014-03-20 13:33:52 -0700555 private MediaRouteProviderProxy.RoutesListener mRoutesCallback
556 = new MediaRouteProviderProxy.RoutesListener() {
557 @Override
558 public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
559 int reqId) {
560 // TODO for now select the first route to test, eventually add the
561 // new routes to the dialog if it is still open
562 synchronized (mLock) {
563 int index = findIndexOfSessionForIdLocked(sessionId);
564 if (index != -1 && routes != null && routes.size() > 0) {
RoboErik4646d282014-05-13 10:13:04 -0700565 MediaSessionRecord record = mAllSessions.get(index);
566 RouteInfo route = routes.get(0);
567 record.selectRoute(route);
568 UserRecord user = mUserRecords.get(record.getUserId());
569 MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
570 provider.addSession(record);
RoboErik07c70772014-03-20 13:33:52 -0700571 }
572 }
573 }
574
575 @Override
576 public void onRouteConnected(String sessionId, RouteInfo route,
577 RouteRequest options, RouteConnectionRecord connection) {
578 synchronized (mLock) {
579 int index = findIndexOfSessionForIdLocked(sessionId);
580 if (index != -1) {
RoboErik4646d282014-05-13 10:13:04 -0700581 MediaSessionRecord session = mAllSessions.get(index);
RoboErik07c70772014-03-20 13:33:52 -0700582 session.setRouteConnected(route, options.getConnectionOptions(), connection);
583 }
584 }
585 }
586 };
587
RoboErik4646d282014-05-13 10:13:04 -0700588 /**
589 * Information about a particular user. The contents of this object is
590 * guarded by mLock.
591 */
592 final class UserRecord {
593 private final int mUserId;
594 private final MediaRouteProviderWatcher mRouteProviderWatcher;
595 private final ArrayList<MediaRouteProviderProxy> mProviders
596 = new ArrayList<MediaRouteProviderProxy>();
597 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
598
599 public UserRecord(Context context, int userId) {
600 mUserId = userId;
601 mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
602 mProviderWatcherCallback, mHandler, userId);
603 }
604
605 public void startLocked() {
606 mRouteProviderWatcher.start();
607 }
608
609 public void stopLocked() {
610 mRouteProviderWatcher.stop();
611 updateInterestLocked();
612 }
613
614 public void destroyLocked() {
615 for (int i = mSessions.size() - 1; i >= 0; i--) {
616 MediaSessionRecord session = mSessions.get(i);
617 MediaSessionService.this.destroySessionLocked(session);
618 if (session.isConnected()) {
RoboErik42ea7ee2014-05-16 16:27:35 -0700619 session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
RoboErik4646d282014-05-13 10:13:04 -0700620 }
621 }
622 }
623
624 public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
625 return mProviders;
626 }
627
628 public ArrayList<MediaSessionRecord> getSessionsLocked() {
629 return mSessions;
630 }
631
632 public void addSessionLocked(MediaSessionRecord session) {
633 mSessions.add(session);
634 updateInterestLocked();
635 }
636
637 public void removeSessionLocked(MediaSessionRecord session) {
638 mSessions.remove(session);
639 RouteInfo route = session.getRoute();
640 if (route != null) {
641 MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
642 if (provider != null) {
643 provider.removeSession(session);
644 }
645 }
646 updateInterestLocked();
647 }
648
649 public void dumpLocked(PrintWriter pw, String prefix) {
650 pw.println(prefix + "Record for user " + mUserId);
651 String indent = prefix + " ";
652 int size = mProviders.size();
653 pw.println(indent + size + " Providers:");
654 for (int i = 0; i < size; i++) {
655 mProviders.get(i).dump(pw, indent);
656 }
657 pw.println();
658 size = mSessions.size();
659 pw.println(indent + size + " Sessions:");
660 for (int i = 0; i < size; i++) {
661 // Just print the session info, the full session dump will
662 // already be in the list of all sessions.
663 pw.println(indent + mSessions.get(i).getSessionInfo());
664 }
665 }
666
667 public void updateInterestLocked() {
668 // TODO go through the sessions and build up the set of interfaces
669 // we're interested in. Update the provider watcher.
670 // For now, just express interest in all providers for the current
671 // user
672 boolean interested = mUserId == mCurrentUserId;
673 for (int i = mProviders.size() - 1; i >= 0; i--) {
674 mProviders.get(i).setInterested(interested);
675 }
676 }
677
678 private MediaRouteProviderProxy getProviderLocked(String providerId) {
679 for (int i = mProviders.size() - 1; i >= 0; i--) {
680 MediaRouteProviderProxy provider = mProviders.get(i);
681 if (TextUtils.equals(providerId, provider.getId())) {
682 return provider;
683 }
684 }
685 return null;
686 }
687
688 private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
689 = new MediaRouteProviderWatcher.Callback() {
690 @Override
691 public void removeProvider(MediaRouteProviderProxy provider) {
692 synchronized (mLock) {
693 mProviders.remove(provider);
694 provider.setRoutesListener(null);
695 provider.setInterested(false);
696 }
697 }
698
699 @Override
700 public void addProvider(MediaRouteProviderProxy provider) {
701 synchronized (mLock) {
702 mProviders.add(provider);
703 provider.setRoutesListener(mRoutesCallback);
704 provider.setInterested(true);
705 }
706 }
707 };
708 }
709
RoboErik2e7a9162014-06-04 16:53:45 -0700710 final class SessionsListenerRecord implements IBinder.DeathRecipient {
711 private final IActiveSessionsListener mListener;
712 private final int mUserId;
713
714 public SessionsListenerRecord(IActiveSessionsListener listener, int userId) {
715 mListener = listener;
716 mUserId = userId;
717 }
718
719 @Override
720 public void binderDied() {
721 synchronized (mLock) {
722 mSessionsListeners.remove(this);
723 }
724 }
725 }
726
RoboErik07c70772014-03-20 13:33:52 -0700727 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700728 private static final String EXTRA_WAKELOCK_ACQUIRED =
729 "android.media.AudioService.WAKELOCK_ACQUIRED";
730 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
731
RoboErik9a9d0b52014-05-20 14:53:39 -0700732 private boolean mVoiceButtonDown = false;
733 private boolean mVoiceButtonHandled = false;
734
RoboErik07c70772014-03-20 13:33:52 -0700735 @Override
RoboErika5b02322014-05-07 17:05:49 -0700736 public ISession createSession(String packageName, ISessionCallback cb, String tag,
737 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800738 final int pid = Binder.getCallingPid();
739 final int uid = Binder.getCallingUid();
740 final long token = Binder.clearCallingIdentity();
741 try {
742 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700743 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
744 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800745 if (cb == null) {
746 throw new IllegalArgumentException("Controller callback cannot be null");
747 }
RoboErika5b02322014-05-07 17:05:49 -0700748 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
749 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700750 } finally {
751 Binder.restoreCallingIdentity(token);
752 }
753 }
754
755 @Override
RoboErika5b02322014-05-07 17:05:49 -0700756 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700757 final int pid = Binder.getCallingPid();
758 final int uid = Binder.getCallingUid();
759 final long token = Binder.clearCallingIdentity();
760
761 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700762 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700763 ArrayList<IBinder> binders = new ArrayList<IBinder>();
764 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700765 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700766 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700767 int size = records.size();
768 for (int i = 0; i < size; i++) {
769 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700770 }
771 }
772 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800773 } finally {
774 Binder.restoreCallingIdentity(token);
775 }
776 }
RoboErika278ea72014-04-24 14:49:01 -0700777
RoboErik2e7a9162014-06-04 16:53:45 -0700778 @Override
779 public void addSessionsListener(IActiveSessionsListener listener,
780 ComponentName componentName, int userId) throws RemoteException {
781 final int pid = Binder.getCallingPid();
782 final int uid = Binder.getCallingUid();
783 final long token = Binder.clearCallingIdentity();
784
785 try {
786 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
787 synchronized (mLock) {
788 int index = findIndexOfSessionsListenerLocked(listener);
789 if (index != -1) {
790 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
791 return;
792 }
793 SessionsListenerRecord record = new SessionsListenerRecord(listener,
794 resolvedUserId);
795 try {
796 listener.asBinder().linkToDeath(record, 0);
797 } catch (RemoteException e) {
798 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
799 return;
800 }
801 mSessionsListeners.add(record);
802 }
803 } finally {
804 Binder.restoreCallingIdentity(token);
805 }
806 }
807
808 @Override
809 public void removeSessionsListener(IActiveSessionsListener listener)
810 throws RemoteException {
811 synchronized (mLock) {
812 int index = findIndexOfSessionsListenerLocked(listener);
813 if (index != -1) {
814 SessionsListenerRecord record = mSessionsListeners.remove(index);
815 try {
816 record.mListener.asBinder().unlinkToDeath(record, 0);
817 } catch (Exception e) {
818 // ignore exceptions, the record is being removed
819 }
820 }
821 }
822 }
823
RoboErik8a2cfc32014-05-16 11:19:38 -0700824 /**
825 * Handles the dispatching of the media button events to one of the
826 * registered listeners, or if there was none, broadcast an
827 * ACTION_MEDIA_BUTTON intent to the rest of the system.
828 *
829 * @param keyEvent a non-null KeyEvent whose key code is one of the
830 * supported media buttons
831 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
832 * while this key event is dispatched.
833 */
834 @Override
835 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
836 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
837 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
838 return;
839 }
840 final int pid = Binder.getCallingPid();
841 final int uid = Binder.getCallingUid();
842 final long token = Binder.clearCallingIdentity();
843
844 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700845 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700846 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700847 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700848 if (isVoiceKey(keyEvent.getKeyCode())) {
849 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700850 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700851 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700852 }
853 }
854 } finally {
855 Binder.restoreCallingIdentity(token);
856 }
857 }
858
RoboErika278ea72014-04-24 14:49:01 -0700859 @Override
RoboErikb69ffd42014-05-30 14:57:59 -0700860 public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
861 throws RemoteException {
862 final int pid = Binder.getCallingPid();
863 final int uid = Binder.getCallingUid();
864 final long token = Binder.clearCallingIdentity();
865 try {
866 synchronized (mLock) {
867 MediaSessionRecord session = mPriorityStack
868 .getDefaultVolumeSession(mCurrentUserId);
869 dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
870 }
871 } finally {
872 Binder.restoreCallingIdentity(token);
873 }
874 }
875
876 @Override
RoboErik19c95182014-06-23 15:38:48 -0700877 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
878 final int pid = Binder.getCallingPid();
879 final int uid = Binder.getCallingUid();
880 final long token = Binder.clearCallingIdentity();
881 try {
882 enforceStatusBarPermission("listen for volume changes", pid, uid);
883 mRvc = rvc;
884 } finally {
885 Binder.restoreCallingIdentity(token);
886 }
887 }
888
889 @Override
RoboErika278ea72014-04-24 14:49:01 -0700890 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
891 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
892 != PackageManager.PERMISSION_GRANTED) {
893 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
894 + Binder.getCallingPid()
895 + ", uid=" + Binder.getCallingUid());
896 return;
897 }
898
899 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
900 pw.println();
901
902 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700903 pw.println("Session for calls:" + mPrioritySession);
904 if (mPrioritySession != null) {
905 mPrioritySession.dump(pw, "");
906 }
RoboErik4646d282014-05-13 10:13:04 -0700907 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700908 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700909 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700910 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700911 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700912 }
RoboErika5b02322014-05-07 17:05:49 -0700913 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700914
RoboErik4646d282014-05-13 10:13:04 -0700915 pw.println("User Records:");
916 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700917 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700918 UserRecord user = mUserRecords.get(i);
919 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700920 }
921 }
922 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700923
RoboErik2e7a9162014-06-04 16:53:45 -0700924 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
925 final int uid) {
926 String packageName = null;
927 if (componentName != null) {
928 // If they gave us a component name verify they own the
929 // package
930 packageName = componentName.getPackageName();
931 enforcePackageName(packageName, uid);
932 }
933 // Check that they can make calls on behalf of the user and
934 // get the final user id
935 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
936 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
937 // Check if they have the permissions or their component is
938 // enabled for the user they're calling from.
939 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
940 return resolvedUserId;
941 }
942
RoboErikb69ffd42014-05-30 14:57:59 -0700943 private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
944 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700945 if (DEBUG) {
946 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
947 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
948 + ", suggestedStream=" + suggestedStream);
949
950 }
951 if (session == null) {
RoboErik3c45c292014-07-08 16:47:31 -0700952 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0) {
953 if (DEBUG) {
954 Log.d(TAG, "No active session to adjust, skipping media only volume event");
955 return;
956 }
957 }
RoboErik0791e172014-06-08 10:52:32 -0700958 try {
959 if (delta == 0) {
960 mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags,
961 getContext().getOpPackageName());
962 } else {
RoboErikef3c9e92014-06-19 16:07:28 -0700963 int direction = 0;
964 int steps = delta;
965 if (delta > 0) {
966 direction = 1;
967 } else if (delta < 0) {
968 direction = -1;
969 steps = -delta;
970 }
RoboErik0791e172014-06-08 10:52:32 -0700971 for (int i = 0; i < steps; i++) {
972 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
973 flags, getContext().getOpPackageName());
974 }
RoboErikb69ffd42014-05-30 14:57:59 -0700975 }
RoboErik0791e172014-06-08 10:52:32 -0700976 } catch (RemoteException e) {
977 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700978 }
979 } else {
RoboErikef3c9e92014-06-19 16:07:28 -0700980 session.adjustVolumeBy(delta, flags);
RoboErik851d2d52014-06-27 18:02:40 -0700981 if (session.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE
982 && mRvc != null) {
RoboErik19c95182014-06-23 15:38:48 -0700983 try {
984 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
985 } catch (Exception e) {
986 Log.wtf(TAG, "Error sending volume change to system UI.", e);
987 }
988 }
RoboErikb69ffd42014-05-30 14:57:59 -0700989 }
990 }
991
RoboErik9a9d0b52014-05-20 14:53:39 -0700992 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
993 MediaSessionRecord session) {
994 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
995 // If the phone app has priority just give it the event
996 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
997 return;
998 }
999 int action = keyEvent.getAction();
1000 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
1001 if (action == KeyEvent.ACTION_DOWN) {
1002 if (keyEvent.getRepeatCount() == 0) {
1003 mVoiceButtonDown = true;
1004 mVoiceButtonHandled = false;
1005 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
1006 mVoiceButtonHandled = true;
1007 startVoiceInput(needWakeLock);
1008 }
1009 } else if (action == KeyEvent.ACTION_UP) {
1010 if (mVoiceButtonDown) {
1011 mVoiceButtonDown = false;
1012 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1013 // Resend the down then send this event through
1014 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
1015 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
1016 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
1017 }
1018 }
1019 }
1020 }
1021
1022 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
1023 MediaSessionRecord session) {
1024 if (session != null) {
1025 if (DEBUG) {
1026 Log.d(TAG, "Sending media key to " + session.getSessionInfo());
1027 }
1028 if (needWakeLock) {
1029 mKeyEventReceiver.aquireWakeLockLocked();
1030 }
1031 // If we don't need a wakelock use -1 as the id so we
1032 // won't release it later
1033 session.sendMediaButton(keyEvent,
1034 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
1035 mKeyEventReceiver);
1036 } else {
1037 if (needWakeLock) {
1038 mMediaEventWakeLock.acquire();
1039 }
1040 if (DEBUG) {
1041 Log.d(TAG, "Sending media key ordered broadcast");
1042 }
1043 // Fallback to legacy behavior
1044 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
1045 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1046 if (needWakeLock) {
1047 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
1048 WAKELOCK_RELEASE_ON_FINISHED);
1049 }
1050 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
1051 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
1052 }
1053 }
1054
1055 private void startVoiceInput(boolean needWakeLock) {
1056 Intent voiceIntent = null;
1057 // select which type of search to launch:
1058 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1059 // - device locked or screen off: action is
1060 // ACTION_VOICE_SEARCH_HANDS_FREE
1061 // with EXTRA_SECURE set to true if the device is securely locked
1062 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1063 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1064 if (!isLocked && pm.isScreenOn()) {
1065 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1066 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1067 } else {
1068 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1069 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1070 isLocked && mKeyguardManager.isKeyguardSecure());
1071 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1072 }
1073 // start the search activity
1074 if (needWakeLock) {
1075 mMediaEventWakeLock.acquire();
1076 }
1077 try {
1078 if (voiceIntent != null) {
1079 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1080 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1081 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1082 }
1083 } catch (ActivityNotFoundException e) {
1084 Log.w(TAG, "No activity for search: " + e);
1085 } finally {
1086 if (needWakeLock) {
1087 mMediaEventWakeLock.release();
1088 }
1089 }
1090 }
1091
1092 private boolean isVoiceKey(int keyCode) {
1093 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
1094 }
1095
RoboErik418c10c2014-05-19 09:25:25 -07001096 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1097
1098 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
1099 private final Handler mHandler;
1100 private int mRefCount = 0;
1101 private int mLastTimeoutId = 0;
1102
1103 public KeyEventWakeLockReceiver(Handler handler) {
1104 super(handler);
1105 mHandler = handler;
1106 }
1107
1108 public void onTimeout() {
1109 synchronized (mLock) {
1110 if (mRefCount == 0) {
1111 // We've already released it, so just return
1112 return;
1113 }
1114 mLastTimeoutId++;
1115 mRefCount = 0;
1116 releaseWakeLockLocked();
1117 }
1118 }
1119
1120 public void aquireWakeLockLocked() {
1121 if (mRefCount == 0) {
1122 mMediaEventWakeLock.acquire();
1123 }
1124 mRefCount++;
1125 mHandler.removeCallbacks(this);
1126 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1127
1128 }
1129
1130 @Override
1131 public void run() {
1132 onTimeout();
1133 }
1134
RoboErik8a2cfc32014-05-16 11:19:38 -07001135 @Override
1136 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001137 if (resultCode < mLastTimeoutId) {
1138 // Ignore results from calls that were before the last
1139 // timeout, just in case.
1140 return;
1141 } else {
1142 synchronized (mLock) {
1143 if (mRefCount > 0) {
1144 mRefCount--;
1145 if (mRefCount == 0) {
1146 releaseWakeLockLocked();
1147 }
1148 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001149 }
1150 }
1151 }
RoboErik418c10c2014-05-19 09:25:25 -07001152
1153 private void releaseWakeLockLocked() {
1154 mMediaEventWakeLock.release();
1155 mHandler.removeCallbacks(this);
1156 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001157 };
1158
1159 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1160 @Override
1161 public void onReceive(Context context, Intent intent) {
1162 if (intent == null) {
1163 return;
1164 }
1165 Bundle extras = intent.getExtras();
1166 if (extras == null) {
1167 return;
1168 }
1169 synchronized (mLock) {
1170 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1171 && mMediaEventWakeLock.isHeld()) {
1172 mMediaEventWakeLock.release();
1173 }
1174 }
1175 }
1176 };
RoboErik01fe6612014-02-13 14:19:04 -08001177 }
1178
RoboErik2e7a9162014-06-04 16:53:45 -07001179 final class MessageHandler extends Handler {
1180 private static final int MSG_SESSIONS_CHANGED = 1;
1181
1182 @Override
1183 public void handleMessage(Message msg) {
1184 switch (msg.what) {
1185 case MSG_SESSIONS_CHANGED:
1186 pushSessionsChanged(msg.arg1);
1187 break;
1188 }
1189 }
1190
1191 public void post(int what, int arg1, int arg2) {
1192 obtainMessage(what, arg1, arg2).sendToTarget();
1193 }
1194 }
RoboErik01fe6612014-02-13 14:19:04 -08001195}