blob: aaa29fc3a1cfa5257918efbf65fa3493153b1820 [file] [log] [blame]
RoboErik01fe6612014-02-13 14:19:04 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.media;
18
RoboErika278ea72014-04-24 14:49:01 -070019import android.Manifest;
RoboErik8a2cfc32014-05-16 11:19:38 -070020import android.app.Activity;
RoboErike7880d82014-04-30 12:48:25 -070021import android.app.ActivityManager;
RoboErik9a9d0b52014-05-20 14:53:39 -070022import android.app.KeyguardManager;
RoboErikb214efb2014-07-24 13:20:30 -070023import android.app.PendingIntent;
24import android.app.PendingIntent.CanceledException;
RoboErik9a9d0b52014-05-20 14:53:39 -070025import android.content.ActivityNotFoundException;
RoboErik8a2cfc32014-05-16 11:19:38 -070026import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070027import android.content.ComponentName;
RoboErik6f0e4dd2014-06-17 16:56:27 -070028import android.content.ContentResolver;
RoboErik01fe6612014-02-13 14:19:04 -080029import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070030import android.content.Intent;
RoboErika278ea72014-04-24 14:49:01 -070031import android.content.pm.PackageManager;
RoboErik7aef77b2014-08-08 15:56:54 -070032import android.database.ContentObserver;
RoboErik3c45c292014-07-08 16:47:31 -070033import android.media.AudioManager;
RoboErik94c716e2014-09-14 13:54:31 -070034import android.media.AudioSystem;
RoboErikb69ffd42014-05-30 14:57:59 -070035import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070036import android.media.IRemoteVolumeController;
RoboErik2e7a9162014-06-04 16:53:45 -070037import android.media.session.IActiveSessionsListener;
RoboErik07c70772014-03-20 13:33:52 -070038import android.media.session.ISession;
39import android.media.session.ISessionCallback;
40import android.media.session.ISessionManager;
RoboErikd2b8c942014-08-19 11:23:40 -070041import android.media.session.MediaController.PlaybackInfo;
Jeff Browndba34ba2014-06-24 20:46:03 -070042import android.media.session.MediaSession;
RoboErik7aef77b2014-08-08 15:56:54 -070043import android.net.Uri;
RoboErik01fe6612014-02-13 14:19:04 -080044import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070045import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080046import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070047import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070048import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070049import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080050import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070051import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070052import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070053import android.os.UserHandle;
54import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070055import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080056import android.text.TextUtils;
57import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070058import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070059import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080060
61import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070062import com.android.server.Watchdog;
63import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080064
RoboErika278ea72014-04-24 14:49:01 -070065import java.io.FileDescriptor;
66import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080067import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070068import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080069
70/**
71 * System implementation of MediaSessionManager
72 */
RoboErika278ea72014-04-24 14:49:01 -070073public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080074 private static final String TAG = "MediaSessionService";
75 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
76
RoboErik418c10c2014-05-19 09:25:25 -070077 private static final int WAKELOCK_TIMEOUT = 5000;
78
RoboErik01fe6612014-02-13 14:19:04 -080079 private final SessionManagerImpl mSessionManagerImpl;
RoboErika8f95142014-05-05 14:23:49 -070080 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080081
RoboErik4646d282014-05-13 10:13:04 -070082 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
83 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070084 private final ArrayList<SessionsListenerRecord> mSessionsListeners
85 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -080086 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070087 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070088 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080089
RoboErik9a9d0b52014-05-20 14:53:39 -070090 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070091 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070092 private ContentResolver mContentResolver;
RoboErik7aef77b2014-08-08 15:56:54 -070093 private SettingsObserver mSettingsObserver;
RoboErik9a9d0b52014-05-20 14:53:39 -070094
RoboErik4646d282014-05-13 10:13:04 -070095 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070096
RoboErik19c95182014-06-23 15:38:48 -070097 // Used to notify system UI when remote volume was changed. TODO find a
98 // better way to handle this.
99 private IRemoteVolumeController mRvc;
100
RoboErik01fe6612014-02-13 14:19:04 -0800101 public MediaSessionService(Context context) {
102 super(context);
103 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700104 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700105 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
106 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -0800107 }
108
109 @Override
110 public void onStart() {
111 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700112 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700113 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700114 mKeyguardManager =
115 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700116 mAudioService = getAudioService();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700117 mContentResolver = getContext().getContentResolver();
RoboErik7aef77b2014-08-08 15:56:54 -0700118 mSettingsObserver = new SettingsObserver();
119 mSettingsObserver.observe();
RoboErikb69ffd42014-05-30 14:57:59 -0700120 }
121
122 private IAudioService getAudioService() {
123 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
124 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700125 }
126
RoboErika8f95142014-05-05 14:23:49 -0700127 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700128 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700129 if (!mAllSessions.contains(record)) {
130 Log.d(TAG, "Unknown session updated. Ignoring.");
131 return;
132 }
RoboErika8f95142014-05-05 14:23:49 -0700133 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700134 }
RoboErik2e7a9162014-06-04 16:53:45 -0700135 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700136 }
137
RoboErika8f95142014-05-05 14:23:49 -0700138 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700139 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700140 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700141 if (!mAllSessions.contains(record)) {
142 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
143 return;
144 }
RoboErik2e7a9162014-06-04 16:53:45 -0700145 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
146 }
147 if (updateSessions) {
148 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700149 }
150 }
151
RoboErik19c95182014-06-23 15:38:48 -0700152 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
153 synchronized (mLock) {
154 if (!mAllSessions.contains(record)) {
155 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
156 return;
157 }
158 pushRemoteVolumeUpdateLocked(record.getUserId());
159 }
160 }
161
RoboErika278ea72014-04-24 14:49:01 -0700162 @Override
RoboErik4646d282014-05-13 10:13:04 -0700163 public void onStartUser(int userHandle) {
164 updateUser();
165 }
166
167 @Override
168 public void onSwitchUser(int userHandle) {
169 updateUser();
170 }
171
172 @Override
173 public void onStopUser(int userHandle) {
174 synchronized (mLock) {
175 UserRecord user = mUserRecords.get(userHandle);
176 if (user != null) {
177 destroyUserLocked(user);
178 }
179 }
180 }
181
182 @Override
RoboErika278ea72014-04-24 14:49:01 -0700183 public void monitor() {
184 synchronized (mLock) {
185 // Check for deadlock
186 }
187 }
188
RoboErik4646d282014-05-13 10:13:04 -0700189 protected void enforcePhoneStatePermission(int pid, int uid) {
190 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
191 != PackageManager.PERMISSION_GRANTED) {
192 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
193 }
194 }
195
RoboErik01fe6612014-02-13 14:19:04 -0800196 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700197 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800198 destroySessionLocked(session);
199 }
200 }
201
202 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700203 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800204 destroySessionLocked(session);
205 }
206 }
207
RoboErik4646d282014-05-13 10:13:04 -0700208 private void updateUser() {
209 synchronized (mLock) {
210 int userId = ActivityManager.getCurrentUser();
211 if (mCurrentUserId != userId) {
212 final int oldUserId = mCurrentUserId;
213 mCurrentUserId = userId; // do this first
214
215 UserRecord oldUser = mUserRecords.get(oldUserId);
216 if (oldUser != null) {
217 oldUser.stopLocked();
218 }
219
220 UserRecord newUser = getOrCreateUser(userId);
221 newUser.startLocked();
222 }
223 }
224 }
225
RoboErik7aef77b2014-08-08 15:56:54 -0700226 private void updateActiveSessionListeners() {
227 synchronized (mLock) {
228 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
229 SessionsListenerRecord listener = mSessionsListeners.get(i);
230 try {
231 enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
232 listener.mUserId);
233 } catch (SecurityException e) {
234 Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
235 + " is no longer authorized. Disconnecting.");
236 mSessionsListeners.remove(i);
237 try {
238 listener.mListener
239 .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
240 } catch (Exception e1) {
241 // ignore
242 }
243 }
244 }
245 }
246 }
247
RoboErik4646d282014-05-13 10:13:04 -0700248 /**
249 * Stop the user and unbind from everything.
250 *
251 * @param user The user to dispose of
252 */
253 private void destroyUserLocked(UserRecord user) {
254 user.stopLocked();
255 user.destroyLocked();
256 mUserRecords.remove(user.mUserId);
257 }
258
259 /*
260 * When a session is removed several things need to happen.
261 * 1. We need to remove it from the relevant user.
262 * 2. We need to remove it from the priority stack.
263 * 3. We need to remove it from all sessions.
264 * 4. If this is the system priority session we need to clear it.
265 * 5. We need to unlink to death from the cb binder
266 * 6. We need to tell the session to do any final cleanup (onDestroy)
267 */
RoboErik01fe6612014-02-13 14:19:04 -0800268 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700269 int userId = session.getUserId();
270 UserRecord user = mUserRecords.get(userId);
271 if (user != null) {
272 user.removeSessionLocked(session);
273 }
274
RoboErika8f95142014-05-05 14:23:49 -0700275 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700276 mAllSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700277
278 try {
279 session.getCallback().asBinder().unlinkToDeath(session, 0);
280 } catch (Exception e) {
281 // ignore exceptions while destroying a session.
282 }
283 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700284
285 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800286 }
287
288 private void enforcePackageName(String packageName, int uid) {
289 if (TextUtils.isEmpty(packageName)) {
290 throw new IllegalArgumentException("packageName may not be empty");
291 }
292 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
293 final int packageCount = packages.length;
294 for (int i = 0; i < packageCount; i++) {
295 if (packageName.equals(packages[i])) {
296 return;
297 }
298 }
299 throw new IllegalArgumentException("packageName is not owned by the calling process");
300 }
301
RoboErike7880d82014-04-30 12:48:25 -0700302 /**
303 * Checks a caller's authorization to register an IRemoteControlDisplay.
304 * Authorization is granted if one of the following is true:
305 * <ul>
306 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
307 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700308 * <li>the caller's listener is one of the enabled notification listeners
309 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700310 * </ul>
311 */
RoboErika5b02322014-05-07 17:05:49 -0700312 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
313 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700314 if (getContext()
315 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
316 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700317 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
318 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700319 throw new SecurityException("Missing permission to control media.");
320 }
321 }
322
RoboErik19c95182014-06-23 15:38:48 -0700323 private void enforceStatusBarPermission(String action, int pid, int uid) {
324 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
325 pid, uid) != PackageManager.PERMISSION_GRANTED) {
326 throw new SecurityException("Only system ui may " + action);
327 }
328 }
329
RoboErika5b02322014-05-07 17:05:49 -0700330 /**
331 * This checks if the component is an enabled notification listener for the
332 * specified user. Enabled components may only operate on behalf of the user
333 * they're running as.
334 *
335 * @param compName The component that is enabled.
336 * @param userId The user id of the caller.
337 * @param forUserId The user id they're making the request on behalf of.
338 * @return True if the component is enabled, false otherwise
339 */
340 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
341 int forUserId) {
342 if (userId != forUserId) {
343 // You may not access another user's content as an enabled listener.
344 return false;
345 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700346 if (DEBUG) {
347 Log.d(TAG, "Checking if enabled notification listener " + compName);
348 }
RoboErike7880d82014-04-30 12:48:25 -0700349 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700350 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700351 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700352 userId);
RoboErike7880d82014-04-30 12:48:25 -0700353 if (enabledNotifListeners != null) {
354 final String[] components = enabledNotifListeners.split(":");
355 for (int i = 0; i < components.length; i++) {
356 final ComponentName component =
357 ComponentName.unflattenFromString(components[i]);
358 if (component != null) {
359 if (compName.equals(component)) {
360 if (DEBUG) {
361 Log.d(TAG, "ok to get sessions: " + component +
362 " is authorized notification listener");
363 }
364 return true;
365 }
366 }
367 }
368 }
369 if (DEBUG) {
370 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700371 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700372 }
373 }
374 return false;
375 }
376
RoboErika5b02322014-05-07 17:05:49 -0700377 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700378 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800379 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700380 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800381 }
382 }
383
RoboErik4646d282014-05-13 10:13:04 -0700384 /*
385 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700386 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700387 * 2. It needs to be added to all sessions.
388 * 3. It needs to be added to the priority stack.
389 * 4. It needs to be added to the relevant user record.
390 */
RoboErika5b02322014-05-07 17:05:49 -0700391 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
392 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700393
RoboErika5b02322014-05-07 17:05:49 -0700394 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
395 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800396 try {
397 cb.asBinder().linkToDeath(session, 0);
398 } catch (RemoteException e) {
399 throw new RuntimeException("Media Session owner died prematurely.", e);
400 }
RoboErik4646d282014-05-13 10:13:04 -0700401
402 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700403 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700404
405 UserRecord user = getOrCreateUser(userId);
406 user.addSessionLocked(session);
407
RoboErik2e7a9162014-06-04 16:53:45 -0700408 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
409
RoboErik01fe6612014-02-13 14:19:04 -0800410 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700411 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800412 }
413 return session;
414 }
415
RoboErik4646d282014-05-13 10:13:04 -0700416 private UserRecord getOrCreateUser(int userId) {
417 UserRecord user = mUserRecords.get(userId);
418 if (user == null) {
419 user = new UserRecord(getContext(), userId);
420 mUserRecords.put(userId, user);
421 }
422 return user;
423 }
424
RoboErik2e7a9162014-06-04 16:53:45 -0700425 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
426 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
427 if (mSessionsListeners.get(i).mListener == listener) {
428 return i;
429 }
430 }
431 return -1;
432 }
433
RoboErike7880d82014-04-30 12:48:25 -0700434 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700435 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700436 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700437 }
438
RoboErik2e7a9162014-06-04 16:53:45 -0700439 private void pushSessionsChanged(int userId) {
440 synchronized (mLock) {
441 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
442 int size = records.size();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700443 if (size > 0) {
RoboErikb214efb2014-07-24 13:20:30 -0700444 rememberMediaButtonReceiverLocked(records.get(0));
RoboErik6f0e4dd2014-06-17 16:56:27 -0700445 }
Jeff Browndba34ba2014-06-24 20:46:03 -0700446 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700447 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700448 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700449 }
RoboErik19c95182014-06-23 15:38:48 -0700450 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700451 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
452 SessionsListenerRecord record = mSessionsListeners.get(i);
453 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
454 try {
455 record.mListener.onActiveSessionsChanged(tokens);
456 } catch (RemoteException e) {
457 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
458 e);
459 mSessionsListeners.remove(i);
460 }
461 }
462 }
463 }
464 }
465
RoboErik19c95182014-06-23 15:38:48 -0700466 private void pushRemoteVolumeUpdateLocked(int userId) {
467 if (mRvc != null) {
468 try {
469 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
470 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
471 } catch (RemoteException e) {
472 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
473 }
474 }
475 }
476
RoboErikb214efb2014-07-24 13:20:30 -0700477 private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
478 PendingIntent receiver = record.getMediaButtonReceiver();
479 UserRecord user = mUserRecords.get(record.getUserId());
480 if (receiver != null && user != null) {
481 user.mLastMediaButtonReceiver = receiver;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700482 }
483 }
484
RoboErik4646d282014-05-13 10:13:04 -0700485 /**
486 * Information about a particular user. The contents of this object is
487 * guarded by mLock.
488 */
489 final class UserRecord {
490 private final int mUserId;
RoboErik4646d282014-05-13 10:13:04 -0700491 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
RoboErikb214efb2014-07-24 13:20:30 -0700492 private PendingIntent mLastMediaButtonReceiver;
RoboErik4646d282014-05-13 10:13:04 -0700493
494 public UserRecord(Context context, int userId) {
495 mUserId = userId;
RoboErik4646d282014-05-13 10:13:04 -0700496 }
497
498 public void startLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700499 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700500 }
501
502 public void stopLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700503 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700504 }
505
506 public void destroyLocked() {
507 for (int i = mSessions.size() - 1; i >= 0; i--) {
508 MediaSessionRecord session = mSessions.get(i);
509 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700510 }
511 }
512
RoboErik4646d282014-05-13 10:13:04 -0700513 public ArrayList<MediaSessionRecord> getSessionsLocked() {
514 return mSessions;
515 }
516
517 public void addSessionLocked(MediaSessionRecord session) {
518 mSessions.add(session);
RoboErik4646d282014-05-13 10:13:04 -0700519 }
520
521 public void removeSessionLocked(MediaSessionRecord session) {
522 mSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700523 }
524
525 public void dumpLocked(PrintWriter pw, String prefix) {
526 pw.println(prefix + "Record for user " + mUserId);
527 String indent = prefix + " ";
RoboErikb214efb2014-07-24 13:20:30 -0700528 pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
Jeff Brown01a500e2014-07-10 22:50:50 -0700529 int size = mSessions.size();
RoboErik4646d282014-05-13 10:13:04 -0700530 pw.println(indent + size + " Sessions:");
531 for (int i = 0; i < size; i++) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700532 // Just print the short version, the full session dump will
RoboErik4646d282014-05-13 10:13:04 -0700533 // already be in the list of all sessions.
RoboErikaa4e23b2014-07-24 18:35:11 -0700534 pw.println(indent + mSessions.get(i).toString());
RoboErik4646d282014-05-13 10:13:04 -0700535 }
536 }
RoboErik4646d282014-05-13 10:13:04 -0700537 }
538
RoboErik2e7a9162014-06-04 16:53:45 -0700539 final class SessionsListenerRecord implements IBinder.DeathRecipient {
540 private final IActiveSessionsListener mListener;
RoboErik7aef77b2014-08-08 15:56:54 -0700541 private final ComponentName mComponentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700542 private final int mUserId;
RoboErik7aef77b2014-08-08 15:56:54 -0700543 private final int mPid;
544 private final int mUid;
RoboErik2e7a9162014-06-04 16:53:45 -0700545
RoboErik7aef77b2014-08-08 15:56:54 -0700546 public SessionsListenerRecord(IActiveSessionsListener listener,
547 ComponentName componentName,
548 int userId, int pid, int uid) {
RoboErik2e7a9162014-06-04 16:53:45 -0700549 mListener = listener;
RoboErik7aef77b2014-08-08 15:56:54 -0700550 mComponentName = componentName;
RoboErik2e7a9162014-06-04 16:53:45 -0700551 mUserId = userId;
RoboErik7aef77b2014-08-08 15:56:54 -0700552 mPid = pid;
553 mUid = uid;
RoboErik2e7a9162014-06-04 16:53:45 -0700554 }
555
556 @Override
557 public void binderDied() {
558 synchronized (mLock) {
559 mSessionsListeners.remove(this);
560 }
561 }
562 }
563
RoboErik7aef77b2014-08-08 15:56:54 -0700564 final class SettingsObserver extends ContentObserver {
565 private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
566 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
567
568 private SettingsObserver() {
569 super(null);
570 }
571
572 private void observe() {
573 mContentResolver.registerContentObserver(mSecureSettingsUri,
574 false, this, UserHandle.USER_ALL);
575 }
576
577 @Override
578 public void onChange(boolean selfChange, Uri uri) {
579 updateActiveSessionListeners();
580 }
581 }
582
RoboErik07c70772014-03-20 13:33:52 -0700583 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700584 private static final String EXTRA_WAKELOCK_ACQUIRED =
585 "android.media.AudioService.WAKELOCK_ACQUIRED";
586 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
587
RoboErik9a9d0b52014-05-20 14:53:39 -0700588 private boolean mVoiceButtonDown = false;
589 private boolean mVoiceButtonHandled = false;
590
RoboErik07c70772014-03-20 13:33:52 -0700591 @Override
RoboErika5b02322014-05-07 17:05:49 -0700592 public ISession createSession(String packageName, ISessionCallback cb, String tag,
593 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800594 final int pid = Binder.getCallingPid();
595 final int uid = Binder.getCallingUid();
596 final long token = Binder.clearCallingIdentity();
597 try {
598 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700599 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
600 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800601 if (cb == null) {
602 throw new IllegalArgumentException("Controller callback cannot be null");
603 }
RoboErika5b02322014-05-07 17:05:49 -0700604 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
605 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700606 } finally {
607 Binder.restoreCallingIdentity(token);
608 }
609 }
610
611 @Override
RoboErika5b02322014-05-07 17:05:49 -0700612 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700613 final int pid = Binder.getCallingPid();
614 final int uid = Binder.getCallingUid();
615 final long token = Binder.clearCallingIdentity();
616
617 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700618 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700619 ArrayList<IBinder> binders = new ArrayList<IBinder>();
620 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700621 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700622 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700623 int size = records.size();
624 for (int i = 0; i < size; i++) {
625 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700626 }
627 }
628 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800629 } finally {
630 Binder.restoreCallingIdentity(token);
631 }
632 }
RoboErika278ea72014-04-24 14:49:01 -0700633
RoboErik2e7a9162014-06-04 16:53:45 -0700634 @Override
635 public void addSessionsListener(IActiveSessionsListener listener,
636 ComponentName componentName, int userId) throws RemoteException {
637 final int pid = Binder.getCallingPid();
638 final int uid = Binder.getCallingUid();
639 final long token = Binder.clearCallingIdentity();
640
641 try {
642 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
643 synchronized (mLock) {
644 int index = findIndexOfSessionsListenerLocked(listener);
645 if (index != -1) {
646 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
647 return;
648 }
649 SessionsListenerRecord record = new SessionsListenerRecord(listener,
RoboErik7aef77b2014-08-08 15:56:54 -0700650 componentName, resolvedUserId, pid, uid);
RoboErik2e7a9162014-06-04 16:53:45 -0700651 try {
652 listener.asBinder().linkToDeath(record, 0);
653 } catch (RemoteException e) {
654 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
655 return;
656 }
657 mSessionsListeners.add(record);
658 }
659 } finally {
660 Binder.restoreCallingIdentity(token);
661 }
662 }
663
664 @Override
665 public void removeSessionsListener(IActiveSessionsListener listener)
666 throws RemoteException {
667 synchronized (mLock) {
668 int index = findIndexOfSessionsListenerLocked(listener);
669 if (index != -1) {
670 SessionsListenerRecord record = mSessionsListeners.remove(index);
671 try {
672 record.mListener.asBinder().unlinkToDeath(record, 0);
673 } catch (Exception e) {
674 // ignore exceptions, the record is being removed
675 }
676 }
677 }
678 }
679
RoboErik8a2cfc32014-05-16 11:19:38 -0700680 /**
681 * Handles the dispatching of the media button events to one of the
682 * registered listeners, or if there was none, broadcast an
683 * ACTION_MEDIA_BUTTON intent to the rest of the system.
684 *
685 * @param keyEvent a non-null KeyEvent whose key code is one of the
686 * supported media buttons
687 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
688 * while this key event is dispatched.
689 */
690 @Override
691 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
692 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
693 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
694 return;
695 }
696 final int pid = Binder.getCallingPid();
697 final int uid = Binder.getCallingUid();
698 final long token = Binder.clearCallingIdentity();
699
700 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700701 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700702 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700703 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700704 if (isVoiceKey(keyEvent.getKeyCode())) {
705 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700706 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700707 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700708 }
709 }
710 } finally {
711 Binder.restoreCallingIdentity(token);
712 }
713 }
714
RoboErika278ea72014-04-24 14:49:01 -0700715 @Override
RoboErik1ff5b162014-07-15 17:23:18 -0700716 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
RoboErikb69ffd42014-05-30 14:57:59 -0700717 throws RemoteException {
718 final int pid = Binder.getCallingPid();
719 final int uid = Binder.getCallingUid();
720 final long token = Binder.clearCallingIdentity();
721 try {
722 synchronized (mLock) {
723 MediaSessionRecord session = mPriorityStack
724 .getDefaultVolumeSession(mCurrentUserId);
RoboErik1ff5b162014-07-15 17:23:18 -0700725 dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
RoboErikb69ffd42014-05-30 14:57:59 -0700726 }
727 } finally {
728 Binder.restoreCallingIdentity(token);
729 }
730 }
731
732 @Override
RoboErik19c95182014-06-23 15:38:48 -0700733 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
734 final int pid = Binder.getCallingPid();
735 final int uid = Binder.getCallingUid();
736 final long token = Binder.clearCallingIdentity();
737 try {
738 enforceStatusBarPermission("listen for volume changes", pid, uid);
739 mRvc = rvc;
740 } finally {
741 Binder.restoreCallingIdentity(token);
742 }
743 }
744
745 @Override
RoboErika278ea72014-04-24 14:49:01 -0700746 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
747 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
748 != PackageManager.PERMISSION_GRANTED) {
749 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
750 + Binder.getCallingPid()
751 + ", uid=" + Binder.getCallingUid());
752 return;
753 }
754
755 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
756 pw.println();
757
758 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700759 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700760 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700761 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700762 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700763 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700764 }
RoboErika5b02322014-05-07 17:05:49 -0700765 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700766
RoboErik4646d282014-05-13 10:13:04 -0700767 pw.println("User Records:");
768 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700769 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700770 UserRecord user = mUserRecords.get(i);
771 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700772 }
773 }
774 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700775
RoboErik2e7a9162014-06-04 16:53:45 -0700776 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
777 final int uid) {
778 String packageName = null;
779 if (componentName != null) {
780 // If they gave us a component name verify they own the
781 // package
782 packageName = componentName.getPackageName();
783 enforcePackageName(packageName, uid);
784 }
785 // Check that they can make calls on behalf of the user and
786 // get the final user id
787 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
788 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
789 // Check if they have the permissions or their component is
790 // enabled for the user they're calling from.
791 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
792 return resolvedUserId;
793 }
794
RoboErik1ff5b162014-07-15 17:23:18 -0700795 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
RoboErikb69ffd42014-05-30 14:57:59 -0700796 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700797 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700798 String description = session == null ? null : session.toString();
799 Log.d(TAG, "Adjusting session " + description + " by " + direction + ". flags="
800 + flags + ", suggestedStream=" + suggestedStream);
RoboErikb69ffd42014-05-30 14:57:59 -0700801
802 }
803 if (session == null) {
RoboErik94c716e2014-09-14 13:54:31 -0700804 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0
805 && !AudioSystem.isStreamActive(AudioManager.STREAM_MUSIC, 0)) {
RoboErik3c45c292014-07-08 16:47:31 -0700806 if (DEBUG) {
807 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -0700808 }
RoboErikb7c014c2014-07-22 15:58:22 -0700809 return;
RoboErik3c45c292014-07-08 16:47:31 -0700810 }
RoboErik0791e172014-06-08 10:52:32 -0700811 try {
RoboErik1ff5b162014-07-15 17:23:18 -0700812 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
813 getContext().getOpPackageName());
RoboErik0791e172014-06-08 10:52:32 -0700814 } catch (RemoteException e) {
815 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700816 }
817 } else {
RoboErik0dac35a2014-08-12 15:48:49 -0700818 session.adjustVolume(direction, flags, getContext().getPackageName(),
RoboErik272e1612014-09-05 11:39:29 -0700819 UserHandle.myUserId(), true);
RoboErikd2b8c942014-08-19 11:23:40 -0700820 if (session.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE
RoboErik851d2d52014-06-27 18:02:40 -0700821 && mRvc != null) {
RoboErik19c95182014-06-23 15:38:48 -0700822 try {
823 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
824 } catch (Exception e) {
825 Log.wtf(TAG, "Error sending volume change to system UI.", e);
826 }
827 }
RoboErikb69ffd42014-05-30 14:57:59 -0700828 }
829 }
830
RoboErik9a9d0b52014-05-20 14:53:39 -0700831 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
832 MediaSessionRecord session) {
833 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
834 // If the phone app has priority just give it the event
835 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
836 return;
837 }
838 int action = keyEvent.getAction();
839 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
840 if (action == KeyEvent.ACTION_DOWN) {
841 if (keyEvent.getRepeatCount() == 0) {
842 mVoiceButtonDown = true;
843 mVoiceButtonHandled = false;
844 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
845 mVoiceButtonHandled = true;
846 startVoiceInput(needWakeLock);
847 }
848 } else if (action == KeyEvent.ACTION_UP) {
849 if (mVoiceButtonDown) {
850 mVoiceButtonDown = false;
851 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
852 // Resend the down then send this event through
853 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
854 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
855 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
856 }
857 }
858 }
859 }
860
861 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
862 MediaSessionRecord session) {
863 if (session != null) {
864 if (DEBUG) {
RoboErikaa4e23b2014-07-24 18:35:11 -0700865 Log.d(TAG, "Sending media key to " + session.toString());
RoboErik9a9d0b52014-05-20 14:53:39 -0700866 }
867 if (needWakeLock) {
868 mKeyEventReceiver.aquireWakeLockLocked();
869 }
870 // If we don't need a wakelock use -1 as the id so we
871 // won't release it later
872 session.sendMediaButton(keyEvent,
873 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
874 mKeyEventReceiver);
875 } else {
RoboErikb214efb2014-07-24 13:20:30 -0700876 // Launch the last PendingIntent we had with priority
877 int userId = ActivityManager.getCurrentUser();
878 UserRecord user = mUserRecords.get(userId);
879 if (user.mLastMediaButtonReceiver != null) {
880 if (DEBUG) {
881 Log.d(TAG, "Sending media key to last known PendingIntent");
882 }
883 if (needWakeLock) {
884 mKeyEventReceiver.aquireWakeLockLocked();
885 }
886 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
887 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
888 try {
889 user.mLastMediaButtonReceiver.send(getContext(),
890 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
891 mediaButtonIntent, mKeyEventReceiver, null);
892 } catch (CanceledException e) {
893 Log.i(TAG, "Error sending key event to media button receiver "
894 + user.mLastMediaButtonReceiver, e);
895 }
896 } else {
897 if (DEBUG) {
898 Log.d(TAG, "Sending media key ordered broadcast");
899 }
900 if (needWakeLock) {
901 mMediaEventWakeLock.acquire();
902 }
903 // Fallback to legacy behavior
904 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
905 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
906 if (needWakeLock) {
907 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
908 WAKELOCK_RELEASE_ON_FINISHED);
909 }
910 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
911 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
RoboErik9a9d0b52014-05-20 14:53:39 -0700912 }
RoboErik9a9d0b52014-05-20 14:53:39 -0700913 }
914 }
915
916 private void startVoiceInput(boolean needWakeLock) {
917 Intent voiceIntent = null;
918 // select which type of search to launch:
919 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
920 // - device locked or screen off: action is
921 // ACTION_VOICE_SEARCH_HANDS_FREE
922 // with EXTRA_SECURE set to true if the device is securely locked
923 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
924 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
925 if (!isLocked && pm.isScreenOn()) {
926 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
927 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
928 } else {
929 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
930 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
931 isLocked && mKeyguardManager.isKeyguardSecure());
932 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
933 }
934 // start the search activity
935 if (needWakeLock) {
936 mMediaEventWakeLock.acquire();
937 }
938 try {
939 if (voiceIntent != null) {
940 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
941 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
942 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
943 }
944 } catch (ActivityNotFoundException e) {
945 Log.w(TAG, "No activity for search: " + e);
946 } finally {
947 if (needWakeLock) {
948 mMediaEventWakeLock.release();
949 }
950 }
951 }
952
953 private boolean isVoiceKey(int keyCode) {
954 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
955 }
956
RoboErik418c10c2014-05-19 09:25:25 -0700957 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
958
RoboErikb214efb2014-07-24 13:20:30 -0700959 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
960 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -0700961 private final Handler mHandler;
962 private int mRefCount = 0;
963 private int mLastTimeoutId = 0;
964
965 public KeyEventWakeLockReceiver(Handler handler) {
966 super(handler);
967 mHandler = handler;
968 }
969
970 public void onTimeout() {
971 synchronized (mLock) {
972 if (mRefCount == 0) {
973 // We've already released it, so just return
974 return;
975 }
976 mLastTimeoutId++;
977 mRefCount = 0;
978 releaseWakeLockLocked();
979 }
980 }
981
982 public void aquireWakeLockLocked() {
983 if (mRefCount == 0) {
984 mMediaEventWakeLock.acquire();
985 }
986 mRefCount++;
987 mHandler.removeCallbacks(this);
988 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
989
990 }
991
992 @Override
993 public void run() {
994 onTimeout();
995 }
996
RoboErik8a2cfc32014-05-16 11:19:38 -0700997 @Override
998 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -0700999 if (resultCode < mLastTimeoutId) {
1000 // Ignore results from calls that were before the last
1001 // timeout, just in case.
1002 return;
1003 } else {
1004 synchronized (mLock) {
1005 if (mRefCount > 0) {
1006 mRefCount--;
1007 if (mRefCount == 0) {
1008 releaseWakeLockLocked();
1009 }
1010 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001011 }
1012 }
1013 }
RoboErik418c10c2014-05-19 09:25:25 -07001014
1015 private void releaseWakeLockLocked() {
1016 mMediaEventWakeLock.release();
1017 mHandler.removeCallbacks(this);
1018 }
RoboErikb214efb2014-07-24 13:20:30 -07001019
1020 @Override
1021 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1022 String resultData, Bundle resultExtras) {
1023 onReceiveResult(resultCode, null);
1024 }
RoboErik8a2cfc32014-05-16 11:19:38 -07001025 };
1026
1027 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1028 @Override
1029 public void onReceive(Context context, Intent intent) {
1030 if (intent == null) {
1031 return;
1032 }
1033 Bundle extras = intent.getExtras();
1034 if (extras == null) {
1035 return;
1036 }
1037 synchronized (mLock) {
1038 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1039 && mMediaEventWakeLock.isHeld()) {
1040 mMediaEventWakeLock.release();
1041 }
1042 }
1043 }
1044 };
RoboErik01fe6612014-02-13 14:19:04 -08001045 }
1046
RoboErik2e7a9162014-06-04 16:53:45 -07001047 final class MessageHandler extends Handler {
1048 private static final int MSG_SESSIONS_CHANGED = 1;
1049
1050 @Override
1051 public void handleMessage(Message msg) {
1052 switch (msg.what) {
1053 case MSG_SESSIONS_CHANGED:
1054 pushSessionsChanged(msg.arg1);
1055 break;
1056 }
1057 }
1058
1059 public void post(int what, int arg1, int arg2) {
1060 obtainMessage(what, arg1, arg2).sendToTarget();
1061 }
1062 }
RoboErik01fe6612014-02-13 14:19:04 -08001063}