blob: 053c988823299121eb6427166b11c19e974807fa [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;
RoboErik94c716e2014-09-14 13:54:31 -070034import android.media.AudioSystem;
RoboErikb69ffd42014-05-30 14:57:59 -070035import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070036import android.media.IRemoteVolumeController;
RoboErik2e7a9162014-06-04 16:53:45 -070037import android.media.session.IActiveSessionsListener;
RoboErik07c70772014-03-20 13:33:52 -070038import android.media.session.ISession;
39import android.media.session.ISessionCallback;
40import android.media.session.ISessionManager;
RoboErikd2b8c942014-08-19 11:23:40 -070041import android.media.session.MediaController.PlaybackInfo;
Jeff Browndba34ba2014-06-24 20:46:03 -070042import android.media.session.MediaSession;
RoboErik7aef77b2014-08-08 15:56:54 -070043import android.net.Uri;
RoboErik01fe6612014-02-13 14:19:04 -080044import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070045import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080046import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070047import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070048import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070049import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080050import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070051import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070052import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070053import android.os.UserHandle;
54import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070055import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080056import android.text.TextUtils;
57import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070058import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070059import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080060
61import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070062import com.android.server.Watchdog;
63import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080064
RoboErika278ea72014-04-24 14:49:01 -070065import java.io.FileDescriptor;
66import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080067import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070068import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080069
70/**
71 * System implementation of MediaSessionManager
72 */
RoboErika278ea72014-04-24 14:49:01 -070073public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080074 private static final String TAG = "MediaSessionService";
75 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
76
RoboErik418c10c2014-05-19 09:25:25 -070077 private static final int WAKELOCK_TIMEOUT = 5000;
78
RoboErik01fe6612014-02-13 14:19:04 -080079 private final SessionManagerImpl mSessionManagerImpl;
RoboErika8f95142014-05-05 14:23:49 -070080 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080081
RoboErik4646d282014-05-13 10:13:04 -070082 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
83 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070084 private final ArrayList<SessionsListenerRecord> mSessionsListeners
85 = new ArrayList<SessionsListenerRecord>();
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;
RoboErik519c7742014-11-18 10:59:09 -080089 private final boolean mUseMasterVolume;
RoboErik01fe6612014-02-13 14:19:04 -080090
RoboErik9a9d0b52014-05-20 14:53:39 -070091 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070092 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070093 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -070094 private SettingsObserver mSettingsObserver;
RoboErik9a9d0b52014-05-20 14:53:39 -070095
RoboErik4646d282014-05-13 10:13:04 -070096 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070097
RoboErik19c95182014-06-23 15:38:48 -070098 // Used to notify system UI when remote volume was changed. TODO find a
99 // better way to handle this.
100 private IRemoteVolumeController mRvc;
101
RoboErik01fe6612014-02-13 14:19:04 -0800102 public MediaSessionService(Context context) {
103 super(context);
104 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700105 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700106 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
107 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik519c7742014-11-18 10:59:09 -0800108 mUseMasterVolume = context.getResources().getBoolean(
109 com.android.internal.R.bool.config_useMasterVolume);
RoboErik01fe6612014-02-13 14:19:04 -0800110 }
111
112 @Override
113 public void onStart() {
114 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700115 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700116 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700117 mKeyguardManager =
118 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700119 mAudioService = getAudioService();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700120 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700121 mSettingsObserver = new SettingsObserver();
122 mSettingsObserver.observe();
RoboErikb69ffd42014-05-30 14:57:59 -0700123 }
124
125 private IAudioService getAudioService() {
126 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
127 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700128 }
129
RoboErika8f95142014-05-05 14:23:49 -0700130 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700131 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700132 if (!mAllSessions.contains(record)) {
133 Log.d(TAG, "Unknown session updated. Ignoring.");
134 return;
135 }
RoboErika8f95142014-05-05 14:23:49 -0700136 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700137 }
RoboErik2e7a9162014-06-04 16:53:45 -0700138 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700139 }
140
RoboErika8f95142014-05-05 14:23:49 -0700141 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700142 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700143 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700144 if (!mAllSessions.contains(record)) {
145 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
146 return;
147 }
RoboErik2e7a9162014-06-04 16:53:45 -0700148 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
149 }
150 if (updateSessions) {
151 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700152 }
153 }
154
RoboErik19c95182014-06-23 15:38:48 -0700155 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
156 synchronized (mLock) {
157 if (!mAllSessions.contains(record)) {
158 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
159 return;
160 }
161 pushRemoteVolumeUpdateLocked(record.getUserId());
162 }
163 }
164
RoboErika278ea72014-04-24 14:49:01 -0700165 @Override
RoboErik4646d282014-05-13 10:13:04 -0700166 public void onStartUser(int userHandle) {
167 updateUser();
168 }
169
170 @Override
171 public void onSwitchUser(int userHandle) {
172 updateUser();
173 }
174
175 @Override
176 public void onStopUser(int userHandle) {
177 synchronized (mLock) {
178 UserRecord user = mUserRecords.get(userHandle);
179 if (user != null) {
180 destroyUserLocked(user);
181 }
182 }
183 }
184
185 @Override
RoboErika278ea72014-04-24 14:49:01 -0700186 public void monitor() {
187 synchronized (mLock) {
188 // Check for deadlock
189 }
190 }
191
RoboErik4646d282014-05-13 10:13:04 -0700192 protected void enforcePhoneStatePermission(int pid, int uid) {
193 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
194 != PackageManager.PERMISSION_GRANTED) {
195 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
196 }
197 }
198
RoboErik01fe6612014-02-13 14:19:04 -0800199 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700200 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800201 destroySessionLocked(session);
202 }
203 }
204
205 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700206 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800207 destroySessionLocked(session);
208 }
209 }
210
RoboErik4646d282014-05-13 10:13:04 -0700211 private void updateUser() {
212 synchronized (mLock) {
213 int userId = ActivityManager.getCurrentUser();
214 if (mCurrentUserId != userId) {
215 final int oldUserId = mCurrentUserId;
216 mCurrentUserId = userId; // do this first
217
218 UserRecord oldUser = mUserRecords.get(oldUserId);
219 if (oldUser != null) {
220 oldUser.stopLocked();
221 }
222
223 UserRecord newUser = getOrCreateUser(userId);
224 newUser.startLocked();
225 }
226 }
227 }
228
RoboErik7aef77b2014-08-08 15:56:54 -0700229 private void updateActiveSessionListeners() {
230 synchronized (mLock) {
231 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
232 SessionsListenerRecord listener = mSessionsListeners.get(i);
233 try {
234 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
235 listener.mUserId);
236 } catch (SecurityException e) {
237 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
238 + " is no longer authorized. Disconnecting.");
239 mSessionsListeners.remove(i);
240 try {
241 listener.mListener
242 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
243 } catch (Exception e1) {
244 // ignore
245 }
246 }
247 }
248 }
249 }
250
RoboErik4646d282014-05-13 10:13:04 -0700251 /**
252 * Stop the user and unbind from everything.
253 *
254 * @param user The user to dispose of
255 */
256 private void destroyUserLocked(UserRecord user) {
257 user.stopLocked();
258 user.destroyLocked();
259 mUserRecords.remove(user.mUserId);
260 }
261
262 /*
263 * When a session is removed several things need to happen.
264 * 1. We need to remove it from the relevant user.
265 * 2. We need to remove it from the priority stack.
266 * 3. We need to remove it from all sessions.
267 * 4. If this is the system priority session we need to clear it.
268 * 5. We need to unlink to death from the cb binder
269 * 6. We need to tell the session to do any final cleanup (onDestroy)
270 */
RoboErik01fe6612014-02-13 14:19:04 -0800271 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700272 int userId = session.getUserId();
273 UserRecord user = mUserRecords.get(userId);
274 if (user != null) {
275 user.removeSessionLocked(session);
276 }
277
RoboErika8f95142014-05-05 14:23:49 -0700278 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700279 mAllSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700280
281 try {
282 session.getCallback().asBinder().unlinkToDeath(session, 0);
283 } catch (Exception e) {
284 // ignore exceptions while destroying a session.
285 }
286 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700287
288 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800289 }
290
291 private void enforcePackageName(String packageName, int uid) {
292 if (TextUtils.isEmpty(packageName)) {
293 throw new IllegalArgumentException("packageName may not be empty");
294 }
295 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
296 final int packageCount = packages.length;
297 for (int i = 0; i < packageCount; i++) {
298 if (packageName.equals(packages[i])) {
299 return;
300 }
301 }
302 throw new IllegalArgumentException("packageName is not owned by the calling process");
303 }
304
RoboErike7880d82014-04-30 12:48:25 -0700305 /**
306 * Checks a caller's authorization to register an IRemoteControlDisplay.
307 * Authorization is granted if one of the following is true:
308 * <ul>
309 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
310 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700311 * <li>the caller's listener is one of the enabled notification listeners
312 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700313 * </ul>
314 */
RoboErika5b02322014-05-07 17:05:49 -0700315 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
316 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700317 if (getContext()
318 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
319 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700320 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
321 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700322 throw new SecurityException("Missing permission to control media.");
323 }
324 }
325
RoboErik19c95182014-06-23 15:38:48 -0700326 private void enforceStatusBarPermission(String action, int pid, int uid) {
327 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
328 pid, uid) != PackageManager.PERMISSION_GRANTED) {
329 throw new SecurityException("Only system ui may " + action);
330 }
331 }
332
RoboErika5b02322014-05-07 17:05:49 -0700333 /**
334 * This checks if the component is an enabled notification listener for the
335 * specified user. Enabled components may only operate on behalf of the user
336 * they're running as.
337 *
338 * @param compName The component that is enabled.
339 * @param userId The user id of the caller.
340 * @param forUserId The user id they're making the request on behalf of.
341 * @return True if the component is enabled, false otherwise
342 */
343 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
344 int forUserId) {
345 if (userId != forUserId) {
346 // You may not access another user's content as an enabled listener.
347 return false;
348 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700349 if (DEBUG) {
350 Log.d(TAG, "Checking if enabled notification listener " + compName);
351 }
RoboErike7880d82014-04-30 12:48:25 -0700352 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700353 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700354 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700355 userId);
RoboErike7880d82014-04-30 12:48:25 -0700356 if (enabledNotifListeners != null) {
357 final String[] components = enabledNotifListeners.split(":");
358 for (int i = 0; i < components.length; i++) {
359 final ComponentName component =
360 ComponentName.unflattenFromString(components[i]);
361 if (component != null) {
362 if (compName.equals(component)) {
363 if (DEBUG) {
364 Log.d(TAG, "ok to get sessions: " + component +
365 " is authorized notification listener");
366 }
367 return true;
368 }
369 }
370 }
371 }
372 if (DEBUG) {
373 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700374 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700375 }
376 }
377 return false;
378 }
379
RoboErika5b02322014-05-07 17:05:49 -0700380 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700381 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800382 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700383 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800384 }
385 }
386
RoboErik4646d282014-05-13 10:13:04 -0700387 /*
388 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700389 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700390 * 2. It needs to be added to all sessions.
391 * 3. It needs to be added to the priority stack.
392 * 4. It needs to be added to the relevant user record.
393 */
RoboErika5b02322014-05-07 17:05:49 -0700394 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
395 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700396
RoboErika5b02322014-05-07 17:05:49 -0700397 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
398 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800399 try {
400 cb.asBinder().linkToDeath(session, 0);
401 } catch (RemoteException e) {
402 throw new RuntimeException("Media Session owner died prematurely.", e);
403 }
RoboErik4646d282014-05-13 10:13:04 -0700404
405 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700406 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700407
408 UserRecord user = getOrCreateUser(userId);
409 user.addSessionLocked(session);
410
RoboErik2e7a9162014-06-04 16:53:45 -0700411 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
412
RoboErik01fe6612014-02-13 14:19:04 -0800413 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700414 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800415 }
416 return session;
417 }
418
RoboErik4646d282014-05-13 10:13:04 -0700419 private UserRecord getOrCreateUser(int userId) {
420 UserRecord user = mUserRecords.get(userId);
421 if (user == null) {
422 user = new UserRecord(getContext(), userId);
423 mUserRecords.put(userId, user);
424 }
425 return user;
426 }
427
RoboErik2e7a9162014-06-04 16:53:45 -0700428 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
429 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
RoboErika08adb242014-11-21 18:28:18 -0800430 if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
RoboErik2e7a9162014-06-04 16:53:45 -0700431 return i;
432 }
433 }
434 return -1;
435 }
436
RoboErike7880d82014-04-30 12:48:25 -0700437 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700438 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700439 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700440 }
441
RoboErik2e7a9162014-06-04 16:53:45 -0700442 private void pushSessionsChanged(int userId) {
443 synchronized (mLock) {
444 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
445 int size = records.size();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700446 if (size > 0) {
RoboErikb214efb2014-07-24 13:20:30 -0700447 rememberMediaButtonReceiverLocked(records.get(0));
RoboErik6f0e4dd2014-06-17 16:56:27 -0700448 }
Jeff Browndba34ba2014-06-24 20:46:03 -0700449 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700450 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700451 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700452 }
RoboErik19c95182014-06-23 15:38:48 -0700453 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700454 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
455 SessionsListenerRecord record = mSessionsListeners.get(i);
456 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
457 try {
458 record.mListener.onActiveSessionsChanged(tokens);
459 } catch (RemoteException e) {
460 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
461 e);
462 mSessionsListeners.remove(i);
463 }
464 }
465 }
466 }
467 }
468
RoboErik19c95182014-06-23 15:38:48 -0700469 private void pushRemoteVolumeUpdateLocked(int userId) {
470 if (mRvc != null) {
471 try {
472 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
473 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
474 } catch (RemoteException e) {
475 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
476 }
477 }
478 }
479
RoboErikb214efb2014-07-24 13:20:30 -0700480 private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
481 PendingIntent receiver = record.getMediaButtonReceiver();
482 UserRecord user = mUserRecords.get(record.getUserId());
483 if (receiver != null && user != null) {
484 user.mLastMediaButtonReceiver = receiver;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700485 }
486 }
487
RoboErik4646d282014-05-13 10:13:04 -0700488 /**
489 * Information about a particular user. The contents of this object is
490 * guarded by mLock.
491 */
492 final class UserRecord {
493 private final int mUserId;
RoboErik4646d282014-05-13 10:13:04 -0700494 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
RoboErikb214efb2014-07-24 13:20:30 -0700495 private PendingIntent mLastMediaButtonReceiver;
RoboErik4646d282014-05-13 10:13:04 -0700496
497 public UserRecord(Context context, int userId) {
498 mUserId = userId;
RoboErik4646d282014-05-13 10:13:04 -0700499 }
500
501 public void startLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700502 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700503 }
504
505 public void stopLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700506 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700507 }
508
509 public void destroyLocked() {
510 for (int i = mSessions.size() - 1; i >= 0; i--) {
511 MediaSessionRecord session = mSessions.get(i);
512 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700513 }
514 }
515
RoboErik4646d282014-05-13 10:13:04 -0700516 public ArrayList<MediaSessionRecord> getSessionsLocked() {
517 return mSessions;
518 }
519
520 public void addSessionLocked(MediaSessionRecord session) {
521 mSessions.add(session);
RoboErik4646d282014-05-13 10:13:04 -0700522 }
523
524 public void removeSessionLocked(MediaSessionRecord session) {
525 mSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700526 }
527
528 public void dumpLocked(PrintWriter pw, String prefix) {
529 pw.println(prefix + "Record for user " + mUserId);
530 String indent = prefix + " ";
RoboErikb214efb2014-07-24 13:20:30 -0700531 pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
Jeff Brown01a500e2014-07-10 22:50:50 -0700532 int size = mSessions.size();
RoboErik4646d282014-05-13 10:13:04 -0700533 pw.println(indent + size + " Sessions:");
534 for (int i = 0; i < size; i++) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700535 // Just print the short version, the full session dump will
RoboErik4646d282014-05-13 10:13:04 -0700536 // already be in the list of all sessions.
RoboErikaa4e23b2014-07-24 18:35:11 -0700537 pw.println(indent + mSessions.get(i).toString());
RoboErik4646d282014-05-13 10:13:04 -0700538 }
539 }
RoboErik4646d282014-05-13 10:13:04 -0700540 }
541
RoboErik2e7a9162014-06-04 16:53:45 -0700542 final class SessionsListenerRecord implements IBinder.DeathRecipient {
543 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700544 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700545 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700546 private final int mPid;
547 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700548
RoboErik7aef77b2014-08-08 15:56:54 -0700549 public SessionsListenerRecord(IActiveSessionsListener listener,
550 ComponentName componentName,
551 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700552 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700553 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700554 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700555 mPid = pid;
556 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700557 }
558
559 @Override
560 public void binderDied() {
561 synchronized (mLock) {
562 mSessionsListeners.remove(this);
563 }
564 }
565 }
566
RoboErik7aef77b2014-08-08 15:56:54 -0700567 final class SettingsObserver extends ContentObserver {
568 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
569 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
570
571 private SettingsObserver() {
572 super(null);
573 }
574
575 private void observe() {
576 mContentResolver.registerContentObserver(mSecureSettingsUri,
577 false, this, UserHandle.USER_ALL);
578 }
579
580 @Override
581 public void onChange(boolean selfChange, Uri uri) {
582 updateActiveSessionListeners();
583 }
584 }
585
RoboErik07c70772014-03-20 13:33:52 -0700586 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700587 private static final String EXTRA_WAKELOCK_ACQUIRED =
588 "android.media.AudioService.WAKELOCK_ACQUIRED";
589 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
590
RoboErik9a9d0b52014-05-20 14:53:39 -0700591 private boolean mVoiceButtonDown = false;
592 private boolean mVoiceButtonHandled = false;
593
RoboErik07c70772014-03-20 13:33:52 -0700594 @Override
RoboErika5b02322014-05-07 17:05:49 -0700595 public ISession createSession(String packageName, ISessionCallback cb, String tag,
596 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800597 final int pid = Binder.getCallingPid();
598 final int uid = Binder.getCallingUid();
599 final long token = Binder.clearCallingIdentity();
600 try {
601 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700602 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
603 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800604 if (cb == null) {
605 throw new IllegalArgumentException("Controller callback cannot be null");
606 }
RoboErika5b02322014-05-07 17:05:49 -0700607 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
608 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700609 } finally {
610 Binder.restoreCallingIdentity(token);
611 }
612 }
613
614 @Override
RoboErika5b02322014-05-07 17:05:49 -0700615 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700616 final int pid = Binder.getCallingPid();
617 final int uid = Binder.getCallingUid();
618 final long token = Binder.clearCallingIdentity();
619
620 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700621 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700622 ArrayList<IBinder> binders = new ArrayList<IBinder>();
623 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700624 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700625 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700626 int size = records.size();
627 for (int i = 0; i < size; i++) {
628 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700629 }
630 }
631 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800632 } finally {
633 Binder.restoreCallingIdentity(token);
634 }
635 }
RoboErika278ea72014-04-24 14:49:01 -0700636
RoboErik2e7a9162014-06-04 16:53:45 -0700637 @Override
638 public void addSessionsListener(IActiveSessionsListener listener,
639 ComponentName componentName, int userId) throws RemoteException {
640 final int pid = Binder.getCallingPid();
641 final int uid = Binder.getCallingUid();
642 final long token = Binder.clearCallingIdentity();
643
644 try {
645 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
646 synchronized (mLock) {
647 int index = findIndexOfSessionsListenerLocked(listener);
648 if (index != -1) {
649 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
650 return;
651 }
652 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -0700653 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -0700654 try {
655 listener.asBinder().linkToDeath(record, 0);
656 } catch (RemoteException e) {
657 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
658 return;
659 }
660 mSessionsListeners.add(record);
661 }
662 } finally {
663 Binder.restoreCallingIdentity(token);
664 }
665 }
666
667 @Override
668 public void removeSessionsListener(IActiveSessionsListener listener)
669 throws RemoteException {
670 synchronized (mLock) {
671 int index = findIndexOfSessionsListenerLocked(listener);
672 if (index != -1) {
673 SessionsListenerRecord record = mSessionsListeners.remove(index);
674 try {
675 record.mListener.asBinder().unlinkToDeath(record, 0);
676 } catch (Exception e) {
677 // ignore exceptions, the record is being removed
678 }
679 }
680 }
681 }
682
RoboErik8a2cfc32014-05-16 11:19:38 -0700683 /**
684 * Handles the dispatching of the media button events to one of the
685 * registered listeners, or if there was none, broadcast an
686 * ACTION_MEDIA_BUTTON intent to the rest of the system.
687 *
688 * @param keyEvent a non-null KeyEvent whose key code is one of the
689 * supported media buttons
690 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
691 * while this key event is dispatched.
692 */
693 @Override
694 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
695 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
696 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
697 return;
698 }
699 final int pid = Binder.getCallingPid();
700 final int uid = Binder.getCallingUid();
701 final long token = Binder.clearCallingIdentity();
702
703 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700704 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700705 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700706 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700707 if (isVoiceKey(keyEvent.getKeyCode())) {
708 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700709 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700710 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700711 }
712 }
713 } finally {
714 Binder.restoreCallingIdentity(token);
715 }
716 }
717
RoboErika278ea72014-04-24 14:49:01 -0700718 @Override
RoboErik1ff5b162014-07-15 17:23:18 -0700719 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
RoboErikb69ffd42014-05-30 14:57:59 -0700720 throws RemoteException {
721 final int pid = Binder.getCallingPid();
722 final int uid = Binder.getCallingUid();
723 final long token = Binder.clearCallingIdentity();
724 try {
725 synchronized (mLock) {
726 MediaSessionRecord session = mPriorityStack
727 .getDefaultVolumeSession(mCurrentUserId);
RoboErik1ff5b162014-07-15 17:23:18 -0700728 dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
RoboErikb69ffd42014-05-30 14:57:59 -0700729 }
730 } finally {
731 Binder.restoreCallingIdentity(token);
732 }
733 }
734
735 @Override
RoboErik19c95182014-06-23 15:38:48 -0700736 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
737 final int pid = Binder.getCallingPid();
738 final int uid = Binder.getCallingUid();
739 final long token = Binder.clearCallingIdentity();
740 try {
741 enforceStatusBarPermission("listen for volume changes", pid, uid);
742 mRvc = rvc;
743 } finally {
744 Binder.restoreCallingIdentity(token);
745 }
746 }
747
748 @Override
RoboErikde9ba392014-09-26 12:51:01 -0700749 public boolean isGlobalPriorityActive() {
750 return mPriorityStack.isGlobalPriorityActive();
751 }
752
753 @Override
RoboErika278ea72014-04-24 14:49:01 -0700754 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
755 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
756 != PackageManager.PERMISSION_GRANTED) {
757 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
758 + Binder.getCallingPid()
759 + ", uid=" + Binder.getCallingUid());
760 return;
761 }
762
763 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
764 pw.println();
765
766 synchronized (mLock) {
RoboErika08adb242014-11-21 18:28:18 -0800767 pw.println(mSessionsListeners.size() + " sessions listeners.");
RoboErik4646d282014-05-13 10:13:04 -0700768 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700769 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700770 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700771 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700772 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700773 }
RoboErika5b02322014-05-07 17:05:49 -0700774 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700775
RoboErik4646d282014-05-13 10:13:04 -0700776 pw.println("User Records:");
777 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700778 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700779 UserRecord user = mUserRecords.get(i);
780 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700781 }
782 }
783 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700784
RoboErik2e7a9162014-06-04 16:53:45 -0700785 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
786 final int uid) {
787 String packageName = null;
788 if (componentName != null) {
789 // If they gave us a component name verify they own the
790 // package
791 packageName = componentName.getPackageName();
792 enforcePackageName(packageName, uid);
793 }
794 // Check that they can make calls on behalf of the user and
795 // get the final user id
796 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
797 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
798 // Check if they have the permissions or their component is
799 // enabled for the user they're calling from.
800 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
801 return resolvedUserId;
802 }
803
RoboErik1ff5b162014-07-15 17:23:18 -0700804 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
RoboErikb69ffd42014-05-30 14:57:59 -0700805 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700806 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700807 String description = session == null ? null : session.toString();
808 Log.d(TAG, "Adjusting session " + description + " by " + direction + ". flags="
809 + flags + ", suggestedStream=" + suggestedStream);
RoboErikb69ffd42014-05-30 14:57:59 -0700810
811 }
RoboErik9c785402014-11-11 16:52:26 -0800812 boolean preferSuggestedStream = false;
813 if (isValidLocalStreamType(suggestedStream)
814 && AudioSystem.isStreamActive(suggestedStream, 0)) {
815 preferSuggestedStream = true;
816 }
817 if (session == null || preferSuggestedStream) {
RoboErik94c716e2014-09-14 13:54:31 -0700818 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
819 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
RoboErik3c45c292014-07-08 16:47:31 -0700820 if (DEBUG) {
821 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -0700822 }
RoboErikb7c014c2014-07-22 15:58:22 -0700823 return;
RoboErik3c45c292014-07-08 16:47:31 -0700824 }
RoboErik0791e172014-06-08 10:52:32 -0700825 try {
RoboErik519c7742014-11-18 10:59:09 -0800826 if (mUseMasterVolume) {
827 mAudioService.adjustMasterVolume(direction, flags,
828 getContext().getOpPackageName());
829 } else {
830 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
831 getContext().getOpPackageName());
832 }
RoboErik0791e172014-06-08 10:52:32 -0700833 } catch (RemoteException e) {
834 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700835 }
836 } else {
RoboErik0dac35a2014-08-12 15:48:49 -0700837 session.adjustVolume(direction, flags, getContext().getPackageName(),
RoboErik272e1612014-09-05 11:39:29 -0700838 UserHandle.myUserId(), true);
RoboErikd2b8c942014-08-19 11:23:40 -0700839 if (session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE
RoboErik851d2d52014-06-27 18:02:40 -0700840 && mRvc != null) {
RoboErik19c95182014-06-23 15:38:48 -0700841 try {
842 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
843 } catch (Exception e) {
844 Log.wtf(TAG, "Error sending volume change to system UI.", e);
845 }
846 }
RoboErikb69ffd42014-05-30 14:57:59 -0700847 }
848 }
849
RoboErik9a9d0b52014-05-20 14:53:39 -0700850 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
851 MediaSessionRecord session) {
852 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
853 // If the phone app has priority just give it the event
854 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
855 return;
856 }
857 int action = keyEvent.getAction();
858 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
859 if (action == KeyEvent.ACTION_DOWN) {
860 if (keyEvent.getRepeatCount() == 0) {
861 mVoiceButtonDown = true;
862 mVoiceButtonHandled = false;
863 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
864 mVoiceButtonHandled = true;
865 startVoiceInput(needWakeLock);
866 }
867 } else if (action == KeyEvent.ACTION_UP) {
868 if (mVoiceButtonDown) {
869 mVoiceButtonDown = false;
870 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
871 // Resend the down then send this event through
872 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
873 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
874 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
875 }
876 }
877 }
878 }
879
880 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
881 MediaSessionRecord session) {
882 if (session != null) {
883 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700884 Log.d(TAG, "Sending media key to " + session.toString());
RoboErik9a9d0b52014-05-20 14:53:39 -0700885 }
886 if (needWakeLock) {
887 mKeyEventReceiver.aquireWakeLockLocked();
888 }
889 // If we don't need a wakelock use -1 as the id so we
890 // won't release it later
891 session.sendMediaButton(keyEvent,
892 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
893 mKeyEventReceiver);
894 } else {
RoboErikb214efb2014-07-24 13:20:30 -0700895 // Launch the last PendingIntent we had with priority
896 int userId = ActivityManager.getCurrentUser();
897 UserRecord user = mUserRecords.get(userId);
898 if (user.mLastMediaButtonReceiver != null) {
899 if (DEBUG) {
900 Log.d(TAG, "Sending media key to last known PendingIntent");
901 }
902 if (needWakeLock) {
903 mKeyEventReceiver.aquireWakeLockLocked();
904 }
905 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
906 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
907 try {
908 user.mLastMediaButtonReceiver.send(getContext(),
909 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
910 mediaButtonIntent, mKeyEventReceiver, null);
911 } catch (CanceledException e) {
912 Log.i(TAG, "Error sending key event to media button receiver "
913 + user.mLastMediaButtonReceiver, e);
914 }
915 } else {
916 if (DEBUG) {
917 Log.d(TAG, "Sending media key ordered broadcast");
918 }
919 if (needWakeLock) {
920 mMediaEventWakeLock.acquire();
921 }
922 // Fallback to legacy behavior
923 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
924 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
925 if (needWakeLock) {
926 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
927 WAKELOCK_RELEASE_ON_FINISHED);
928 }
929 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
930 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
RoboErik9a9d0b52014-05-20 14:53:39 -0700931 }
RoboErik9a9d0b52014-05-20 14:53:39 -0700932 }
933 }
934
935 private void startVoiceInput(boolean needWakeLock) {
936 Intent voiceIntent = null;
937 // select which type of search to launch:
938 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
939 // - device locked or screen off: action is
940 // ACTION_VOICE_SEARCH_HANDS_FREE
941 // with EXTRA_SECURE set to true if the device is securely locked
942 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
943 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
944 if (!isLocked && pm.isScreenOn()) {
945 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
946 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
947 } else {
948 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
949 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
950 isLocked && mKeyguardManager.isKeyguardSecure());
951 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
952 }
953 // start the search activity
954 if (needWakeLock) {
955 mMediaEventWakeLock.acquire();
956 }
957 try {
958 if (voiceIntent != null) {
959 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
960 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
961 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
962 }
963 } catch (ActivityNotFoundException e) {
964 Log.w(TAG, "No activity for search: " + e);
965 } finally {
966 if (needWakeLock) {
967 mMediaEventWakeLock.release();
968 }
969 }
970 }
971
972 private boolean isVoiceKey(int keyCode) {
973 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
974 }
975
RoboErik9c785402014-11-11 16:52:26 -0800976 // we only handle public stream types, which are 0-5
977 private boolean isValidLocalStreamType(int streamType) {
978 return streamType >= AudioManager.STREAM_VOICE_CALL
979 && streamType <= AudioManager.STREAM_NOTIFICATION;
980 }
981
RoboErik418c10c2014-05-19 09:25:25 -0700982 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
983
RoboErikb214efb2014-07-24 13:20:30 -0700984 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
985 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -0700986 private final Handler mHandler;
987 private int mRefCount = 0;
988 private int mLastTimeoutId = 0;
989
990 public KeyEventWakeLockReceiver(Handler handler) {
991 super(handler);
992 mHandler = handler;
993 }
994
995 public void onTimeout() {
996 synchronized (mLock) {
997 if (mRefCount == 0) {
998 // We've already released it, so just return
999 return;
1000 }
1001 mLastTimeoutId++;
1002 mRefCount = 0;
1003 releaseWakeLockLocked();
1004 }
1005 }
1006
1007 public void aquireWakeLockLocked() {
1008 if (mRefCount == 0) {
1009 mMediaEventWakeLock.acquire();
1010 }
1011 mRefCount++;
1012 mHandler.removeCallbacks(this);
1013 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1014
1015 }
1016
1017 @Override
1018 public void run() {
1019 onTimeout();
1020 }
1021
RoboErik8a2cfc32014-05-16 11:19:38 -07001022 @Override
1023 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001024 if (resultCode < mLastTimeoutId) {
1025 // Ignore results from calls that were before the last
1026 // timeout, just in case.
1027 return;
1028 } else {
1029 synchronized (mLock) {
1030 if (mRefCount > 0) {
1031 mRefCount--;
1032 if (mRefCount == 0) {
1033 releaseWakeLockLocked();
1034 }
1035 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001036 }
1037 }
1038 }
RoboErik418c10c2014-05-19 09:25:25 -07001039
1040 private void releaseWakeLockLocked() {
1041 mMediaEventWakeLock.release();
1042 mHandler.removeCallbacks(this);
1043 }
RoboErikb214efb2014-07-24 13:20:30 -07001044
1045 @Override
1046 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1047 String resultData, Bundle resultExtras) {
1048 onReceiveResult(resultCode, null);
1049 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001050 };
1051
1052 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1053 @Override
1054 public void onReceive(Context context, Intent intent) {
1055 if (intent == null) {
1056 return;
1057 }
1058 Bundle extras = intent.getExtras();
1059 if (extras == null) {
1060 return;
1061 }
1062 synchronized (mLock) {
1063 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1064 && mMediaEventWakeLock.isHeld()) {
1065 mMediaEventWakeLock.release();
1066 }
1067 }
1068 }
1069 };
RoboErik01fe6612014-02-13 14:19:04 -08001070 }
1071
RoboErik2e7a9162014-06-04 16:53:45 -07001072 final class MessageHandler extends Handler {
1073 private static final int MSG_SESSIONS_CHANGED = 1;
1074
1075 @Override
1076 public void handleMessage(Message msg) {
1077 switch (msg.what) {
1078 case MSG_SESSIONS_CHANGED:
1079 pushSessionsChanged(msg.arg1);
1080 break;
1081 }
1082 }
1083
1084 public void post(int what, int arg1, int arg2) {
1085 obtainMessage(what, arg1, arg2).sendToTarget();
1086 }
1087 }
RoboErik01fe6612014-02-13 14:19:04 -08001088}