blob: b4ec607cc5ff6639853d56075ef119d94aac1f1b [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;
RoboErik7c82ced2014-12-04 17:39:08 -080043import android.media.session.MediaSessionManager;
RoboErik7aef77b2014-08-08 15:56:54 -070044import android.net.Uri;
RoboErik01fe6612014-02-13 14:19:04 -080045import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070046import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080047import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070048import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070049import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070050import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080051import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070052import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070053import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070054import android.os.UserHandle;
55import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070056import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080057import android.text.TextUtils;
58import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070059import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070060import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080061
62import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070063import com.android.server.Watchdog;
64import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080065
RoboErika278ea72014-04-24 14:49:01 -070066import java.io.FileDescriptor;
67import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080068import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070069import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080070
71/**
72 * System implementation of MediaSessionManager
73 */
RoboErika278ea72014-04-24 14:49:01 -070074public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080075 private static final String TAG = "MediaSessionService";
76 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
77
RoboErik418c10c2014-05-19 09:25:25 -070078 private static final int WAKELOCK_TIMEOUT = 5000;
79
RoboErik01fe6612014-02-13 14:19:04 -080080 private final SessionManagerImpl mSessionManagerImpl;
RoboErika8f95142014-05-05 14:23:49 -070081 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080082
RoboErik4646d282014-05-13 10:13:04 -070083 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
84 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070085 private final ArrayList<SessionsListenerRecord> mSessionsListeners
86 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -080087 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070088 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070089 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik519c7742014-11-18 10:59:09 -080090 private final boolean mUseMasterVolume;
RoboErik01fe6612014-02-13 14:19:04 -080091
RoboErik9a9d0b52014-05-20 14:53:39 -070092 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070093 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070094 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -070095 private SettingsObserver mSettingsObserver;
RoboErik9a9d0b52014-05-20 14:53:39 -070096
RoboErik4646d282014-05-13 10:13:04 -070097 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070098
RoboErik19c95182014-06-23 15:38:48 -070099 // Used to notify system UI when remote volume was changed. TODO find a
100 // better way to handle this.
101 private IRemoteVolumeController mRvc;
102
RoboErik01fe6612014-02-13 14:19:04 -0800103 public MediaSessionService(Context context) {
104 super(context);
105 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700106 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700107 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
108 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik519c7742014-11-18 10:59:09 -0800109 mUseMasterVolume = context.getResources().getBoolean(
110 com.android.internal.R.bool.config_useMasterVolume);
RoboErik01fe6612014-02-13 14:19:04 -0800111 }
112
113 @Override
114 public void onStart() {
115 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700116 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700117 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700118 mKeyguardManager =
119 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700120 mAudioService = getAudioService();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700121 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700122 mSettingsObserver = new SettingsObserver();
123 mSettingsObserver.observe();
RoboErikb69ffd42014-05-30 14:57:59 -0700124 }
125
126 private IAudioService getAudioService() {
127 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
128 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700129 }
130
RoboErika8f95142014-05-05 14:23:49 -0700131 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700132 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700133 if (!mAllSessions.contains(record)) {
134 Log.d(TAG, "Unknown session updated. Ignoring.");
135 return;
136 }
RoboErika8f95142014-05-05 14:23:49 -0700137 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700138 }
RoboErik2e7a9162014-06-04 16:53:45 -0700139 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700140 }
141
RoboErika8f95142014-05-05 14:23:49 -0700142 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700143 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700144 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700145 if (!mAllSessions.contains(record)) {
146 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
147 return;
148 }
RoboErik2e7a9162014-06-04 16:53:45 -0700149 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
150 }
151 if (updateSessions) {
152 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700153 }
154 }
155
RoboErik19c95182014-06-23 15:38:48 -0700156 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
157 synchronized (mLock) {
158 if (!mAllSessions.contains(record)) {
159 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
160 return;
161 }
162 pushRemoteVolumeUpdateLocked(record.getUserId());
163 }
164 }
165
RoboErika278ea72014-04-24 14:49:01 -0700166 @Override
RoboErik4646d282014-05-13 10:13:04 -0700167 public void onStartUser(int userHandle) {
168 updateUser();
169 }
170
171 @Override
172 public void onSwitchUser(int userHandle) {
173 updateUser();
174 }
175
176 @Override
177 public void onStopUser(int userHandle) {
178 synchronized (mLock) {
179 UserRecord user = mUserRecords.get(userHandle);
180 if (user != null) {
181 destroyUserLocked(user);
182 }
183 }
184 }
185
186 @Override
RoboErika278ea72014-04-24 14:49:01 -0700187 public void monitor() {
188 synchronized (mLock) {
189 // Check for deadlock
190 }
191 }
192
RoboErik4646d282014-05-13 10:13:04 -0700193 protected void enforcePhoneStatePermission(int pid, int uid) {
194 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
195 != PackageManager.PERMISSION_GRANTED) {
196 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
197 }
198 }
199
RoboErik01fe6612014-02-13 14:19:04 -0800200 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700201 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800202 destroySessionLocked(session);
203 }
204 }
205
206 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700207 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800208 destroySessionLocked(session);
209 }
210 }
211
RoboErik4646d282014-05-13 10:13:04 -0700212 private void updateUser() {
213 synchronized (mLock) {
214 int userId = ActivityManager.getCurrentUser();
215 if (mCurrentUserId != userId) {
216 final int oldUserId = mCurrentUserId;
217 mCurrentUserId = userId; // do this first
218
219 UserRecord oldUser = mUserRecords.get(oldUserId);
220 if (oldUser != null) {
221 oldUser.stopLocked();
222 }
223
224 UserRecord newUser = getOrCreateUser(userId);
225 newUser.startLocked();
226 }
227 }
228 }
229
RoboErik7aef77b2014-08-08 15:56:54 -0700230 private void updateActiveSessionListeners() {
231 synchronized (mLock) {
232 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
233 SessionsListenerRecord listener = mSessionsListeners.get(i);
234 try {
235 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
236 listener.mUserId);
237 } catch (SecurityException e) {
238 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
239 + " is no longer authorized. Disconnecting.");
240 mSessionsListeners.remove(i);
241 try {
242 listener.mListener
243 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
244 } catch (Exception e1) {
245 // ignore
246 }
247 }
248 }
249 }
250 }
251
RoboErik4646d282014-05-13 10:13:04 -0700252 /**
253 * Stop the user and unbind from everything.
254 *
255 * @param user The user to dispose of
256 */
257 private void destroyUserLocked(UserRecord user) {
258 user.stopLocked();
259 user.destroyLocked();
260 mUserRecords.remove(user.mUserId);
261 }
262
263 /*
264 * When a session is removed several things need to happen.
265 * 1. We need to remove it from the relevant user.
266 * 2. We need to remove it from the priority stack.
267 * 3. We need to remove it from all sessions.
268 * 4. If this is the system priority session we need to clear it.
269 * 5. We need to unlink to death from the cb binder
270 * 6. We need to tell the session to do any final cleanup (onDestroy)
271 */
RoboErik01fe6612014-02-13 14:19:04 -0800272 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700273 int userId = session.getUserId();
274 UserRecord user = mUserRecords.get(userId);
275 if (user != null) {
276 user.removeSessionLocked(session);
277 }
278
RoboErika8f95142014-05-05 14:23:49 -0700279 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700280 mAllSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700281
282 try {
283 session.getCallback().asBinder().unlinkToDeath(session, 0);
284 } catch (Exception e) {
285 // ignore exceptions while destroying a session.
286 }
287 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700288
289 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800290 }
291
292 private void enforcePackageName(String packageName, int uid) {
293 if (TextUtils.isEmpty(packageName)) {
294 throw new IllegalArgumentException("packageName may not be empty");
295 }
296 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
297 final int packageCount = packages.length;
298 for (int i = 0; i < packageCount; i++) {
299 if (packageName.equals(packages[i])) {
300 return;
301 }
302 }
303 throw new IllegalArgumentException("packageName is not owned by the calling process");
304 }
305
RoboErike7880d82014-04-30 12:48:25 -0700306 /**
307 * Checks a caller's authorization to register an IRemoteControlDisplay.
308 * Authorization is granted if one of the following is true:
309 * <ul>
310 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
311 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700312 * <li>the caller's listener is one of the enabled notification listeners
313 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700314 * </ul>
315 */
RoboErika5b02322014-05-07 17:05:49 -0700316 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
317 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700318 if (getContext()
319 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
320 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700321 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
322 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700323 throw new SecurityException("Missing permission to control media.");
324 }
325 }
326
RoboErik19c95182014-06-23 15:38:48 -0700327 private void enforceStatusBarPermission(String action, int pid, int uid) {
328 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
329 pid, uid) != PackageManager.PERMISSION_GRANTED) {
330 throw new SecurityException("Only system ui may " + action);
331 }
332 }
333
RoboErika5b02322014-05-07 17:05:49 -0700334 /**
335 * This checks if the component is an enabled notification listener for the
336 * specified user. Enabled components may only operate on behalf of the user
337 * they're running as.
338 *
339 * @param compName The component that is enabled.
340 * @param userId The user id of the caller.
341 * @param forUserId The user id they're making the request on behalf of.
342 * @return True if the component is enabled, false otherwise
343 */
344 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
345 int forUserId) {
346 if (userId != forUserId) {
347 // You may not access another user's content as an enabled listener.
348 return false;
349 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700350 if (DEBUG) {
351 Log.d(TAG, "Checking if enabled notification listener " + compName);
352 }
RoboErike7880d82014-04-30 12:48:25 -0700353 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700354 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700355 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700356 userId);
RoboErike7880d82014-04-30 12:48:25 -0700357 if (enabledNotifListeners != null) {
358 final String[] components = enabledNotifListeners.split(":");
359 for (int i = 0; i < components.length; i++) {
360 final ComponentName component =
361 ComponentName.unflattenFromString(components[i]);
362 if (component != null) {
363 if (compName.equals(component)) {
364 if (DEBUG) {
365 Log.d(TAG, "ok to get sessions: " + component +
366 " is authorized notification listener");
367 }
368 return true;
369 }
370 }
371 }
372 }
373 if (DEBUG) {
374 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700375 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700376 }
377 }
378 return false;
379 }
380
RoboErika5b02322014-05-07 17:05:49 -0700381 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700382 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800383 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700384 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800385 }
386 }
387
RoboErik4646d282014-05-13 10:13:04 -0700388 /*
389 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700390 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700391 * 2. It needs to be added to all sessions.
392 * 3. It needs to be added to the priority stack.
393 * 4. It needs to be added to the relevant user record.
394 */
RoboErika5b02322014-05-07 17:05:49 -0700395 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
396 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700397
RoboErika5b02322014-05-07 17:05:49 -0700398 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
399 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800400 try {
401 cb.asBinder().linkToDeath(session, 0);
402 } catch (RemoteException e) {
403 throw new RuntimeException("Media Session owner died prematurely.", e);
404 }
RoboErik4646d282014-05-13 10:13:04 -0700405
406 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700407 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700408
409 UserRecord user = getOrCreateUser(userId);
410 user.addSessionLocked(session);
411
RoboErik2e7a9162014-06-04 16:53:45 -0700412 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
413
RoboErik01fe6612014-02-13 14:19:04 -0800414 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700415 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800416 }
417 return session;
418 }
419
RoboErik4646d282014-05-13 10:13:04 -0700420 private UserRecord getOrCreateUser(int userId) {
421 UserRecord user = mUserRecords.get(userId);
422 if (user == null) {
423 user = new UserRecord(getContext(), userId);
424 mUserRecords.put(userId, user);
425 }
426 return user;
427 }
428
RoboErik2e7a9162014-06-04 16:53:45 -0700429 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
430 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
RoboErika08adb242014-11-21 18:28:18 -0800431 if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
RoboErik2e7a9162014-06-04 16:53:45 -0700432 return i;
433 }
434 }
435 return -1;
436 }
437
RoboErike7880d82014-04-30 12:48:25 -0700438 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700439 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700440 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700441 }
442
RoboErik2e7a9162014-06-04 16:53:45 -0700443 private void pushSessionsChanged(int userId) {
444 synchronized (mLock) {
445 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
446 int size = records.size();
RoboErik870c5a62014-12-02 15:08:26 -0800447 if (size > 0 && records.get(0).isPlaybackActive(false)) {
RoboErikb214efb2014-07-24 13:20:30 -0700448 rememberMediaButtonReceiverLocked(records.get(0));
RoboErik6f0e4dd2014-06-17 16:56:27 -0700449 }
Jeff Browndba34ba2014-06-24 20:46:03 -0700450 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700451 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700452 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700453 }
RoboErik19c95182014-06-23 15:38:48 -0700454 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700455 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
456 SessionsListenerRecord record = mSessionsListeners.get(i);
457 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
458 try {
459 record.mListener.onActiveSessionsChanged(tokens);
460 } catch (RemoteException e) {
461 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
462 e);
463 mSessionsListeners.remove(i);
464 }
465 }
466 }
467 }
468 }
469
RoboErik19c95182014-06-23 15:38:48 -0700470 private void pushRemoteVolumeUpdateLocked(int userId) {
471 if (mRvc != null) {
472 try {
473 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
474 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
475 } catch (RemoteException e) {
476 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
477 }
478 }
479 }
480
RoboErikb214efb2014-07-24 13:20:30 -0700481 private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
482 PendingIntent receiver = record.getMediaButtonReceiver();
483 UserRecord user = mUserRecords.get(record.getUserId());
484 if (receiver != null && user != null) {
485 user.mLastMediaButtonReceiver = receiver;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700486 }
487 }
488
RoboErik4646d282014-05-13 10:13:04 -0700489 /**
490 * Information about a particular user. The contents of this object is
491 * guarded by mLock.
492 */
493 final class UserRecord {
494 private final int mUserId;
RoboErik4646d282014-05-13 10:13:04 -0700495 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
RoboErikb214efb2014-07-24 13:20:30 -0700496 private PendingIntent mLastMediaButtonReceiver;
RoboErik4646d282014-05-13 10:13:04 -0700497
498 public UserRecord(Context context, int userId) {
499 mUserId = userId;
RoboErik4646d282014-05-13 10:13:04 -0700500 }
501
502 public void startLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700503 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700504 }
505
506 public void stopLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700507 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700508 }
509
510 public void destroyLocked() {
511 for (int i = mSessions.size() - 1; i >= 0; i--) {
512 MediaSessionRecord session = mSessions.get(i);
513 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700514 }
515 }
516
RoboErik4646d282014-05-13 10:13:04 -0700517 public ArrayList<MediaSessionRecord> getSessionsLocked() {
518 return mSessions;
519 }
520
521 public void addSessionLocked(MediaSessionRecord session) {
522 mSessions.add(session);
RoboErik4646d282014-05-13 10:13:04 -0700523 }
524
525 public void removeSessionLocked(MediaSessionRecord session) {
526 mSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700527 }
528
529 public void dumpLocked(PrintWriter pw, String prefix) {
530 pw.println(prefix + "Record for user " + mUserId);
531 String indent = prefix + " ";
RoboErikb214efb2014-07-24 13:20:30 -0700532 pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
Jeff Brown01a500e2014-07-10 22:50:50 -0700533 int size = mSessions.size();
RoboErik4646d282014-05-13 10:13:04 -0700534 pw.println(indent + size + " Sessions:");
535 for (int i = 0; i < size; i++) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700536 // Just print the short version, the full session dump will
RoboErik4646d282014-05-13 10:13:04 -0700537 // already be in the list of all sessions.
RoboErikaa4e23b2014-07-24 18:35:11 -0700538 pw.println(indent + mSessions.get(i).toString());
RoboErik4646d282014-05-13 10:13:04 -0700539 }
540 }
RoboErik4646d282014-05-13 10:13:04 -0700541 }
542
RoboErik2e7a9162014-06-04 16:53:45 -0700543 final class SessionsListenerRecord implements IBinder.DeathRecipient {
544 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700545 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700546 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700547 private final int mPid;
548 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700549
RoboErik7aef77b2014-08-08 15:56:54 -0700550 public SessionsListenerRecord(IActiveSessionsListener listener,
551 ComponentName componentName,
552 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700553 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700554 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700555 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700556 mPid = pid;
557 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700558 }
559
560 @Override
561 public void binderDied() {
562 synchronized (mLock) {
563 mSessionsListeners.remove(this);
564 }
565 }
566 }
567
RoboErik7aef77b2014-08-08 15:56:54 -0700568 final class SettingsObserver extends ContentObserver {
569 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
570 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
571
572 private SettingsObserver() {
573 super(null);
574 }
575
576 private void observe() {
577 mContentResolver.registerContentObserver(mSecureSettingsUri,
578 false, this, UserHandle.USER_ALL);
579 }
580
581 @Override
582 public void onChange(boolean selfChange, Uri uri) {
583 updateActiveSessionListeners();
584 }
585 }
586
RoboErik07c70772014-03-20 13:33:52 -0700587 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700588 private static final String EXTRA_WAKELOCK_ACQUIRED =
589 "android.media.AudioService.WAKELOCK_ACQUIRED";
590 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
591
RoboErik7c82ced2014-12-04 17:39:08 -0800592 private final IBinder mICallback = new Binder();
593
RoboErik9a9d0b52014-05-20 14:53:39 -0700594 private boolean mVoiceButtonDown = false;
595 private boolean mVoiceButtonHandled = false;
596
RoboErik07c70772014-03-20 13:33:52 -0700597 @Override
RoboErika5b02322014-05-07 17:05:49 -0700598 public ISession createSession(String packageName, ISessionCallback cb, String tag,
599 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800600 final int pid = Binder.getCallingPid();
601 final int uid = Binder.getCallingUid();
602 final long token = Binder.clearCallingIdentity();
603 try {
604 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700605 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
606 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800607 if (cb == null) {
608 throw new IllegalArgumentException("Controller callback cannot be null");
609 }
RoboErika5b02322014-05-07 17:05:49 -0700610 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
611 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700612 } finally {
613 Binder.restoreCallingIdentity(token);
614 }
615 }
616
617 @Override
RoboErika5b02322014-05-07 17:05:49 -0700618 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700619 final int pid = Binder.getCallingPid();
620 final int uid = Binder.getCallingUid();
621 final long token = Binder.clearCallingIdentity();
622
623 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700624 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700625 ArrayList<IBinder> binders = new ArrayList<IBinder>();
626 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700627 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700628 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700629 int size = records.size();
630 for (int i = 0; i < size; i++) {
631 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700632 }
633 }
634 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800635 } finally {
636 Binder.restoreCallingIdentity(token);
637 }
638 }
RoboErika278ea72014-04-24 14:49:01 -0700639
RoboErik2e7a9162014-06-04 16:53:45 -0700640 @Override
641 public void addSessionsListener(IActiveSessionsListener listener,
642 ComponentName componentName, int userId) throws RemoteException {
643 final int pid = Binder.getCallingPid();
644 final int uid = Binder.getCallingUid();
645 final long token = Binder.clearCallingIdentity();
646
647 try {
648 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
649 synchronized (mLock) {
650 int index = findIndexOfSessionsListenerLocked(listener);
651 if (index != -1) {
652 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
653 return;
654 }
655 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -0700656 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -0700657 try {
658 listener.asBinder().linkToDeath(record, 0);
659 } catch (RemoteException e) {
660 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
661 return;
662 }
663 mSessionsListeners.add(record);
664 }
665 } finally {
666 Binder.restoreCallingIdentity(token);
667 }
668 }
669
670 @Override
671 public void removeSessionsListener(IActiveSessionsListener listener)
672 throws RemoteException {
673 synchronized (mLock) {
674 int index = findIndexOfSessionsListenerLocked(listener);
675 if (index != -1) {
676 SessionsListenerRecord record = mSessionsListeners.remove(index);
677 try {
678 record.mListener.asBinder().unlinkToDeath(record, 0);
679 } catch (Exception e) {
680 // ignore exceptions, the record is being removed
681 }
682 }
683 }
684 }
685
RoboErik8a2cfc32014-05-16 11:19:38 -0700686 /**
687 * Handles the dispatching of the media button events to one of the
688 * registered listeners, or if there was none, broadcast an
689 * ACTION_MEDIA_BUTTON intent to the rest of the system.
690 *
691 * @param keyEvent a non-null KeyEvent whose key code is one of the
692 * supported media buttons
693 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
694 * while this key event is dispatched.
695 */
696 @Override
697 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
698 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
699 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
700 return;
701 }
702 final int pid = Binder.getCallingPid();
703 final int uid = Binder.getCallingUid();
704 final long token = Binder.clearCallingIdentity();
705
706 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700707 synchronized (mLock) {
RoboErik870c5a62014-12-02 15:08:26 -0800708 // If we don't have a media button receiver to fall back on
709 // include non-playing sessions for dispatching
710 boolean useNotPlayingSessions = mUserRecords.get(
711 ActivityManager.getCurrentUser()).mLastMediaButtonReceiver == null;
RoboErik9a9d0b52014-05-20 14:53:39 -0700712 MediaSessionRecord session = mPriorityStack
RoboErik870c5a62014-12-02 15:08:26 -0800713 .getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions);
RoboErik9a9d0b52014-05-20 14:53:39 -0700714 if (isVoiceKey(keyEvent.getKeyCode())) {
715 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700716 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700717 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700718 }
719 }
720 } finally {
721 Binder.restoreCallingIdentity(token);
722 }
723 }
724
RoboErika278ea72014-04-24 14:49:01 -0700725 @Override
RoboErik7c82ced2014-12-04 17:39:08 -0800726 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
RoboErikb69ffd42014-05-30 14:57:59 -0700727 final int pid = Binder.getCallingPid();
728 final int uid = Binder.getCallingUid();
729 final long token = Binder.clearCallingIdentity();
730 try {
731 synchronized (mLock) {
732 MediaSessionRecord session = mPriorityStack
733 .getDefaultVolumeSession(mCurrentUserId);
RoboErik1ff5b162014-07-15 17:23:18 -0700734 dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
RoboErikb69ffd42014-05-30 14:57:59 -0700735 }
736 } finally {
737 Binder.restoreCallingIdentity(token);
738 }
739 }
740
741 @Override
RoboErik19c95182014-06-23 15:38:48 -0700742 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
743 final int pid = Binder.getCallingPid();
744 final int uid = Binder.getCallingUid();
745 final long token = Binder.clearCallingIdentity();
746 try {
747 enforceStatusBarPermission("listen for volume changes", pid, uid);
748 mRvc = rvc;
749 } finally {
750 Binder.restoreCallingIdentity(token);
751 }
752 }
753
754 @Override
RoboErikde9ba392014-09-26 12:51:01 -0700755 public boolean isGlobalPriorityActive() {
756 return mPriorityStack.isGlobalPriorityActive();
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) {
RoboErika08adb242014-11-21 18:28:18 -0800773 pw.println(mSessionsListeners.size() + " sessions listeners.");
RoboErik4646d282014-05-13 10:13:04 -0700774 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700775 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700776 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700777 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700778 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700779 }
RoboErika5b02322014-05-07 17:05:49 -0700780 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700781
RoboErik4646d282014-05-13 10:13:04 -0700782 pw.println("User Records:");
783 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700784 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700785 UserRecord user = mUserRecords.get(i);
786 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700787 }
788 }
789 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700790
RoboErik2e7a9162014-06-04 16:53:45 -0700791 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
792 final int uid) {
793 String packageName = null;
794 if (componentName != null) {
795 // If they gave us a component name verify they own the
796 // package
797 packageName = componentName.getPackageName();
798 enforcePackageName(packageName, uid);
799 }
800 // Check that they can make calls on behalf of the user and
801 // get the final user id
802 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
803 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
804 // Check if they have the permissions or their component is
805 // enabled for the user they're calling from.
806 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
807 return resolvedUserId;
808 }
809
RoboErik1ff5b162014-07-15 17:23:18 -0700810 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
RoboErikb69ffd42014-05-30 14:57:59 -0700811 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700812 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700813 String description = session == null ? null : session.toString();
814 Log.d(TAG, "Adjusting session " + description + " by " + direction + ". flags="
815 + flags + ", suggestedStream=" + suggestedStream);
RoboErikb69ffd42014-05-30 14:57:59 -0700816
817 }
RoboErik9c785402014-11-11 16:52:26 -0800818 boolean preferSuggestedStream = false;
819 if (isValidLocalStreamType(suggestedStream)
820 && AudioSystem.isStreamActive(suggestedStream, 0)) {
821 preferSuggestedStream = true;
822 }
823 if (session == null || preferSuggestedStream) {
RoboErik94c716e2014-09-14 13:54:31 -0700824 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
825 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
RoboErik3c45c292014-07-08 16:47:31 -0700826 if (DEBUG) {
827 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -0700828 }
RoboErikb7c014c2014-07-22 15:58:22 -0700829 return;
RoboErik3c45c292014-07-08 16:47:31 -0700830 }
RoboErik0791e172014-06-08 10:52:32 -0700831 try {
RoboErik519c7742014-11-18 10:59:09 -0800832 if (mUseMasterVolume) {
RoboErik7c82ced2014-12-04 17:39:08 -0800833 if (direction == MediaSessionManager.DIRECTION_MUTE) {
834 mAudioService.setMasterMute(!mAudioService.isMasterMute(), flags,
835 getContext().getOpPackageName(), mICallback);
836 } else {
837 mAudioService.adjustMasterVolume(direction, flags,
838 getContext().getOpPackageName());
839 }
RoboErik519c7742014-11-18 10:59:09 -0800840 } else {
RoboErik7c82ced2014-12-04 17:39:08 -0800841 if (direction == MediaSessionManager.DIRECTION_MUTE) {
842 mAudioService.setStreamMute(suggestedStream,
843 !mAudioService.isStreamMute(suggestedStream), mICallback);
844 } else {
845 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
846 flags, getContext().getOpPackageName());
847 }
RoboErik519c7742014-11-18 10:59:09 -0800848 }
RoboErik0791e172014-06-08 10:52:32 -0700849 } catch (RemoteException e) {
850 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700851 }
852 } else {
RoboErik0dac35a2014-08-12 15:48:49 -0700853 session.adjustVolume(direction, flags, getContext().getPackageName(),
RoboErik272e1612014-09-05 11:39:29 -0700854 UserHandle.myUserId(), true);
RoboErikd2b8c942014-08-19 11:23:40 -0700855 if (session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE
RoboErik7c82ced2014-12-04 17:39:08 -0800856 && mRvc != null && direction != MediaSessionManager.DIRECTION_MUTE) {
RoboErik19c95182014-06-23 15:38:48 -0700857 try {
858 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
859 } catch (Exception e) {
860 Log.wtf(TAG, "Error sending volume change to system UI.", e);
861 }
862 }
RoboErikb69ffd42014-05-30 14:57:59 -0700863 }
864 }
865
RoboErik9a9d0b52014-05-20 14:53:39 -0700866 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
867 MediaSessionRecord session) {
868 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
869 // If the phone app has priority just give it the event
870 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
871 return;
872 }
873 int action = keyEvent.getAction();
874 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
875 if (action == KeyEvent.ACTION_DOWN) {
876 if (keyEvent.getRepeatCount() == 0) {
877 mVoiceButtonDown = true;
878 mVoiceButtonHandled = false;
879 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
880 mVoiceButtonHandled = true;
881 startVoiceInput(needWakeLock);
882 }
883 } else if (action == KeyEvent.ACTION_UP) {
884 if (mVoiceButtonDown) {
885 mVoiceButtonDown = false;
886 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
887 // Resend the down then send this event through
888 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
889 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
890 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
891 }
892 }
893 }
894 }
895
896 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
897 MediaSessionRecord session) {
898 if (session != null) {
899 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700900 Log.d(TAG, "Sending media key to " + session.toString());
RoboErik9a9d0b52014-05-20 14:53:39 -0700901 }
902 if (needWakeLock) {
903 mKeyEventReceiver.aquireWakeLockLocked();
904 }
905 // If we don't need a wakelock use -1 as the id so we
906 // won't release it later
907 session.sendMediaButton(keyEvent,
908 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
909 mKeyEventReceiver);
910 } else {
RoboErikb214efb2014-07-24 13:20:30 -0700911 // Launch the last PendingIntent we had with priority
912 int userId = ActivityManager.getCurrentUser();
913 UserRecord user = mUserRecords.get(userId);
914 if (user.mLastMediaButtonReceiver != null) {
915 if (DEBUG) {
916 Log.d(TAG, "Sending media key to last known PendingIntent");
917 }
918 if (needWakeLock) {
919 mKeyEventReceiver.aquireWakeLockLocked();
920 }
921 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
922 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
923 try {
924 user.mLastMediaButtonReceiver.send(getContext(),
925 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
926 mediaButtonIntent, mKeyEventReceiver, null);
927 } catch (CanceledException e) {
928 Log.i(TAG, "Error sending key event to media button receiver "
929 + user.mLastMediaButtonReceiver, e);
930 }
931 } else {
932 if (DEBUG) {
933 Log.d(TAG, "Sending media key ordered broadcast");
934 }
935 if (needWakeLock) {
936 mMediaEventWakeLock.acquire();
937 }
938 // Fallback to legacy behavior
939 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
940 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
941 if (needWakeLock) {
942 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
943 WAKELOCK_RELEASE_ON_FINISHED);
944 }
945 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
946 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
RoboErik9a9d0b52014-05-20 14:53:39 -0700947 }
RoboErik9a9d0b52014-05-20 14:53:39 -0700948 }
949 }
950
951 private void startVoiceInput(boolean needWakeLock) {
952 Intent voiceIntent = null;
953 // select which type of search to launch:
954 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
955 // - device locked or screen off: action is
956 // ACTION_VOICE_SEARCH_HANDS_FREE
957 // with EXTRA_SECURE set to true if the device is securely locked
958 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
959 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
960 if (!isLocked && pm.isScreenOn()) {
961 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
962 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
963 } else {
964 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
965 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
966 isLocked && mKeyguardManager.isKeyguardSecure());
967 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
968 }
969 // start the search activity
970 if (needWakeLock) {
971 mMediaEventWakeLock.acquire();
972 }
973 try {
974 if (voiceIntent != null) {
975 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
976 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
977 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
978 }
979 } catch (ActivityNotFoundException e) {
980 Log.w(TAG, "No activity for search: " + e);
981 } finally {
982 if (needWakeLock) {
983 mMediaEventWakeLock.release();
984 }
985 }
986 }
987
988 private boolean isVoiceKey(int keyCode) {
989 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
990 }
991
RoboErik9c785402014-11-11 16:52:26 -0800992 // we only handle public stream types, which are 0-5
993 private boolean isValidLocalStreamType(int streamType) {
994 return streamType >= AudioManager.STREAM_VOICE_CALL
995 && streamType <= AudioManager.STREAM_NOTIFICATION;
996 }
997
RoboErik418c10c2014-05-19 09:25:25 -0700998 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
999
RoboErikb214efb2014-07-24 13:20:30 -07001000 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
1001 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -07001002 private final Handler mHandler;
1003 private int mRefCount = 0;
1004 private int mLastTimeoutId = 0;
1005
1006 public KeyEventWakeLockReceiver(Handler handler) {
1007 super(handler);
1008 mHandler = handler;
1009 }
1010
1011 public void onTimeout() {
1012 synchronized (mLock) {
1013 if (mRefCount == 0) {
1014 // We've already released it, so just return
1015 return;
1016 }
1017 mLastTimeoutId++;
1018 mRefCount = 0;
1019 releaseWakeLockLocked();
1020 }
1021 }
1022
1023 public void aquireWakeLockLocked() {
1024 if (mRefCount == 0) {
1025 mMediaEventWakeLock.acquire();
1026 }
1027 mRefCount++;
1028 mHandler.removeCallbacks(this);
1029 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1030
1031 }
1032
1033 @Override
1034 public void run() {
1035 onTimeout();
1036 }
1037
RoboErik8a2cfc32014-05-16 11:19:38 -07001038 @Override
1039 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001040 if (resultCode < mLastTimeoutId) {
1041 // Ignore results from calls that were before the last
1042 // timeout, just in case.
1043 return;
1044 } else {
1045 synchronized (mLock) {
1046 if (mRefCount > 0) {
1047 mRefCount--;
1048 if (mRefCount == 0) {
1049 releaseWakeLockLocked();
1050 }
1051 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001052 }
1053 }
1054 }
RoboErik418c10c2014-05-19 09:25:25 -07001055
1056 private void releaseWakeLockLocked() {
1057 mMediaEventWakeLock.release();
1058 mHandler.removeCallbacks(this);
1059 }
RoboErikb214efb2014-07-24 13:20:30 -07001060
1061 @Override
1062 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1063 String resultData, Bundle resultExtras) {
1064 onReceiveResult(resultCode, null);
1065 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001066 };
1067
1068 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1069 @Override
1070 public void onReceive(Context context, Intent intent) {
1071 if (intent == null) {
1072 return;
1073 }
1074 Bundle extras = intent.getExtras();
1075 if (extras == null) {
1076 return;
1077 }
1078 synchronized (mLock) {
1079 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1080 && mMediaEventWakeLock.isHeld()) {
1081 mMediaEventWakeLock.release();
1082 }
1083 }
1084 }
1085 };
RoboErik01fe6612014-02-13 14:19:04 -08001086 }
1087
RoboErik2e7a9162014-06-04 16:53:45 -07001088 final class MessageHandler extends Handler {
1089 private static final int MSG_SESSIONS_CHANGED = 1;
1090
1091 @Override
1092 public void handleMessage(Message msg) {
1093 switch (msg.what) {
1094 case MSG_SESSIONS_CHANGED:
1095 pushSessionsChanged(msg.arg1);
1096 break;
1097 }
1098 }
1099
1100 public void post(int what, int arg1, int arg2) {
1101 obtainMessage(what, arg1, arg2).sendToTarget();
1102 }
1103 }
RoboErik01fe6612014-02-13 14:19:04 -08001104}