blob: 277344efadf5fe8fb6eb9ec4b9b5ca5b3129cef2 [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;
RoboErikb214efb2014-07-24 13:20:30 -070023import android.app.PendingIntent;
24import android.app.PendingIntent.CanceledException;
RoboErik9a9d0b52014-05-20 14:53:39 -070025import android.content.ActivityNotFoundException;
RoboErik8a2cfc32014-05-16 11:19:38 -070026import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070027import android.content.ComponentName;
RoboErik6f0e4dd2014-06-17 16:56:27 -070028import android.content.ContentResolver;
RoboErik01fe6612014-02-13 14:19:04 -080029import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070030import android.content.Intent;
RoboErika278ea72014-04-24 14:49:01 -070031import android.content.pm.PackageManager;
RoboErik7aef77b2014-08-08 15:56:54 -070032import android.database.ContentObserver;
RoboErik3c45c292014-07-08 16:47:31 -070033import android.media.AudioManager;
RoboErikb69ffd42014-05-30 14:57:59 -070034import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070035import android.media.IRemoteVolumeController;
RoboErik2e7a9162014-06-04 16:53:45 -070036import android.media.session.IActiveSessionsListener;
RoboErik07c70772014-03-20 13:33:52 -070037import android.media.session.ISession;
38import android.media.session.ISessionCallback;
39import android.media.session.ISessionManager;
Jeff Browndba34ba2014-06-24 20:46:03 -070040import android.media.session.MediaSession;
RoboErik7aef77b2014-08-08 15:56:54 -070041import android.net.Uri;
RoboErik01fe6612014-02-13 14:19:04 -080042import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070043import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080044import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070045import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070046import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070047import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080048import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070049import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070050import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070051import android.os.UserHandle;
52import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070053import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080054import android.text.TextUtils;
55import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070056import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070057import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080058
59import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070060import com.android.server.Watchdog;
61import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080062
RoboErika278ea72014-04-24 14:49:01 -070063import java.io.FileDescriptor;
64import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080065import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070066import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080067
68/**
69 * System implementation of MediaSessionManager
70 */
RoboErika278ea72014-04-24 14:49:01 -070071public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080072 private static final String TAG = "MediaSessionService";
73 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
74
RoboErik418c10c2014-05-19 09:25:25 -070075 private static final int WAKELOCK_TIMEOUT = 5000;
76
RoboErik01fe6612014-02-13 14:19:04 -080077 private final SessionManagerImpl mSessionManagerImpl;
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>();
RoboErik01fe6612014-02-13 14:19:04 -080084 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070085 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070086 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080087
RoboErik9a9d0b52014-05-20 14:53:39 -070088 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070089 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070090 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -070091 private SettingsObserver mSettingsObserver;
RoboErik9a9d0b52014-05-20 14:53:39 -070092
RoboErike7880d82014-04-30 12:48:25 -070093 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070094 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070095
RoboErik19c95182014-06-23 15:38:48 -070096 // Used to notify system UI when remote volume was changed. TODO find a
97 // better way to handle this.
98 private IRemoteVolumeController mRvc;
99
RoboErik01fe6612014-02-13 14:19:04 -0800100 public MediaSessionService(Context context) {
101 super(context);
102 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700103 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700104 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
105 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -0800106 }
107
108 @Override
109 public void onStart() {
110 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700111 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700112 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700113 mKeyguardManager =
114 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700115 mAudioService = getAudioService();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700116 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700117 mSettingsObserver = new SettingsObserver();
118 mSettingsObserver.observe();
RoboErikb69ffd42014-05-30 14:57:59 -0700119 }
120
121 private IAudioService getAudioService() {
122 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
123 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700124 }
125
RoboErika8f95142014-05-05 14:23:49 -0700126 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700127 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700128 if (!mAllSessions.contains(record)) {
129 Log.d(TAG, "Unknown session updated. Ignoring.");
130 return;
131 }
RoboErika8f95142014-05-05 14:23:49 -0700132 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700133 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700134 if (record.isActive()) {
135 if (mPrioritySession != null) {
136 Log.w(TAG, "Replacing existing priority session with a new session");
137 }
138 mPrioritySession = record;
139 } else {
140 if (mPrioritySession == record) {
141 mPrioritySession = null;
142 }
RoboErike7880d82014-04-30 12:48:25 -0700143 }
RoboErike7880d82014-04-30 12:48:25 -0700144 }
145 }
RoboErik2e7a9162014-06-04 16:53:45 -0700146 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700147 }
148
RoboErika8f95142014-05-05 14:23:49 -0700149 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700150 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700151 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700152 if (!mAllSessions.contains(record)) {
153 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
154 return;
155 }
RoboErik2e7a9162014-06-04 16:53:45 -0700156 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
157 }
158 if (updateSessions) {
159 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700160 }
161 }
162
RoboErik19c95182014-06-23 15:38:48 -0700163 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
164 synchronized (mLock) {
165 if (!mAllSessions.contains(record)) {
166 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
167 return;
168 }
169 pushRemoteVolumeUpdateLocked(record.getUserId());
170 }
171 }
172
RoboErika278ea72014-04-24 14:49:01 -0700173 @Override
RoboErik4646d282014-05-13 10:13:04 -0700174 public void onStartUser(int userHandle) {
175 updateUser();
176 }
177
178 @Override
179 public void onSwitchUser(int userHandle) {
180 updateUser();
181 }
182
183 @Override
184 public void onStopUser(int userHandle) {
185 synchronized (mLock) {
186 UserRecord user = mUserRecords.get(userHandle);
187 if (user != null) {
188 destroyUserLocked(user);
189 }
190 }
191 }
192
193 @Override
RoboErika278ea72014-04-24 14:49:01 -0700194 public void monitor() {
195 synchronized (mLock) {
196 // Check for deadlock
197 }
198 }
199
RoboErik4646d282014-05-13 10:13:04 -0700200 protected void enforcePhoneStatePermission(int pid, int uid) {
201 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
202 != PackageManager.PERMISSION_GRANTED) {
203 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
204 }
205 }
206
RoboErik01fe6612014-02-13 14:19:04 -0800207 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700208 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800209 destroySessionLocked(session);
210 }
211 }
212
213 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700214 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800215 destroySessionLocked(session);
216 }
217 }
218
RoboErik4646d282014-05-13 10:13:04 -0700219 private void updateUser() {
220 synchronized (mLock) {
221 int userId = ActivityManager.getCurrentUser();
222 if (mCurrentUserId != userId) {
223 final int oldUserId = mCurrentUserId;
224 mCurrentUserId = userId; // do this first
225
226 UserRecord oldUser = mUserRecords.get(oldUserId);
227 if (oldUser != null) {
228 oldUser.stopLocked();
229 }
230
231 UserRecord newUser = getOrCreateUser(userId);
232 newUser.startLocked();
233 }
234 }
235 }
236
RoboErik7aef77b2014-08-08 15:56:54 -0700237 private void updateActiveSessionListeners() {
238 synchronized (mLock) {
239 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
240 SessionsListenerRecord listener = mSessionsListeners.get(i);
241 try {
242 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
243 listener.mUserId);
244 } catch (SecurityException e) {
245 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
246 + " is no longer authorized. Disconnecting.");
247 mSessionsListeners.remove(i);
248 try {
249 listener.mListener
250 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
251 } catch (Exception e1) {
252 // ignore
253 }
254 }
255 }
256 }
257 }
258
RoboErik4646d282014-05-13 10:13:04 -0700259 /**
260 * Stop the user and unbind from everything.
261 *
262 * @param user The user to dispose of
263 */
264 private void destroyUserLocked(UserRecord user) {
265 user.stopLocked();
266 user.destroyLocked();
267 mUserRecords.remove(user.mUserId);
268 }
269
270 /*
271 * When a session is removed several things need to happen.
272 * 1. We need to remove it from the relevant user.
273 * 2. We need to remove it from the priority stack.
274 * 3. We need to remove it from all sessions.
275 * 4. If this is the system priority session we need to clear it.
276 * 5. We need to unlink to death from the cb binder
277 * 6. We need to tell the session to do any final cleanup (onDestroy)
278 */
RoboErik01fe6612014-02-13 14:19:04 -0800279 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700280 int userId = session.getUserId();
281 UserRecord user = mUserRecords.get(userId);
282 if (user != null) {
283 user.removeSessionLocked(session);
284 }
285
RoboErika8f95142014-05-05 14:23:49 -0700286 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700287 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700288 if (session == mPrioritySession) {
289 mPrioritySession = null;
290 }
RoboErik4646d282014-05-13 10:13:04 -0700291
292 try {
293 session.getCallback().asBinder().unlinkToDeath(session, 0);
294 } catch (Exception e) {
295 // ignore exceptions while destroying a session.
296 }
297 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700298
299 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800300 }
301
302 private void enforcePackageName(String packageName, int uid) {
303 if (TextUtils.isEmpty(packageName)) {
304 throw new IllegalArgumentException("packageName may not be empty");
305 }
306 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
307 final int packageCount = packages.length;
308 for (int i = 0; i < packageCount; i++) {
309 if (packageName.equals(packages[i])) {
310 return;
311 }
312 }
313 throw new IllegalArgumentException("packageName is not owned by the calling process");
314 }
315
RoboErike7880d82014-04-30 12:48:25 -0700316 /**
317 * Checks a caller's authorization to register an IRemoteControlDisplay.
318 * Authorization is granted if one of the following is true:
319 * <ul>
320 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
321 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700322 * <li>the caller's listener is one of the enabled notification listeners
323 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700324 * </ul>
325 */
RoboErika5b02322014-05-07 17:05:49 -0700326 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
327 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700328 if (getContext()
329 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
330 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700331 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
332 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700333 throw new SecurityException("Missing permission to control media.");
334 }
335 }
336
RoboErik19c95182014-06-23 15:38:48 -0700337 private void enforceStatusBarPermission(String action, int pid, int uid) {
338 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
339 pid, uid) != PackageManager.PERMISSION_GRANTED) {
340 throw new SecurityException("Only system ui may " + action);
341 }
342 }
343
RoboErika5b02322014-05-07 17:05:49 -0700344 /**
345 * This checks if the component is an enabled notification listener for the
346 * specified user. Enabled components may only operate on behalf of the user
347 * they're running as.
348 *
349 * @param compName The component that is enabled.
350 * @param userId The user id of the caller.
351 * @param forUserId The user id they're making the request on behalf of.
352 * @return True if the component is enabled, false otherwise
353 */
354 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
355 int forUserId) {
356 if (userId != forUserId) {
357 // You may not access another user's content as an enabled listener.
358 return false;
359 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700360 if (DEBUG) {
361 Log.d(TAG, "Checking if enabled notification listener " + compName);
362 }
RoboErike7880d82014-04-30 12:48:25 -0700363 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700364 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700365 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700366 userId);
RoboErike7880d82014-04-30 12:48:25 -0700367 if (enabledNotifListeners != null) {
368 final String[] components = enabledNotifListeners.split(":");
369 for (int i = 0; i < components.length; i++) {
370 final ComponentName component =
371 ComponentName.unflattenFromString(components[i]);
372 if (component != null) {
373 if (compName.equals(component)) {
374 if (DEBUG) {
375 Log.d(TAG, "ok to get sessions: " + component +
376 " is authorized notification listener");
377 }
378 return true;
379 }
380 }
381 }
382 }
383 if (DEBUG) {
384 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700385 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700386 }
387 }
388 return false;
389 }
390
RoboErika5b02322014-05-07 17:05:49 -0700391 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700392 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800393 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700394 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800395 }
396 }
397
RoboErik4646d282014-05-13 10:13:04 -0700398 /*
399 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700400 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700401 * 2. It needs to be added to all sessions.
402 * 3. It needs to be added to the priority stack.
403 * 4. It needs to be added to the relevant user record.
404 */
RoboErika5b02322014-05-07 17:05:49 -0700405 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
406 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700407
RoboErika5b02322014-05-07 17:05:49 -0700408 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
409 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800410 try {
411 cb.asBinder().linkToDeath(session, 0);
412 } catch (RemoteException e) {
413 throw new RuntimeException("Media Session owner died prematurely.", e);
414 }
RoboErik4646d282014-05-13 10:13:04 -0700415
416 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700417 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700418
419 UserRecord user = getOrCreateUser(userId);
420 user.addSessionLocked(session);
421
RoboErik2e7a9162014-06-04 16:53:45 -0700422 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
423
RoboErik01fe6612014-02-13 14:19:04 -0800424 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700425 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800426 }
427 return session;
428 }
429
RoboErik4646d282014-05-13 10:13:04 -0700430 private UserRecord getOrCreateUser(int userId) {
431 UserRecord user = mUserRecords.get(userId);
432 if (user == null) {
433 user = new UserRecord(getContext(), userId);
434 mUserRecords.put(userId, user);
435 }
436 return user;
437 }
438
RoboErik2e7a9162014-06-04 16:53:45 -0700439 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
440 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
441 if (mSessionsListeners.get(i).mListener == listener) {
442 return i;
443 }
444 }
445 return -1;
446 }
447
RoboErike7880d82014-04-30 12:48:25 -0700448 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700449 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700450 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700451 }
452
RoboErik2e7a9162014-06-04 16:53:45 -0700453 private void pushSessionsChanged(int userId) {
454 synchronized (mLock) {
455 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
456 int size = records.size();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700457 if (size > 0) {
RoboErikb214efb2014-07-24 13:20:30 -0700458 rememberMediaButtonReceiverLocked(records.get(0));
RoboErik6f0e4dd2014-06-17 16:56:27 -0700459 }
Jeff Browndba34ba2014-06-24 20:46:03 -0700460 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700461 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700462 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700463 }
RoboErik19c95182014-06-23 15:38:48 -0700464 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700465 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
466 SessionsListenerRecord record = mSessionsListeners.get(i);
467 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
468 try {
469 record.mListener.onActiveSessionsChanged(tokens);
470 } catch (RemoteException e) {
471 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
472 e);
473 mSessionsListeners.remove(i);
474 }
475 }
476 }
477 }
478 }
479
RoboErik19c95182014-06-23 15:38:48 -0700480 private void pushRemoteVolumeUpdateLocked(int userId) {
481 if (mRvc != null) {
482 try {
483 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
484 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
485 } catch (RemoteException e) {
486 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
487 }
488 }
489 }
490
RoboErikb214efb2014-07-24 13:20:30 -0700491 private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
492 PendingIntent receiver = record.getMediaButtonReceiver();
493 UserRecord user = mUserRecords.get(record.getUserId());
494 if (receiver != null && user != null) {
495 user.mLastMediaButtonReceiver = receiver;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700496 }
497 }
498
RoboErik4646d282014-05-13 10:13:04 -0700499 /**
500 * Information about a particular user. The contents of this object is
501 * guarded by mLock.
502 */
503 final class UserRecord {
504 private final int mUserId;
RoboErik4646d282014-05-13 10:13:04 -0700505 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
RoboErikb214efb2014-07-24 13:20:30 -0700506 private PendingIntent mLastMediaButtonReceiver;
RoboErik4646d282014-05-13 10:13:04 -0700507
508 public UserRecord(Context context, int userId) {
509 mUserId = userId;
RoboErik4646d282014-05-13 10:13:04 -0700510 }
511
512 public void startLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700513 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700514 }
515
516 public void stopLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700517 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700518 }
519
520 public void destroyLocked() {
521 for (int i = mSessions.size() - 1; i >= 0; i--) {
522 MediaSessionRecord session = mSessions.get(i);
523 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700524 }
525 }
526
RoboErik4646d282014-05-13 10:13:04 -0700527 public ArrayList<MediaSessionRecord> getSessionsLocked() {
528 return mSessions;
529 }
530
531 public void addSessionLocked(MediaSessionRecord session) {
532 mSessions.add(session);
RoboErik4646d282014-05-13 10:13:04 -0700533 }
534
535 public void removeSessionLocked(MediaSessionRecord session) {
536 mSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700537 }
538
539 public void dumpLocked(PrintWriter pw, String prefix) {
540 pw.println(prefix + "Record for user " + mUserId);
541 String indent = prefix + " ";
RoboErikb214efb2014-07-24 13:20:30 -0700542 pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
Jeff Brown01a500e2014-07-10 22:50:50 -0700543 int size = mSessions.size();
RoboErik4646d282014-05-13 10:13:04 -0700544 pw.println(indent + size + " Sessions:");
545 for (int i = 0; i < size; i++) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700546 // Just print the short version, the full session dump will
RoboErik4646d282014-05-13 10:13:04 -0700547 // already be in the list of all sessions.
RoboErikaa4e23b2014-07-24 18:35:11 -0700548 pw.println(indent + mSessions.get(i).toString());
RoboErik4646d282014-05-13 10:13:04 -0700549 }
550 }
RoboErik4646d282014-05-13 10:13:04 -0700551 }
552
RoboErik2e7a9162014-06-04 16:53:45 -0700553 final class SessionsListenerRecord implements IBinder.DeathRecipient {
554 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700555 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700556 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700557 private final int mPid;
558 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700559
RoboErik7aef77b2014-08-08 15:56:54 -0700560 public SessionsListenerRecord(IActiveSessionsListener listener,
561 ComponentName componentName,
562 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700563 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700564 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700565 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700566 mPid = pid;
567 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700568 }
569
570 @Override
571 public void binderDied() {
572 synchronized (mLock) {
573 mSessionsListeners.remove(this);
574 }
575 }
576 }
577
RoboErik7aef77b2014-08-08 15:56:54 -0700578 final class SettingsObserver extends ContentObserver {
579 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
580 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
581
582 private SettingsObserver() {
583 super(null);
584 }
585
586 private void observe() {
587 mContentResolver.registerContentObserver(mSecureSettingsUri,
588 false, this, UserHandle.USER_ALL);
589 }
590
591 @Override
592 public void onChange(boolean selfChange, Uri uri) {
593 updateActiveSessionListeners();
594 }
595 }
596
RoboErik07c70772014-03-20 13:33:52 -0700597 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700598 private static final String EXTRA_WAKELOCK_ACQUIRED =
599 "android.media.AudioService.WAKELOCK_ACQUIRED";
600 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
601
RoboErik9a9d0b52014-05-20 14:53:39 -0700602 private boolean mVoiceButtonDown = false;
603 private boolean mVoiceButtonHandled = false;
604
RoboErik07c70772014-03-20 13:33:52 -0700605 @Override
RoboErika5b02322014-05-07 17:05:49 -0700606 public ISession createSession(String packageName, ISessionCallback cb, String tag,
607 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800608 final int pid = Binder.getCallingPid();
609 final int uid = Binder.getCallingUid();
610 final long token = Binder.clearCallingIdentity();
611 try {
612 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700613 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
614 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800615 if (cb == null) {
616 throw new IllegalArgumentException("Controller callback cannot be null");
617 }
RoboErika5b02322014-05-07 17:05:49 -0700618 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
619 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700620 } finally {
621 Binder.restoreCallingIdentity(token);
622 }
623 }
624
625 @Override
RoboErika5b02322014-05-07 17:05:49 -0700626 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700627 final int pid = Binder.getCallingPid();
628 final int uid = Binder.getCallingUid();
629 final long token = Binder.clearCallingIdentity();
630
631 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700632 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700633 ArrayList<IBinder> binders = new ArrayList<IBinder>();
634 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700635 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700636 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700637 int size = records.size();
638 for (int i = 0; i < size; i++) {
639 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700640 }
641 }
642 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800643 } finally {
644 Binder.restoreCallingIdentity(token);
645 }
646 }
RoboErika278ea72014-04-24 14:49:01 -0700647
RoboErik2e7a9162014-06-04 16:53:45 -0700648 @Override
649 public void addSessionsListener(IActiveSessionsListener listener,
650 ComponentName componentName, int userId) throws RemoteException {
651 final int pid = Binder.getCallingPid();
652 final int uid = Binder.getCallingUid();
653 final long token = Binder.clearCallingIdentity();
654
655 try {
656 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
657 synchronized (mLock) {
658 int index = findIndexOfSessionsListenerLocked(listener);
659 if (index != -1) {
660 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
661 return;
662 }
663 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -0700664 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -0700665 try {
666 listener.asBinder().linkToDeath(record, 0);
667 } catch (RemoteException e) {
668 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
669 return;
670 }
671 mSessionsListeners.add(record);
672 }
673 } finally {
674 Binder.restoreCallingIdentity(token);
675 }
676 }
677
678 @Override
679 public void removeSessionsListener(IActiveSessionsListener listener)
680 throws RemoteException {
681 synchronized (mLock) {
682 int index = findIndexOfSessionsListenerLocked(listener);
683 if (index != -1) {
684 SessionsListenerRecord record = mSessionsListeners.remove(index);
685 try {
686 record.mListener.asBinder().unlinkToDeath(record, 0);
687 } catch (Exception e) {
688 // ignore exceptions, the record is being removed
689 }
690 }
691 }
692 }
693
RoboErik8a2cfc32014-05-16 11:19:38 -0700694 /**
695 * Handles the dispatching of the media button events to one of the
696 * registered listeners, or if there was none, broadcast an
697 * ACTION_MEDIA_BUTTON intent to the rest of the system.
698 *
699 * @param keyEvent a non-null KeyEvent whose key code is one of the
700 * supported media buttons
701 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
702 * while this key event is dispatched.
703 */
704 @Override
705 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
706 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
707 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
708 return;
709 }
710 final int pid = Binder.getCallingPid();
711 final int uid = Binder.getCallingUid();
712 final long token = Binder.clearCallingIdentity();
713
714 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700715 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700716 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700717 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700718 if (isVoiceKey(keyEvent.getKeyCode())) {
719 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700720 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700721 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700722 }
723 }
724 } finally {
725 Binder.restoreCallingIdentity(token);
726 }
727 }
728
RoboErika278ea72014-04-24 14:49:01 -0700729 @Override
RoboErik1ff5b162014-07-15 17:23:18 -0700730 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
RoboErikb69ffd42014-05-30 14:57:59 -0700731 throws RemoteException {
732 final int pid = Binder.getCallingPid();
733 final int uid = Binder.getCallingUid();
734 final long token = Binder.clearCallingIdentity();
735 try {
736 synchronized (mLock) {
737 MediaSessionRecord session = mPriorityStack
738 .getDefaultVolumeSession(mCurrentUserId);
RoboErik1ff5b162014-07-15 17:23:18 -0700739 dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
RoboErikb69ffd42014-05-30 14:57:59 -0700740 }
741 } finally {
742 Binder.restoreCallingIdentity(token);
743 }
744 }
745
746 @Override
RoboErik19c95182014-06-23 15:38:48 -0700747 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
748 final int pid = Binder.getCallingPid();
749 final int uid = Binder.getCallingUid();
750 final long token = Binder.clearCallingIdentity();
751 try {
752 enforceStatusBarPermission("listen for volume changes", pid, uid);
753 mRvc = rvc;
754 } finally {
755 Binder.restoreCallingIdentity(token);
756 }
757 }
758
759 @Override
RoboErika278ea72014-04-24 14:49:01 -0700760 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
761 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
762 != PackageManager.PERMISSION_GRANTED) {
763 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
764 + Binder.getCallingPid()
765 + ", uid=" + Binder.getCallingUid());
766 return;
767 }
768
769 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
770 pw.println();
771
772 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700773 pw.println("Session for calls:" + mPrioritySession);
774 if (mPrioritySession != null) {
775 mPrioritySession.dump(pw, "");
776 }
RoboErik4646d282014-05-13 10:13:04 -0700777 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700778 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700779 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700780 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700781 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700782 }
RoboErika5b02322014-05-07 17:05:49 -0700783 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700784
RoboErik4646d282014-05-13 10:13:04 -0700785 pw.println("User Records:");
786 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700787 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700788 UserRecord user = mUserRecords.get(i);
789 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700790 }
791 }
792 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700793
RoboErik2e7a9162014-06-04 16:53:45 -0700794 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
795 final int uid) {
796 String packageName = null;
797 if (componentName != null) {
798 // If they gave us a component name verify they own the
799 // package
800 packageName = componentName.getPackageName();
801 enforcePackageName(packageName, uid);
802 }
803 // Check that they can make calls on behalf of the user and
804 // get the final user id
805 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
806 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
807 // Check if they have the permissions or their component is
808 // enabled for the user they're calling from.
809 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
810 return resolvedUserId;
811 }
812
RoboErik1ff5b162014-07-15 17:23:18 -0700813 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
RoboErikb69ffd42014-05-30 14:57:59 -0700814 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700815 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700816 String description = session == null ? null : session.toString();
817 Log.d(TAG, "Adjusting session " + description + " by " + direction + ". flags="
818 + flags + ", suggestedStream=" + suggestedStream);
RoboErikb69ffd42014-05-30 14:57:59 -0700819
820 }
821 if (session == null) {
RoboErik3c45c292014-07-08 16:47:31 -0700822 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0) {
823 if (DEBUG) {
824 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -0700825 }
RoboErikb7c014c2014-07-22 15:58:22 -0700826 return;
RoboErik3c45c292014-07-08 16:47:31 -0700827 }
RoboErik0791e172014-06-08 10:52:32 -0700828 try {
RoboErik1ff5b162014-07-15 17:23:18 -0700829 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
830 getContext().getOpPackageName());
RoboErik0791e172014-06-08 10:52:32 -0700831 } catch (RemoteException e) {
832 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700833 }
834 } else {
RoboErik1ff5b162014-07-15 17:23:18 -0700835 session.adjustVolume(direction, flags);
RoboErik851d2d52014-06-27 18:02:40 -0700836 if (session.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE
837 && mRvc != null) {
RoboErik19c95182014-06-23 15:38:48 -0700838 try {
839 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
840 } catch (Exception e) {
841 Log.wtf(TAG, "Error sending volume change to system UI.", e);
842 }
843 }
RoboErikb69ffd42014-05-30 14:57:59 -0700844 }
845 }
846
RoboErik9a9d0b52014-05-20 14:53:39 -0700847 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
848 MediaSessionRecord session) {
849 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
850 // If the phone app has priority just give it the event
851 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
852 return;
853 }
854 int action = keyEvent.getAction();
855 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
856 if (action == KeyEvent.ACTION_DOWN) {
857 if (keyEvent.getRepeatCount() == 0) {
858 mVoiceButtonDown = true;
859 mVoiceButtonHandled = false;
860 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
861 mVoiceButtonHandled = true;
862 startVoiceInput(needWakeLock);
863 }
864 } else if (action == KeyEvent.ACTION_UP) {
865 if (mVoiceButtonDown) {
866 mVoiceButtonDown = false;
867 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
868 // Resend the down then send this event through
869 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
870 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
871 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
872 }
873 }
874 }
875 }
876
877 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
878 MediaSessionRecord session) {
879 if (session != null) {
880 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700881 Log.d(TAG, "Sending media key to " + session.toString());
RoboErik9a9d0b52014-05-20 14:53:39 -0700882 }
883 if (needWakeLock) {
884 mKeyEventReceiver.aquireWakeLockLocked();
885 }
886 // If we don't need a wakelock use -1 as the id so we
887 // won't release it later
888 session.sendMediaButton(keyEvent,
889 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
890 mKeyEventReceiver);
891 } else {
RoboErikb214efb2014-07-24 13:20:30 -0700892 // Launch the last PendingIntent we had with priority
893 int userId = ActivityManager.getCurrentUser();
894 UserRecord user = mUserRecords.get(userId);
895 if (user.mLastMediaButtonReceiver != null) {
896 if (DEBUG) {
897 Log.d(TAG, "Sending media key to last known PendingIntent");
898 }
899 if (needWakeLock) {
900 mKeyEventReceiver.aquireWakeLockLocked();
901 }
902 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
903 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
904 try {
905 user.mLastMediaButtonReceiver.send(getContext(),
906 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
907 mediaButtonIntent, mKeyEventReceiver, null);
908 } catch (CanceledException e) {
909 Log.i(TAG, "Error sending key event to media button receiver "
910 + user.mLastMediaButtonReceiver, e);
911 }
912 } else {
913 if (DEBUG) {
914 Log.d(TAG, "Sending media key ordered broadcast");
915 }
916 if (needWakeLock) {
917 mMediaEventWakeLock.acquire();
918 }
919 // Fallback to legacy behavior
920 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
921 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
922 if (needWakeLock) {
923 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
924 WAKELOCK_RELEASE_ON_FINISHED);
925 }
926 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
927 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
RoboErik9a9d0b52014-05-20 14:53:39 -0700928 }
RoboErik9a9d0b52014-05-20 14:53:39 -0700929 }
930 }
931
932 private void startVoiceInput(boolean needWakeLock) {
933 Intent voiceIntent = null;
934 // select which type of search to launch:
935 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
936 // - device locked or screen off: action is
937 // ACTION_VOICE_SEARCH_HANDS_FREE
938 // with EXTRA_SECURE set to true if the device is securely locked
939 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
940 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
941 if (!isLocked && pm.isScreenOn()) {
942 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
943 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
944 } else {
945 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
946 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
947 isLocked && mKeyguardManager.isKeyguardSecure());
948 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
949 }
950 // start the search activity
951 if (needWakeLock) {
952 mMediaEventWakeLock.acquire();
953 }
954 try {
955 if (voiceIntent != null) {
956 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
957 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
958 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
959 }
960 } catch (ActivityNotFoundException e) {
961 Log.w(TAG, "No activity for search: " + e);
962 } finally {
963 if (needWakeLock) {
964 mMediaEventWakeLock.release();
965 }
966 }
967 }
968
969 private boolean isVoiceKey(int keyCode) {
970 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
971 }
972
RoboErik418c10c2014-05-19 09:25:25 -0700973 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
974
RoboErikb214efb2014-07-24 13:20:30 -0700975 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
976 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -0700977 private final Handler mHandler;
978 private int mRefCount = 0;
979 private int mLastTimeoutId = 0;
980
981 public KeyEventWakeLockReceiver(Handler handler) {
982 super(handler);
983 mHandler = handler;
984 }
985
986 public void onTimeout() {
987 synchronized (mLock) {
988 if (mRefCount == 0) {
989 // We've already released it, so just return
990 return;
991 }
992 mLastTimeoutId++;
993 mRefCount = 0;
994 releaseWakeLockLocked();
995 }
996 }
997
998 public void aquireWakeLockLocked() {
999 if (mRefCount == 0) {
1000 mMediaEventWakeLock.acquire();
1001 }
1002 mRefCount++;
1003 mHandler.removeCallbacks(this);
1004 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1005
1006 }
1007
1008 @Override
1009 public void run() {
1010 onTimeout();
1011 }
1012
RoboErik8a2cfc32014-05-16 11:19:38 -07001013 @Override
1014 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001015 if (resultCode < mLastTimeoutId) {
1016 // Ignore results from calls that were before the last
1017 // timeout, just in case.
1018 return;
1019 } else {
1020 synchronized (mLock) {
1021 if (mRefCount > 0) {
1022 mRefCount--;
1023 if (mRefCount == 0) {
1024 releaseWakeLockLocked();
1025 }
1026 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001027 }
1028 }
1029 }
RoboErik418c10c2014-05-19 09:25:25 -07001030
1031 private void releaseWakeLockLocked() {
1032 mMediaEventWakeLock.release();
1033 mHandler.removeCallbacks(this);
1034 }
RoboErikb214efb2014-07-24 13:20:30 -07001035
1036 @Override
1037 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1038 String resultData, Bundle resultExtras) {
1039 onReceiveResult(resultCode, null);
1040 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001041 };
1042
1043 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1044 @Override
1045 public void onReceive(Context context, Intent intent) {
1046 if (intent == null) {
1047 return;
1048 }
1049 Bundle extras = intent.getExtras();
1050 if (extras == null) {
1051 return;
1052 }
1053 synchronized (mLock) {
1054 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1055 && mMediaEventWakeLock.isHeld()) {
1056 mMediaEventWakeLock.release();
1057 }
1058 }
1059 }
1060 };
RoboErik01fe6612014-02-13 14:19:04 -08001061 }
1062
RoboErik2e7a9162014-06-04 16:53:45 -07001063 final class MessageHandler extends Handler {
1064 private static final int MSG_SESSIONS_CHANGED = 1;
1065
1066 @Override
1067 public void handleMessage(Message msg) {
1068 switch (msg.what) {
1069 case MSG_SESSIONS_CHANGED:
1070 pushSessionsChanged(msg.arg1);
1071 break;
1072 }
1073 }
1074
1075 public void post(int what, int arg1, int arg2) {
1076 obtainMessage(what, arg1, arg2).sendToTarget();
1077 }
1078 }
RoboErik01fe6612014-02-13 14:19:04 -08001079}