blob: 77a1fa9237936ca584965e3d7da03bed1f7a53fa [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
RoboErik2610d712015-01-07 11:10:23 -080080 /* package */final IBinder mICallback = new Binder();
81
RoboErik01fe6612014-02-13 14:19:04 -080082 private final SessionManagerImpl mSessionManagerImpl;
RoboErika8f95142014-05-05 14:23:49 -070083 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080084
RoboErik4646d282014-05-13 10:13:04 -070085 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
86 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070087 private final ArrayList<SessionsListenerRecord> mSessionsListeners
88 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -080089 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070090 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070091 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik519c7742014-11-18 10:59:09 -080092 private final boolean mUseMasterVolume;
RoboErik01fe6612014-02-13 14:19:04 -080093
RoboErik9a9d0b52014-05-20 14:53:39 -070094 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070095 private IAudioService mAudioService;
RoboErik2610d712015-01-07 11:10:23 -080096 private AudioManager mAudioManager;
RoboErik6f0e4dd2014-06-17 16:56:27 -070097 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -070098 private SettingsObserver mSettingsObserver;
RoboErik9a9d0b52014-05-20 14:53:39 -070099
RoboErik4646d282014-05-13 10:13:04 -0700100 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -0700101
RoboErik19c95182014-06-23 15:38:48 -0700102 // Used to notify system UI when remote volume was changed. TODO find a
103 // better way to handle this.
104 private IRemoteVolumeController mRvc;
105
RoboErik01fe6612014-02-13 14:19:04 -0800106 public MediaSessionService(Context context) {
107 super(context);
108 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700109 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700110 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
111 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik519c7742014-11-18 10:59:09 -0800112 mUseMasterVolume = context.getResources().getBoolean(
113 com.android.internal.R.bool.config_useMasterVolume);
RoboErik01fe6612014-02-13 14:19:04 -0800114 }
115
116 @Override
117 public void onStart() {
118 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700119 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700120 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700121 mKeyguardManager =
122 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700123 mAudioService = getAudioService();
RoboErik2610d712015-01-07 11:10:23 -0800124 mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
RoboErik6f0e4dd2014-06-17 16:56:27 -0700125 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700126 mSettingsObserver = new SettingsObserver();
127 mSettingsObserver.observe();
RoboErikb69ffd42014-05-30 14:57:59 -0700128 }
129
130 private IAudioService getAudioService() {
131 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
132 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700133 }
134
RoboErika8f95142014-05-05 14:23:49 -0700135 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700136 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700137 if (!mAllSessions.contains(record)) {
138 Log.d(TAG, "Unknown session updated. Ignoring.");
139 return;
140 }
RoboErika8f95142014-05-05 14:23:49 -0700141 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700142 }
RoboErik2e7a9162014-06-04 16:53:45 -0700143 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700144 }
145
RoboErik9c5b7cb2015-01-15 15:09:09 -0800146 /**
147 * Tells the system UI that volume has changed on a remote session.
148 */
149 public void notifyRemoteVolumeChanged(int flags, MediaSessionRecord session) {
150 if (mRvc == null) {
151 return;
152 }
153 try {
154 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
155 } catch (Exception e) {
156 Log.wtf(TAG, "Error sending volume change to system UI.", e);
157 }
158 }
159
RoboErika8f95142014-05-05 14:23:49 -0700160 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700161 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700162 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700163 if (!mAllSessions.contains(record)) {
164 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
165 return;
166 }
RoboErik2e7a9162014-06-04 16:53:45 -0700167 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
168 }
169 if (updateSessions) {
170 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700171 }
172 }
173
RoboErik19c95182014-06-23 15:38:48 -0700174 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
175 synchronized (mLock) {
176 if (!mAllSessions.contains(record)) {
177 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
178 return;
179 }
180 pushRemoteVolumeUpdateLocked(record.getUserId());
181 }
182 }
183
RoboErika278ea72014-04-24 14:49:01 -0700184 @Override
RoboErik4646d282014-05-13 10:13:04 -0700185 public void onStartUser(int userHandle) {
186 updateUser();
187 }
188
189 @Override
190 public void onSwitchUser(int userHandle) {
191 updateUser();
192 }
193
194 @Override
195 public void onStopUser(int userHandle) {
196 synchronized (mLock) {
197 UserRecord user = mUserRecords.get(userHandle);
198 if (user != null) {
199 destroyUserLocked(user);
200 }
201 }
202 }
203
204 @Override
RoboErika278ea72014-04-24 14:49:01 -0700205 public void monitor() {
206 synchronized (mLock) {
207 // Check for deadlock
208 }
209 }
210
RoboErik4646d282014-05-13 10:13:04 -0700211 protected void enforcePhoneStatePermission(int pid, int uid) {
212 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
213 != PackageManager.PERMISSION_GRANTED) {
214 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
215 }
216 }
217
RoboErik01fe6612014-02-13 14:19:04 -0800218 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700219 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800220 destroySessionLocked(session);
221 }
222 }
223
224 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700225 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800226 destroySessionLocked(session);
227 }
228 }
229
RoboErik4646d282014-05-13 10:13:04 -0700230 private void updateUser() {
231 synchronized (mLock) {
232 int userId = ActivityManager.getCurrentUser();
233 if (mCurrentUserId != userId) {
234 final int oldUserId = mCurrentUserId;
235 mCurrentUserId = userId; // do this first
236
237 UserRecord oldUser = mUserRecords.get(oldUserId);
238 if (oldUser != null) {
239 oldUser.stopLocked();
240 }
241
242 UserRecord newUser = getOrCreateUser(userId);
243 newUser.startLocked();
244 }
245 }
246 }
247
RoboErik7aef77b2014-08-08 15:56:54 -0700248 private void updateActiveSessionListeners() {
249 synchronized (mLock) {
250 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
251 SessionsListenerRecord listener = mSessionsListeners.get(i);
252 try {
253 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
254 listener.mUserId);
255 } catch (SecurityException e) {
256 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
257 + " is no longer authorized. Disconnecting.");
258 mSessionsListeners.remove(i);
259 try {
260 listener.mListener
261 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
262 } catch (Exception e1) {
263 // ignore
264 }
265 }
266 }
267 }
268 }
269
RoboErik4646d282014-05-13 10:13:04 -0700270 /**
271 * Stop the user and unbind from everything.
272 *
273 * @param user The user to dispose of
274 */
275 private void destroyUserLocked(UserRecord user) {
276 user.stopLocked();
277 user.destroyLocked();
278 mUserRecords.remove(user.mUserId);
279 }
280
281 /*
282 * When a session is removed several things need to happen.
283 * 1. We need to remove it from the relevant user.
284 * 2. We need to remove it from the priority stack.
285 * 3. We need to remove it from all sessions.
286 * 4. If this is the system priority session we need to clear it.
287 * 5. We need to unlink to death from the cb binder
288 * 6. We need to tell the session to do any final cleanup (onDestroy)
289 */
RoboErik01fe6612014-02-13 14:19:04 -0800290 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700291 int userId = session.getUserId();
292 UserRecord user = mUserRecords.get(userId);
293 if (user != null) {
294 user.removeSessionLocked(session);
295 }
296
RoboErika8f95142014-05-05 14:23:49 -0700297 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700298 mAllSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700299
300 try {
301 session.getCallback().asBinder().unlinkToDeath(session, 0);
302 } catch (Exception e) {
303 // ignore exceptions while destroying a session.
304 }
305 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700306
307 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800308 }
309
310 private void enforcePackageName(String packageName, int uid) {
311 if (TextUtils.isEmpty(packageName)) {
312 throw new IllegalArgumentException("packageName may not be empty");
313 }
314 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
315 final int packageCount = packages.length;
316 for (int i = 0; i < packageCount; i++) {
317 if (packageName.equals(packages[i])) {
318 return;
319 }
320 }
321 throw new IllegalArgumentException("packageName is not owned by the calling process");
322 }
323
RoboErike7880d82014-04-30 12:48:25 -0700324 /**
325 * Checks a caller's authorization to register an IRemoteControlDisplay.
326 * Authorization is granted if one of the following is true:
327 * <ul>
328 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
329 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700330 * <li>the caller's listener is one of the enabled notification listeners
331 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700332 * </ul>
333 */
RoboErika5b02322014-05-07 17:05:49 -0700334 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
335 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700336 if (getContext()
337 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
338 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700339 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
340 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700341 throw new SecurityException("Missing permission to control media.");
342 }
343 }
344
RoboErik19c95182014-06-23 15:38:48 -0700345 private void enforceStatusBarPermission(String action, int pid, int uid) {
346 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
347 pid, uid) != PackageManager.PERMISSION_GRANTED) {
348 throw new SecurityException("Only system ui may " + action);
349 }
350 }
351
RoboErika5b02322014-05-07 17:05:49 -0700352 /**
353 * This checks if the component is an enabled notification listener for the
354 * specified user. Enabled components may only operate on behalf of the user
355 * they're running as.
356 *
357 * @param compName The component that is enabled.
358 * @param userId The user id of the caller.
359 * @param forUserId The user id they're making the request on behalf of.
360 * @return True if the component is enabled, false otherwise
361 */
362 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
363 int forUserId) {
364 if (userId != forUserId) {
365 // You may not access another user's content as an enabled listener.
366 return false;
367 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700368 if (DEBUG) {
369 Log.d(TAG, "Checking if enabled notification listener " + compName);
370 }
RoboErike7880d82014-04-30 12:48:25 -0700371 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700372 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700373 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700374 userId);
RoboErike7880d82014-04-30 12:48:25 -0700375 if (enabledNotifListeners != null) {
376 final String[] components = enabledNotifListeners.split(":");
377 for (int i = 0; i < components.length; i++) {
378 final ComponentName component =
379 ComponentName.unflattenFromString(components[i]);
380 if (component != null) {
381 if (compName.equals(component)) {
382 if (DEBUG) {
383 Log.d(TAG, "ok to get sessions: " + component +
384 " is authorized notification listener");
385 }
386 return true;
387 }
388 }
389 }
390 }
391 if (DEBUG) {
392 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700393 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700394 }
395 }
396 return false;
397 }
398
RoboErika5b02322014-05-07 17:05:49 -0700399 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700400 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800401 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700402 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800403 }
404 }
405
RoboErik4646d282014-05-13 10:13:04 -0700406 /*
407 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700408 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700409 * 2. It needs to be added to all sessions.
410 * 3. It needs to be added to the priority stack.
411 * 4. It needs to be added to the relevant user record.
412 */
RoboErika5b02322014-05-07 17:05:49 -0700413 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
414 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700415
RoboErika5b02322014-05-07 17:05:49 -0700416 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
417 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800418 try {
419 cb.asBinder().linkToDeath(session, 0);
420 } catch (RemoteException e) {
421 throw new RuntimeException("Media Session owner died prematurely.", e);
422 }
RoboErik4646d282014-05-13 10:13:04 -0700423
424 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700425 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700426
427 UserRecord user = getOrCreateUser(userId);
428 user.addSessionLocked(session);
429
RoboErik2e7a9162014-06-04 16:53:45 -0700430 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
431
RoboErik01fe6612014-02-13 14:19:04 -0800432 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700433 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800434 }
435 return session;
436 }
437
RoboErik4646d282014-05-13 10:13:04 -0700438 private UserRecord getOrCreateUser(int userId) {
439 UserRecord user = mUserRecords.get(userId);
440 if (user == null) {
441 user = new UserRecord(getContext(), userId);
442 mUserRecords.put(userId, user);
443 }
444 return user;
445 }
446
RoboErik2e7a9162014-06-04 16:53:45 -0700447 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
448 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
RoboErika08adb242014-11-21 18:28:18 -0800449 if (mSessionsListeners.get(i).mListener.asBinder() == listener.asBinder()) {
RoboErik2e7a9162014-06-04 16:53:45 -0700450 return i;
451 }
452 }
453 return -1;
454 }
455
RoboErike7880d82014-04-30 12:48:25 -0700456 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700457 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700458 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700459 }
460
RoboErik2e7a9162014-06-04 16:53:45 -0700461 private void pushSessionsChanged(int userId) {
462 synchronized (mLock) {
463 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
464 int size = records.size();
RoboErik870c5a62014-12-02 15:08:26 -0800465 if (size > 0 && records.get(0).isPlaybackActive(false)) {
RoboErikb214efb2014-07-24 13:20:30 -0700466 rememberMediaButtonReceiverLocked(records.get(0));
RoboErik6f0e4dd2014-06-17 16:56:27 -0700467 }
Jeff Browndba34ba2014-06-24 20:46:03 -0700468 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700469 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700470 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700471 }
RoboErik19c95182014-06-23 15:38:48 -0700472 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700473 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
474 SessionsListenerRecord record = mSessionsListeners.get(i);
475 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
476 try {
477 record.mListener.onActiveSessionsChanged(tokens);
478 } catch (RemoteException e) {
479 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
480 e);
481 mSessionsListeners.remove(i);
482 }
483 }
484 }
485 }
486 }
487
RoboErik19c95182014-06-23 15:38:48 -0700488 private void pushRemoteVolumeUpdateLocked(int userId) {
489 if (mRvc != null) {
490 try {
491 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
492 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
493 } catch (RemoteException e) {
494 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
495 }
496 }
497 }
498
RoboErikb214efb2014-07-24 13:20:30 -0700499 private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
500 PendingIntent receiver = record.getMediaButtonReceiver();
501 UserRecord user = mUserRecords.get(record.getUserId());
502 if (receiver != null && user != null) {
503 user.mLastMediaButtonReceiver = receiver;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700504 }
505 }
506
RoboErik4646d282014-05-13 10:13:04 -0700507 /**
508 * Information about a particular user. The contents of this object is
509 * guarded by mLock.
510 */
511 final class UserRecord {
512 private final int mUserId;
RoboErik4646d282014-05-13 10:13:04 -0700513 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
RoboErikb214efb2014-07-24 13:20:30 -0700514 private PendingIntent mLastMediaButtonReceiver;
RoboErik4646d282014-05-13 10:13:04 -0700515
516 public UserRecord(Context context, int userId) {
517 mUserId = userId;
RoboErik4646d282014-05-13 10:13:04 -0700518 }
519
520 public void startLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700521 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700522 }
523
524 public void stopLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700525 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700526 }
527
528 public void destroyLocked() {
529 for (int i = mSessions.size() - 1; i >= 0; i--) {
530 MediaSessionRecord session = mSessions.get(i);
531 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700532 }
533 }
534
RoboErik4646d282014-05-13 10:13:04 -0700535 public ArrayList<MediaSessionRecord> getSessionsLocked() {
536 return mSessions;
537 }
538
539 public void addSessionLocked(MediaSessionRecord session) {
540 mSessions.add(session);
RoboErik4646d282014-05-13 10:13:04 -0700541 }
542
543 public void removeSessionLocked(MediaSessionRecord session) {
544 mSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700545 }
546
547 public void dumpLocked(PrintWriter pw, String prefix) {
548 pw.println(prefix + "Record for user " + mUserId);
549 String indent = prefix + " ";
RoboErikb214efb2014-07-24 13:20:30 -0700550 pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
Jeff Brown01a500e2014-07-10 22:50:50 -0700551 int size = mSessions.size();
RoboErik4646d282014-05-13 10:13:04 -0700552 pw.println(indent + size + " Sessions:");
553 for (int i = 0; i < size; i++) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700554 // Just print the short version, the full session dump will
RoboErik4646d282014-05-13 10:13:04 -0700555 // already be in the list of all sessions.
RoboErikaa4e23b2014-07-24 18:35:11 -0700556 pw.println(indent + mSessions.get(i).toString());
RoboErik4646d282014-05-13 10:13:04 -0700557 }
558 }
RoboErik4646d282014-05-13 10:13:04 -0700559 }
560
RoboErik2e7a9162014-06-04 16:53:45 -0700561 final class SessionsListenerRecord implements IBinder.DeathRecipient {
562 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700563 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700564 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700565 private final int mPid;
566 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700567
RoboErik7aef77b2014-08-08 15:56:54 -0700568 public SessionsListenerRecord(IActiveSessionsListener listener,
569 ComponentName componentName,
570 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700571 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700572 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700573 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700574 mPid = pid;
575 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700576 }
577
578 @Override
579 public void binderDied() {
580 synchronized (mLock) {
581 mSessionsListeners.remove(this);
582 }
583 }
584 }
585
RoboErik7aef77b2014-08-08 15:56:54 -0700586 final class SettingsObserver extends ContentObserver {
587 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
588 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
589
590 private SettingsObserver() {
591 super(null);
592 }
593
594 private void observe() {
595 mContentResolver.registerContentObserver(mSecureSettingsUri,
596 false, this, UserHandle.USER_ALL);
597 }
598
599 @Override
600 public void onChange(boolean selfChange, Uri uri) {
601 updateActiveSessionListeners();
602 }
603 }
604
RoboErik07c70772014-03-20 13:33:52 -0700605 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700606 private static final String EXTRA_WAKELOCK_ACQUIRED =
607 "android.media.AudioService.WAKELOCK_ACQUIRED";
608 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
609
RoboErik9a9d0b52014-05-20 14:53:39 -0700610 private boolean mVoiceButtonDown = false;
611 private boolean mVoiceButtonHandled = false;
612
RoboErik07c70772014-03-20 13:33:52 -0700613 @Override
RoboErika5b02322014-05-07 17:05:49 -0700614 public ISession createSession(String packageName, ISessionCallback cb, String tag,
615 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800616 final int pid = Binder.getCallingPid();
617 final int uid = Binder.getCallingUid();
618 final long token = Binder.clearCallingIdentity();
619 try {
620 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700621 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
622 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800623 if (cb == null) {
624 throw new IllegalArgumentException("Controller callback cannot be null");
625 }
RoboErika5b02322014-05-07 17:05:49 -0700626 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
627 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700628 } finally {
629 Binder.restoreCallingIdentity(token);
630 }
631 }
632
633 @Override
RoboErika5b02322014-05-07 17:05:49 -0700634 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700635 final int pid = Binder.getCallingPid();
636 final int uid = Binder.getCallingUid();
637 final long token = Binder.clearCallingIdentity();
638
639 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700640 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700641 ArrayList<IBinder> binders = new ArrayList<IBinder>();
642 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700643 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700644 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700645 int size = records.size();
646 for (int i = 0; i < size; i++) {
647 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700648 }
649 }
650 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800651 } finally {
652 Binder.restoreCallingIdentity(token);
653 }
654 }
RoboErika278ea72014-04-24 14:49:01 -0700655
RoboErik2e7a9162014-06-04 16:53:45 -0700656 @Override
657 public void addSessionsListener(IActiveSessionsListener listener,
658 ComponentName componentName, int userId) throws RemoteException {
659 final int pid = Binder.getCallingPid();
660 final int uid = Binder.getCallingUid();
661 final long token = Binder.clearCallingIdentity();
662
663 try {
664 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
665 synchronized (mLock) {
666 int index = findIndexOfSessionsListenerLocked(listener);
667 if (index != -1) {
668 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
669 return;
670 }
671 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -0700672 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -0700673 try {
674 listener.asBinder().linkToDeath(record, 0);
675 } catch (RemoteException e) {
676 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
677 return;
678 }
679 mSessionsListeners.add(record);
680 }
681 } finally {
682 Binder.restoreCallingIdentity(token);
683 }
684 }
685
686 @Override
687 public void removeSessionsListener(IActiveSessionsListener listener)
688 throws RemoteException {
689 synchronized (mLock) {
690 int index = findIndexOfSessionsListenerLocked(listener);
691 if (index != -1) {
692 SessionsListenerRecord record = mSessionsListeners.remove(index);
693 try {
694 record.mListener.asBinder().unlinkToDeath(record, 0);
695 } catch (Exception e) {
696 // ignore exceptions, the record is being removed
697 }
698 }
699 }
700 }
701
RoboErik8a2cfc32014-05-16 11:19:38 -0700702 /**
703 * Handles the dispatching of the media button events to one of the
704 * registered listeners, or if there was none, broadcast an
705 * ACTION_MEDIA_BUTTON intent to the rest of the system.
706 *
707 * @param keyEvent a non-null KeyEvent whose key code is one of the
708 * supported media buttons
709 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
710 * while this key event is dispatched.
711 */
712 @Override
713 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
714 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
715 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
716 return;
717 }
718 final int pid = Binder.getCallingPid();
719 final int uid = Binder.getCallingUid();
720 final long token = Binder.clearCallingIdentity();
721
722 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700723 synchronized (mLock) {
RoboErik870c5a62014-12-02 15:08:26 -0800724 // If we don't have a media button receiver to fall back on
725 // include non-playing sessions for dispatching
726 boolean useNotPlayingSessions = mUserRecords.get(
727 ActivityManager.getCurrentUser()).mLastMediaButtonReceiver == null;
RoboErik9a9d0b52014-05-20 14:53:39 -0700728 MediaSessionRecord session = mPriorityStack
RoboErik870c5a62014-12-02 15:08:26 -0800729 .getDefaultMediaButtonSession(mCurrentUserId, useNotPlayingSessions);
RoboErik9a9d0b52014-05-20 14:53:39 -0700730 if (isVoiceKey(keyEvent.getKeyCode())) {
731 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700732 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700733 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700734 }
735 }
736 } finally {
737 Binder.restoreCallingIdentity(token);
738 }
739 }
740
RoboErika278ea72014-04-24 14:49:01 -0700741 @Override
RoboErik7c82ced2014-12-04 17:39:08 -0800742 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags) {
RoboErikb69ffd42014-05-30 14:57:59 -0700743 final int pid = Binder.getCallingPid();
744 final int uid = Binder.getCallingUid();
745 final long token = Binder.clearCallingIdentity();
746 try {
747 synchronized (mLock) {
748 MediaSessionRecord session = mPriorityStack
749 .getDefaultVolumeSession(mCurrentUserId);
RoboErik1ff5b162014-07-15 17:23:18 -0700750 dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
RoboErikb69ffd42014-05-30 14:57:59 -0700751 }
752 } finally {
753 Binder.restoreCallingIdentity(token);
754 }
755 }
756
757 @Override
RoboErik19c95182014-06-23 15:38:48 -0700758 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
759 final int pid = Binder.getCallingPid();
760 final int uid = Binder.getCallingUid();
761 final long token = Binder.clearCallingIdentity();
762 try {
763 enforceStatusBarPermission("listen for volume changes", pid, uid);
764 mRvc = rvc;
765 } finally {
766 Binder.restoreCallingIdentity(token);
767 }
768 }
769
770 @Override
RoboErikde9ba392014-09-26 12:51:01 -0700771 public boolean isGlobalPriorityActive() {
772 return mPriorityStack.isGlobalPriorityActive();
773 }
774
775 @Override
RoboErika278ea72014-04-24 14:49:01 -0700776 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
777 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
778 != PackageManager.PERMISSION_GRANTED) {
779 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
780 + Binder.getCallingPid()
781 + ", uid=" + Binder.getCallingUid());
782 return;
783 }
784
785 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
786 pw.println();
787
788 synchronized (mLock) {
RoboErika08adb242014-11-21 18:28:18 -0800789 pw.println(mSessionsListeners.size() + " sessions listeners.");
RoboErik4646d282014-05-13 10:13:04 -0700790 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700791 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700792 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700793 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700794 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700795 }
RoboErika5b02322014-05-07 17:05:49 -0700796 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700797
RoboErik4646d282014-05-13 10:13:04 -0700798 pw.println("User Records:");
799 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700800 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700801 UserRecord user = mUserRecords.get(i);
802 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700803 }
804 }
805 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700806
RoboErik2e7a9162014-06-04 16:53:45 -0700807 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
808 final int uid) {
809 String packageName = null;
810 if (componentName != null) {
811 // If they gave us a component name verify they own the
812 // package
813 packageName = componentName.getPackageName();
814 enforcePackageName(packageName, uid);
815 }
816 // Check that they can make calls on behalf of the user and
817 // get the final user id
818 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
819 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
820 // Check if they have the permissions or their component is
821 // enabled for the user they're calling from.
822 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
823 return resolvedUserId;
824 }
825
RoboErik1ff5b162014-07-15 17:23:18 -0700826 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
RoboErikb69ffd42014-05-30 14:57:59 -0700827 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700828 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700829 String description = session == null ? null : session.toString();
830 Log.d(TAG, "Adjusting session " + description + " by " + direction + ". flags="
831 + flags + ", suggestedStream=" + suggestedStream);
RoboErikb69ffd42014-05-30 14:57:59 -0700832
833 }
RoboErik9c785402014-11-11 16:52:26 -0800834 boolean preferSuggestedStream = false;
835 if (isValidLocalStreamType(suggestedStream)
836 && AudioSystem.isStreamActive(suggestedStream, 0)) {
837 preferSuggestedStream = true;
838 }
839 if (session == null || preferSuggestedStream) {
RoboErik94c716e2014-09-14 13:54:31 -0700840 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
841 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
RoboErik3c45c292014-07-08 16:47:31 -0700842 if (DEBUG) {
843 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -0700844 }
RoboErikb7c014c2014-07-22 15:58:22 -0700845 return;
RoboErik3c45c292014-07-08 16:47:31 -0700846 }
RoboErik0791e172014-06-08 10:52:32 -0700847 try {
Eric Laurent2b5208c2014-12-19 10:07:03 -0800848 String packageName = getContext().getOpPackageName();
RoboErik519c7742014-11-18 10:59:09 -0800849 if (mUseMasterVolume) {
Eric Laurent2b5208c2014-12-19 10:07:03 -0800850 boolean isMasterMute = mAudioService.isMasterMute();
RoboErik7c82ced2014-12-04 17:39:08 -0800851 if (direction == MediaSessionManager.DIRECTION_MUTE) {
Eric Laurent2b5208c2014-12-19 10:07:03 -0800852 mAudioService.setMasterMute(!isMasterMute, flags, packageName, mICallback);
RoboErik7c82ced2014-12-04 17:39:08 -0800853 } else {
Eric Laurent2b5208c2014-12-19 10:07:03 -0800854 mAudioService.adjustMasterVolume(direction, flags, packageName);
Sungsoo Limd5489222015-01-05 09:08:34 +0900855 // Do not call setMasterMute when direction = 0 which is used just to
856 // show the UI.
Sungsoo Lim48248c82014-12-24 17:30:00 +0900857 if (isMasterMute && direction != 0) {
Eric Laurent2b5208c2014-12-19 10:07:03 -0800858 mAudioService.setMasterMute(false, flags, packageName, mICallback);
859 }
RoboErik7c82ced2014-12-04 17:39:08 -0800860 }
RoboErik519c7742014-11-18 10:59:09 -0800861 } else {
Eric Laurent2b5208c2014-12-19 10:07:03 -0800862 boolean isStreamMute = mAudioService.isStreamMute(suggestedStream);
RoboErik7c82ced2014-12-04 17:39:08 -0800863 if (direction == MediaSessionManager.DIRECTION_MUTE) {
RoboErik2610d712015-01-07 11:10:23 -0800864 mAudioManager.setStreamMute(suggestedStream, !isStreamMute);
RoboErik7c82ced2014-12-04 17:39:08 -0800865 } else {
866 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
Eric Laurent2b5208c2014-12-19 10:07:03 -0800867 flags, packageName);
Sungsoo Limd5489222015-01-05 09:08:34 +0900868 // Do not call setStreamMute when direction = 0 which is used just to
869 // show the UI.
Sungsoo Lim48248c82014-12-24 17:30:00 +0900870 if (isStreamMute && direction != 0) {
RoboErik2610d712015-01-07 11:10:23 -0800871 mAudioManager.setStreamMute(suggestedStream, false);
Eric Laurent2b5208c2014-12-19 10:07:03 -0800872 }
RoboErik7c82ced2014-12-04 17:39:08 -0800873 }
RoboErik519c7742014-11-18 10:59:09 -0800874 }
RoboErik0791e172014-06-08 10:52:32 -0700875 } catch (RemoteException e) {
876 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700877 }
878 } else {
RoboErik0dac35a2014-08-12 15:48:49 -0700879 session.adjustVolume(direction, flags, getContext().getPackageName(),
RoboErik272e1612014-09-05 11:39:29 -0700880 UserHandle.myUserId(), true);
RoboErikb69ffd42014-05-30 14:57:59 -0700881 }
882 }
883
RoboErik9a9d0b52014-05-20 14:53:39 -0700884 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
885 MediaSessionRecord session) {
886 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
887 // If the phone app has priority just give it the event
888 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
889 return;
890 }
891 int action = keyEvent.getAction();
892 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
893 if (action == KeyEvent.ACTION_DOWN) {
894 if (keyEvent.getRepeatCount() == 0) {
895 mVoiceButtonDown = true;
896 mVoiceButtonHandled = false;
897 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
898 mVoiceButtonHandled = true;
899 startVoiceInput(needWakeLock);
900 }
901 } else if (action == KeyEvent.ACTION_UP) {
902 if (mVoiceButtonDown) {
903 mVoiceButtonDown = false;
904 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
905 // Resend the down then send this event through
906 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
907 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
908 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
909 }
910 }
911 }
912 }
913
914 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
915 MediaSessionRecord session) {
916 if (session != null) {
917 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700918 Log.d(TAG, "Sending media key to " + session.toString());
RoboErik9a9d0b52014-05-20 14:53:39 -0700919 }
920 if (needWakeLock) {
921 mKeyEventReceiver.aquireWakeLockLocked();
922 }
923 // If we don't need a wakelock use -1 as the id so we
924 // won't release it later
925 session.sendMediaButton(keyEvent,
926 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
927 mKeyEventReceiver);
928 } else {
RoboErikb214efb2014-07-24 13:20:30 -0700929 // Launch the last PendingIntent we had with priority
930 int userId = ActivityManager.getCurrentUser();
931 UserRecord user = mUserRecords.get(userId);
932 if (user.mLastMediaButtonReceiver != null) {
933 if (DEBUG) {
934 Log.d(TAG, "Sending media key to last known PendingIntent");
935 }
936 if (needWakeLock) {
937 mKeyEventReceiver.aquireWakeLockLocked();
938 }
939 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
940 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
941 try {
942 user.mLastMediaButtonReceiver.send(getContext(),
943 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
944 mediaButtonIntent, mKeyEventReceiver, null);
945 } catch (CanceledException e) {
946 Log.i(TAG, "Error sending key event to media button receiver "
947 + user.mLastMediaButtonReceiver, e);
948 }
949 } else {
950 if (DEBUG) {
951 Log.d(TAG, "Sending media key ordered broadcast");
952 }
953 if (needWakeLock) {
954 mMediaEventWakeLock.acquire();
955 }
956 // Fallback to legacy behavior
957 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
958 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
959 if (needWakeLock) {
960 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
961 WAKELOCK_RELEASE_ON_FINISHED);
962 }
963 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
964 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
RoboErik9a9d0b52014-05-20 14:53:39 -0700965 }
RoboErik9a9d0b52014-05-20 14:53:39 -0700966 }
967 }
968
969 private void startVoiceInput(boolean needWakeLock) {
970 Intent voiceIntent = null;
971 // select which type of search to launch:
972 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
973 // - device locked or screen off: action is
974 // ACTION_VOICE_SEARCH_HANDS_FREE
975 // with EXTRA_SECURE set to true if the device is securely locked
976 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
977 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
978 if (!isLocked && pm.isScreenOn()) {
979 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
980 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
981 } else {
982 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
983 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
984 isLocked && mKeyguardManager.isKeyguardSecure());
985 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
986 }
987 // start the search activity
988 if (needWakeLock) {
989 mMediaEventWakeLock.acquire();
990 }
991 try {
992 if (voiceIntent != null) {
993 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
994 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
995 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
996 }
997 } catch (ActivityNotFoundException e) {
998 Log.w(TAG, "No activity for search: " + e);
999 } finally {
1000 if (needWakeLock) {
1001 mMediaEventWakeLock.release();
1002 }
1003 }
1004 }
1005
1006 private boolean isVoiceKey(int keyCode) {
1007 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
1008 }
1009
RoboErik9c785402014-11-11 16:52:26 -08001010 // we only handle public stream types, which are 0-5
1011 private boolean isValidLocalStreamType(int streamType) {
1012 return streamType >= AudioManager.STREAM_VOICE_CALL
1013 && streamType <= AudioManager.STREAM_NOTIFICATION;
1014 }
1015
RoboErik418c10c2014-05-19 09:25:25 -07001016 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1017
RoboErikb214efb2014-07-24 13:20:30 -07001018 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
1019 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -07001020 private final Handler mHandler;
1021 private int mRefCount = 0;
1022 private int mLastTimeoutId = 0;
1023
1024 public KeyEventWakeLockReceiver(Handler handler) {
1025 super(handler);
1026 mHandler = handler;
1027 }
1028
1029 public void onTimeout() {
1030 synchronized (mLock) {
1031 if (mRefCount == 0) {
1032 // We've already released it, so just return
1033 return;
1034 }
1035 mLastTimeoutId++;
1036 mRefCount = 0;
1037 releaseWakeLockLocked();
1038 }
1039 }
1040
1041 public void aquireWakeLockLocked() {
1042 if (mRefCount == 0) {
1043 mMediaEventWakeLock.acquire();
1044 }
1045 mRefCount++;
1046 mHandler.removeCallbacks(this);
1047 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1048
1049 }
1050
1051 @Override
1052 public void run() {
1053 onTimeout();
1054 }
1055
RoboErik8a2cfc32014-05-16 11:19:38 -07001056 @Override
1057 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -07001058 if (resultCode < mLastTimeoutId) {
1059 // Ignore results from calls that were before the last
1060 // timeout, just in case.
1061 return;
1062 } else {
1063 synchronized (mLock) {
1064 if (mRefCount > 0) {
1065 mRefCount--;
1066 if (mRefCount == 0) {
1067 releaseWakeLockLocked();
1068 }
1069 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001070 }
1071 }
1072 }
RoboErik418c10c2014-05-19 09:25:25 -07001073
1074 private void releaseWakeLockLocked() {
1075 mMediaEventWakeLock.release();
1076 mHandler.removeCallbacks(this);
1077 }
RoboErikb214efb2014-07-24 13:20:30 -07001078
1079 @Override
1080 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1081 String resultData, Bundle resultExtras) {
1082 onReceiveResult(resultCode, null);
1083 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001084 };
1085
1086 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1087 @Override
1088 public void onReceive(Context context, Intent intent) {
1089 if (intent == null) {
1090 return;
1091 }
1092 Bundle extras = intent.getExtras();
1093 if (extras == null) {
1094 return;
1095 }
1096 synchronized (mLock) {
1097 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1098 && mMediaEventWakeLock.isHeld()) {
1099 mMediaEventWakeLock.release();
1100 }
1101 }
1102 }
1103 };
RoboErik01fe6612014-02-13 14:19:04 -08001104 }
1105
RoboErik2e7a9162014-06-04 16:53:45 -07001106 final class MessageHandler extends Handler {
1107 private static final int MSG_SESSIONS_CHANGED = 1;
1108
1109 @Override
1110 public void handleMessage(Message msg) {
1111 switch (msg.what) {
1112 case MSG_SESSIONS_CHANGED:
1113 pushSessionsChanged(msg.arg1);
1114 break;
1115 }
1116 }
1117
1118 public void post(int what, int arg1, int arg2) {
1119 obtainMessage(what, arg1, arg2).sendToTarget();
1120 }
1121 }
RoboErik01fe6612014-02-13 14:19:04 -08001122}