blob: b0ccd622715ad8551904fd57cf9dcacedc5a7b8f [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;
23import android.content.ActivityNotFoundException;
RoboErik8a2cfc32014-05-16 11:19:38 -070024import android.content.BroadcastReceiver;
RoboErike7880d82014-04-30 12:48:25 -070025import android.content.ComponentName;
RoboErik6f0e4dd2014-06-17 16:56:27 -070026import android.content.ContentResolver;
RoboErik01fe6612014-02-13 14:19:04 -080027import android.content.Context;
RoboErik8a2cfc32014-05-16 11:19:38 -070028import android.content.Intent;
RoboErika278ea72014-04-24 14:49:01 -070029import android.content.pm.PackageManager;
RoboErik3c45c292014-07-08 16:47:31 -070030import android.media.AudioManager;
RoboErikb69ffd42014-05-30 14:57:59 -070031import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070032import android.media.IRemoteVolumeController;
RoboErik2e7a9162014-06-04 16:53:45 -070033import android.media.session.IActiveSessionsListener;
RoboErik07c70772014-03-20 13:33:52 -070034import android.media.session.ISession;
35import android.media.session.ISessionCallback;
36import android.media.session.ISessionManager;
Jeff Browndba34ba2014-06-24 20:46:03 -070037import android.media.session.MediaSession;
RoboErik01fe6612014-02-13 14:19:04 -080038import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070039import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080040import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070041import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070042import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070043import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080044import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070045import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070046import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070047import android.os.UserHandle;
48import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070049import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080050import android.text.TextUtils;
51import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070052import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070053import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080054
55import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070056import com.android.server.Watchdog;
57import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080058
RoboErika278ea72014-04-24 14:49:01 -070059import java.io.FileDescriptor;
60import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080061import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070062import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080063
64/**
65 * System implementation of MediaSessionManager
66 */
RoboErika278ea72014-04-24 14:49:01 -070067public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080068 private static final String TAG = "MediaSessionService";
69 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
70
RoboErik418c10c2014-05-19 09:25:25 -070071 private static final int WAKELOCK_TIMEOUT = 5000;
72
RoboErik01fe6612014-02-13 14:19:04 -080073 private final SessionManagerImpl mSessionManagerImpl;
RoboErika8f95142014-05-05 14:23:49 -070074 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080075
RoboErik4646d282014-05-13 10:13:04 -070076 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
77 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070078 private final ArrayList<SessionsListenerRecord> mSessionsListeners
79 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -080080 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070081 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070082 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080083
RoboErik9a9d0b52014-05-20 14:53:39 -070084 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070085 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070086 private ContentResolver mContentResolver;
RoboErik9a9d0b52014-05-20 14:53:39 -070087
RoboErike7880d82014-04-30 12:48:25 -070088 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070089 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070090
RoboErik19c95182014-06-23 15:38:48 -070091 // Used to notify system UI when remote volume was changed. TODO find a
92 // better way to handle this.
93 private IRemoteVolumeController mRvc;
94
RoboErik01fe6612014-02-13 14:19:04 -080095 public MediaSessionService(Context context) {
96 super(context);
97 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -070098 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -070099 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
100 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -0800101 }
102
103 @Override
104 public void onStart() {
105 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700106 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700107 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700108 mKeyguardManager =
109 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700110 mAudioService = getAudioService();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700111 mContentResolver = getContext().getContentResolver();
RoboErikb69ffd42014-05-30 14:57:59 -0700112 }
113
114 private IAudioService getAudioService() {
115 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
116 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700117 }
118
RoboErika8f95142014-05-05 14:23:49 -0700119 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700120 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700121 if (!mAllSessions.contains(record)) {
122 Log.d(TAG, "Unknown session updated. Ignoring.");
123 return;
124 }
RoboErika8f95142014-05-05 14:23:49 -0700125 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700126 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700127 if (record.isActive()) {
128 if (mPrioritySession != null) {
129 Log.w(TAG, "Replacing existing priority session with a new session");
130 }
131 mPrioritySession = record;
132 } else {
133 if (mPrioritySession == record) {
134 mPrioritySession = null;
135 }
RoboErike7880d82014-04-30 12:48:25 -0700136 }
RoboErike7880d82014-04-30 12:48:25 -0700137 }
138 }
RoboErik2e7a9162014-06-04 16:53:45 -0700139 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700140 }
141
RoboErika8f95142014-05-05 14:23:49 -0700142 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700143 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700144 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700145 if (!mAllSessions.contains(record)) {
146 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
147 return;
148 }
RoboErik2e7a9162014-06-04 16:53:45 -0700149 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
150 }
151 if (updateSessions) {
152 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700153 }
154 }
155
RoboErik19c95182014-06-23 15:38:48 -0700156 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
157 synchronized (mLock) {
158 if (!mAllSessions.contains(record)) {
159 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
160 return;
161 }
162 pushRemoteVolumeUpdateLocked(record.getUserId());
163 }
164 }
165
RoboErika278ea72014-04-24 14:49:01 -0700166 @Override
RoboErik4646d282014-05-13 10:13:04 -0700167 public void onStartUser(int userHandle) {
168 updateUser();
169 }
170
171 @Override
172 public void onSwitchUser(int userHandle) {
173 updateUser();
174 }
175
176 @Override
177 public void onStopUser(int userHandle) {
178 synchronized (mLock) {
179 UserRecord user = mUserRecords.get(userHandle);
180 if (user != null) {
181 destroyUserLocked(user);
182 }
183 }
184 }
185
186 @Override
RoboErika278ea72014-04-24 14:49:01 -0700187 public void monitor() {
188 synchronized (mLock) {
189 // Check for deadlock
190 }
191 }
192
RoboErik4646d282014-05-13 10:13:04 -0700193 protected void enforcePhoneStatePermission(int pid, int uid) {
194 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
195 != PackageManager.PERMISSION_GRANTED) {
196 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
197 }
198 }
199
RoboErik01fe6612014-02-13 14:19:04 -0800200 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700201 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800202 destroySessionLocked(session);
203 }
204 }
205
206 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700207 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800208 destroySessionLocked(session);
209 }
210 }
211
RoboErik4646d282014-05-13 10:13:04 -0700212 private void updateUser() {
213 synchronized (mLock) {
214 int userId = ActivityManager.getCurrentUser();
215 if (mCurrentUserId != userId) {
216 final int oldUserId = mCurrentUserId;
217 mCurrentUserId = userId; // do this first
218
219 UserRecord oldUser = mUserRecords.get(oldUserId);
220 if (oldUser != null) {
221 oldUser.stopLocked();
222 }
223
224 UserRecord newUser = getOrCreateUser(userId);
225 newUser.startLocked();
226 }
227 }
228 }
229
230 /**
231 * Stop the user and unbind from everything.
232 *
233 * @param user The user to dispose of
234 */
235 private void destroyUserLocked(UserRecord user) {
236 user.stopLocked();
237 user.destroyLocked();
238 mUserRecords.remove(user.mUserId);
239 }
240
241 /*
242 * When a session is removed several things need to happen.
243 * 1. We need to remove it from the relevant user.
244 * 2. We need to remove it from the priority stack.
245 * 3. We need to remove it from all sessions.
246 * 4. If this is the system priority session we need to clear it.
247 * 5. We need to unlink to death from the cb binder
248 * 6. We need to tell the session to do any final cleanup (onDestroy)
249 */
RoboErik01fe6612014-02-13 14:19:04 -0800250 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700251 int userId = session.getUserId();
252 UserRecord user = mUserRecords.get(userId);
253 if (user != null) {
254 user.removeSessionLocked(session);
255 }
256
RoboErika8f95142014-05-05 14:23:49 -0700257 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700258 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700259 if (session == mPrioritySession) {
260 mPrioritySession = null;
261 }
RoboErik4646d282014-05-13 10:13:04 -0700262
263 try {
264 session.getCallback().asBinder().unlinkToDeath(session, 0);
265 } catch (Exception e) {
266 // ignore exceptions while destroying a session.
267 }
268 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700269
270 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800271 }
272
273 private void enforcePackageName(String packageName, int uid) {
274 if (TextUtils.isEmpty(packageName)) {
275 throw new IllegalArgumentException("packageName may not be empty");
276 }
277 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
278 final int packageCount = packages.length;
279 for (int i = 0; i < packageCount; i++) {
280 if (packageName.equals(packages[i])) {
281 return;
282 }
283 }
284 throw new IllegalArgumentException("packageName is not owned by the calling process");
285 }
286
RoboErike7880d82014-04-30 12:48:25 -0700287 /**
288 * Checks a caller's authorization to register an IRemoteControlDisplay.
289 * Authorization is granted if one of the following is true:
290 * <ul>
291 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
292 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700293 * <li>the caller's listener is one of the enabled notification listeners
294 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700295 * </ul>
296 */
RoboErika5b02322014-05-07 17:05:49 -0700297 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
298 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700299 if (getContext()
300 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
301 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700302 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
303 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700304 throw new SecurityException("Missing permission to control media.");
305 }
306 }
307
RoboErik19c95182014-06-23 15:38:48 -0700308 private void enforceStatusBarPermission(String action, int pid, int uid) {
309 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
310 pid, uid) != PackageManager.PERMISSION_GRANTED) {
311 throw new SecurityException("Only system ui may " + action);
312 }
313 }
314
RoboErika5b02322014-05-07 17:05:49 -0700315 /**
316 * This checks if the component is an enabled notification listener for the
317 * specified user. Enabled components may only operate on behalf of the user
318 * they're running as.
319 *
320 * @param compName The component that is enabled.
321 * @param userId The user id of the caller.
322 * @param forUserId The user id they're making the request on behalf of.
323 * @return True if the component is enabled, false otherwise
324 */
325 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
326 int forUserId) {
327 if (userId != forUserId) {
328 // You may not access another user's content as an enabled listener.
329 return false;
330 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700331 if (DEBUG) {
332 Log.d(TAG, "Checking if enabled notification listener " + compName);
333 }
RoboErike7880d82014-04-30 12:48:25 -0700334 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700335 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700336 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700337 userId);
RoboErike7880d82014-04-30 12:48:25 -0700338 if (enabledNotifListeners != null) {
339 final String[] components = enabledNotifListeners.split(":");
340 for (int i = 0; i < components.length; i++) {
341 final ComponentName component =
342 ComponentName.unflattenFromString(components[i]);
343 if (component != null) {
344 if (compName.equals(component)) {
345 if (DEBUG) {
346 Log.d(TAG, "ok to get sessions: " + component +
347 " is authorized notification listener");
348 }
349 return true;
350 }
351 }
352 }
353 }
354 if (DEBUG) {
355 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700356 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700357 }
358 }
359 return false;
360 }
361
RoboErika5b02322014-05-07 17:05:49 -0700362 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700363 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800364 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700365 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800366 }
367 }
368
RoboErik4646d282014-05-13 10:13:04 -0700369 /*
370 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700371 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700372 * 2. It needs to be added to all sessions.
373 * 3. It needs to be added to the priority stack.
374 * 4. It needs to be added to the relevant user record.
375 */
RoboErika5b02322014-05-07 17:05:49 -0700376 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
377 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700378
RoboErika5b02322014-05-07 17:05:49 -0700379 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
380 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800381 try {
382 cb.asBinder().linkToDeath(session, 0);
383 } catch (RemoteException e) {
384 throw new RuntimeException("Media Session owner died prematurely.", e);
385 }
RoboErik4646d282014-05-13 10:13:04 -0700386
387 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700388 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700389
390 UserRecord user = getOrCreateUser(userId);
391 user.addSessionLocked(session);
392
RoboErik2e7a9162014-06-04 16:53:45 -0700393 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
394
RoboErik01fe6612014-02-13 14:19:04 -0800395 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700396 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800397 }
398 return session;
399 }
400
RoboErik4646d282014-05-13 10:13:04 -0700401 private UserRecord getOrCreateUser(int userId) {
402 UserRecord user = mUserRecords.get(userId);
403 if (user == null) {
404 user = new UserRecord(getContext(), userId);
405 mUserRecords.put(userId, user);
406 }
407 return user;
408 }
409
RoboErika8f95142014-05-05 14:23:49 -0700410 private int findIndexOfSessionForIdLocked(String sessionId) {
RoboErik4646d282014-05-13 10:13:04 -0700411 for (int i = mAllSessions.size() - 1; i >= 0; i--) {
412 MediaSessionRecord session = mAllSessions.get(i);
RoboErika8f95142014-05-05 14:23:49 -0700413 if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
414 return i;
415 }
416 }
417 return -1;
418 }
419
RoboErik2e7a9162014-06-04 16:53:45 -0700420 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
421 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
422 if (mSessionsListeners.get(i).mListener == listener) {
423 return i;
424 }
425 }
426 return -1;
427 }
428
RoboErike7880d82014-04-30 12:48:25 -0700429 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700430 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700431 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700432 }
433
RoboErik2e7a9162014-06-04 16:53:45 -0700434 private void pushSessionsChanged(int userId) {
435 synchronized (mLock) {
436 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
437 int size = records.size();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700438 if (size > 0) {
439 persistMediaButtonReceiverLocked(records.get(0));
440 }
Jeff Browndba34ba2014-06-24 20:46:03 -0700441 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700442 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700443 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700444 }
RoboErik19c95182014-06-23 15:38:48 -0700445 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700446 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
447 SessionsListenerRecord record = mSessionsListeners.get(i);
448 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
449 try {
450 record.mListener.onActiveSessionsChanged(tokens);
451 } catch (RemoteException e) {
452 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
453 e);
454 mSessionsListeners.remove(i);
455 }
456 }
457 }
458 }
459 }
460
RoboErik19c95182014-06-23 15:38:48 -0700461 private void pushRemoteVolumeUpdateLocked(int userId) {
462 if (mRvc != null) {
463 try {
464 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
465 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
466 } catch (RemoteException e) {
467 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
468 }
469 }
470 }
471
RoboErik6f0e4dd2014-06-17 16:56:27 -0700472 private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
473 ComponentName receiver = record.getMediaButtonReceiver();
474 if (receiver != null) {
475 Settings.System.putStringForUser(mContentResolver,
476 Settings.System.MEDIA_BUTTON_RECEIVER,
477 receiver == null ? "" : receiver.flattenToString(),
478 UserHandle.USER_CURRENT);
479 }
480 }
481
RoboErik4646d282014-05-13 10:13:04 -0700482 /**
483 * Information about a particular user. The contents of this object is
484 * guarded by mLock.
485 */
486 final class UserRecord {
487 private final int mUserId;
RoboErik4646d282014-05-13 10:13:04 -0700488 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
489
490 public UserRecord(Context context, int userId) {
491 mUserId = userId;
RoboErik4646d282014-05-13 10:13:04 -0700492 }
493
494 public void startLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700495 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700496 }
497
498 public void stopLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700499 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700500 }
501
502 public void destroyLocked() {
503 for (int i = mSessions.size() - 1; i >= 0; i--) {
504 MediaSessionRecord session = mSessions.get(i);
505 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700506 }
507 }
508
RoboErik4646d282014-05-13 10:13:04 -0700509 public ArrayList<MediaSessionRecord> getSessionsLocked() {
510 return mSessions;
511 }
512
513 public void addSessionLocked(MediaSessionRecord session) {
514 mSessions.add(session);
RoboErik4646d282014-05-13 10:13:04 -0700515 }
516
517 public void removeSessionLocked(MediaSessionRecord session) {
518 mSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700519 }
520
521 public void dumpLocked(PrintWriter pw, String prefix) {
522 pw.println(prefix + "Record for user " + mUserId);
523 String indent = prefix + " ";
Jeff Brown01a500e2014-07-10 22:50:50 -0700524 int size = mSessions.size();
RoboErik4646d282014-05-13 10:13:04 -0700525 pw.println(indent + size + " Sessions:");
526 for (int i = 0; i < size; i++) {
527 // Just print the session info, the full session dump will
528 // already be in the list of all sessions.
529 pw.println(indent + mSessions.get(i).getSessionInfo());
530 }
531 }
RoboErik4646d282014-05-13 10:13:04 -0700532 }
533
RoboErik2e7a9162014-06-04 16:53:45 -0700534 final class SessionsListenerRecord implements IBinder.DeathRecipient {
535 private final IActiveSessionsListener mListener;
536 private final int mUserId;
537
538 public SessionsListenerRecord(IActiveSessionsListener listener, int userId) {
539 mListener = listener;
540 mUserId = userId;
541 }
542
543 @Override
544 public void binderDied() {
545 synchronized (mLock) {
546 mSessionsListeners.remove(this);
547 }
548 }
549 }
550
RoboErik07c70772014-03-20 13:33:52 -0700551 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700552 private static final String EXTRA_WAKELOCK_ACQUIRED =
553 "android.media.AudioService.WAKELOCK_ACQUIRED";
554 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
555
RoboErik9a9d0b52014-05-20 14:53:39 -0700556 private boolean mVoiceButtonDown = false;
557 private boolean mVoiceButtonHandled = false;
558
RoboErik07c70772014-03-20 13:33:52 -0700559 @Override
RoboErika5b02322014-05-07 17:05:49 -0700560 public ISession createSession(String packageName, ISessionCallback cb, String tag,
561 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800562 final int pid = Binder.getCallingPid();
563 final int uid = Binder.getCallingUid();
564 final long token = Binder.clearCallingIdentity();
565 try {
566 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700567 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
568 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800569 if (cb == null) {
570 throw new IllegalArgumentException("Controller callback cannot be null");
571 }
RoboErika5b02322014-05-07 17:05:49 -0700572 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
573 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700574 } finally {
575 Binder.restoreCallingIdentity(token);
576 }
577 }
578
579 @Override
RoboErika5b02322014-05-07 17:05:49 -0700580 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700581 final int pid = Binder.getCallingPid();
582 final int uid = Binder.getCallingUid();
583 final long token = Binder.clearCallingIdentity();
584
585 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700586 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700587 ArrayList<IBinder> binders = new ArrayList<IBinder>();
588 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700589 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700590 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700591 int size = records.size();
592 for (int i = 0; i < size; i++) {
593 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700594 }
595 }
596 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800597 } finally {
598 Binder.restoreCallingIdentity(token);
599 }
600 }
RoboErika278ea72014-04-24 14:49:01 -0700601
RoboErik2e7a9162014-06-04 16:53:45 -0700602 @Override
603 public void addSessionsListener(IActiveSessionsListener listener,
604 ComponentName componentName, int userId) throws RemoteException {
605 final int pid = Binder.getCallingPid();
606 final int uid = Binder.getCallingUid();
607 final long token = Binder.clearCallingIdentity();
608
609 try {
610 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
611 synchronized (mLock) {
612 int index = findIndexOfSessionsListenerLocked(listener);
613 if (index != -1) {
614 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
615 return;
616 }
617 SessionsListenerRecord record = new SessionsListenerRecord(listener,
618 resolvedUserId);
619 try {
620 listener.asBinder().linkToDeath(record, 0);
621 } catch (RemoteException e) {
622 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
623 return;
624 }
625 mSessionsListeners.add(record);
626 }
627 } finally {
628 Binder.restoreCallingIdentity(token);
629 }
630 }
631
632 @Override
633 public void removeSessionsListener(IActiveSessionsListener listener)
634 throws RemoteException {
635 synchronized (mLock) {
636 int index = findIndexOfSessionsListenerLocked(listener);
637 if (index != -1) {
638 SessionsListenerRecord record = mSessionsListeners.remove(index);
639 try {
640 record.mListener.asBinder().unlinkToDeath(record, 0);
641 } catch (Exception e) {
642 // ignore exceptions, the record is being removed
643 }
644 }
645 }
646 }
647
RoboErik8a2cfc32014-05-16 11:19:38 -0700648 /**
649 * Handles the dispatching of the media button events to one of the
650 * registered listeners, or if there was none, broadcast an
651 * ACTION_MEDIA_BUTTON intent to the rest of the system.
652 *
653 * @param keyEvent a non-null KeyEvent whose key code is one of the
654 * supported media buttons
655 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
656 * while this key event is dispatched.
657 */
658 @Override
659 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
660 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
661 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
662 return;
663 }
664 final int pid = Binder.getCallingPid();
665 final int uid = Binder.getCallingUid();
666 final long token = Binder.clearCallingIdentity();
667
668 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700669 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700670 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700671 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700672 if (isVoiceKey(keyEvent.getKeyCode())) {
673 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700674 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700675 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700676 }
677 }
678 } finally {
679 Binder.restoreCallingIdentity(token);
680 }
681 }
682
RoboErika278ea72014-04-24 14:49:01 -0700683 @Override
RoboErik1ff5b162014-07-15 17:23:18 -0700684 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
RoboErikb69ffd42014-05-30 14:57:59 -0700685 throws RemoteException {
686 final int pid = Binder.getCallingPid();
687 final int uid = Binder.getCallingUid();
688 final long token = Binder.clearCallingIdentity();
689 try {
690 synchronized (mLock) {
691 MediaSessionRecord session = mPriorityStack
692 .getDefaultVolumeSession(mCurrentUserId);
RoboErik1ff5b162014-07-15 17:23:18 -0700693 dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
RoboErikb69ffd42014-05-30 14:57:59 -0700694 }
695 } finally {
696 Binder.restoreCallingIdentity(token);
697 }
698 }
699
700 @Override
RoboErik19c95182014-06-23 15:38:48 -0700701 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
702 final int pid = Binder.getCallingPid();
703 final int uid = Binder.getCallingUid();
704 final long token = Binder.clearCallingIdentity();
705 try {
706 enforceStatusBarPermission("listen for volume changes", pid, uid);
707 mRvc = rvc;
708 } finally {
709 Binder.restoreCallingIdentity(token);
710 }
711 }
712
713 @Override
RoboErika278ea72014-04-24 14:49:01 -0700714 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
715 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
716 != PackageManager.PERMISSION_GRANTED) {
717 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
718 + Binder.getCallingPid()
719 + ", uid=" + Binder.getCallingUid());
720 return;
721 }
722
723 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
724 pw.println();
725
726 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700727 pw.println("Session for calls:" + mPrioritySession);
728 if (mPrioritySession != null) {
729 mPrioritySession.dump(pw, "");
730 }
RoboErik4646d282014-05-13 10:13:04 -0700731 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700732 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700733 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700734 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700735 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700736 }
RoboErika5b02322014-05-07 17:05:49 -0700737 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700738
RoboErik4646d282014-05-13 10:13:04 -0700739 pw.println("User Records:");
740 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700741 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700742 UserRecord user = mUserRecords.get(i);
743 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700744 }
745 }
746 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700747
RoboErik2e7a9162014-06-04 16:53:45 -0700748 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
749 final int uid) {
750 String packageName = null;
751 if (componentName != null) {
752 // If they gave us a component name verify they own the
753 // package
754 packageName = componentName.getPackageName();
755 enforcePackageName(packageName, uid);
756 }
757 // Check that they can make calls on behalf of the user and
758 // get the final user id
759 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
760 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
761 // Check if they have the permissions or their component is
762 // enabled for the user they're calling from.
763 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
764 return resolvedUserId;
765 }
766
RoboErik1ff5b162014-07-15 17:23:18 -0700767 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
RoboErikb69ffd42014-05-30 14:57:59 -0700768 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700769 if (DEBUG) {
770 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
RoboErik1ff5b162014-07-15 17:23:18 -0700771 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + direction + ". flags=" + flags
RoboErikb69ffd42014-05-30 14:57:59 -0700772 + ", suggestedStream=" + suggestedStream);
773
774 }
775 if (session == null) {
RoboErik3c45c292014-07-08 16:47:31 -0700776 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0) {
777 if (DEBUG) {
778 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -0700779 }
RoboErikb7c014c2014-07-22 15:58:22 -0700780 return;
RoboErik3c45c292014-07-08 16:47:31 -0700781 }
RoboErik0791e172014-06-08 10:52:32 -0700782 try {
RoboErik1ff5b162014-07-15 17:23:18 -0700783 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
784 getContext().getOpPackageName());
RoboErik0791e172014-06-08 10:52:32 -0700785 } catch (RemoteException e) {
786 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700787 }
788 } else {
RoboErik1ff5b162014-07-15 17:23:18 -0700789 session.adjustVolume(direction, flags);
RoboErik851d2d52014-06-27 18:02:40 -0700790 if (session.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE
791 && mRvc != null) {
RoboErik19c95182014-06-23 15:38:48 -0700792 try {
793 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
794 } catch (Exception e) {
795 Log.wtf(TAG, "Error sending volume change to system UI.", e);
796 }
797 }
RoboErikb69ffd42014-05-30 14:57:59 -0700798 }
799 }
800
RoboErik9a9d0b52014-05-20 14:53:39 -0700801 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
802 MediaSessionRecord session) {
803 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
804 // If the phone app has priority just give it the event
805 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
806 return;
807 }
808 int action = keyEvent.getAction();
809 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
810 if (action == KeyEvent.ACTION_DOWN) {
811 if (keyEvent.getRepeatCount() == 0) {
812 mVoiceButtonDown = true;
813 mVoiceButtonHandled = false;
814 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
815 mVoiceButtonHandled = true;
816 startVoiceInput(needWakeLock);
817 }
818 } else if (action == KeyEvent.ACTION_UP) {
819 if (mVoiceButtonDown) {
820 mVoiceButtonDown = false;
821 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
822 // Resend the down then send this event through
823 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
824 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
825 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
826 }
827 }
828 }
829 }
830
831 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
832 MediaSessionRecord session) {
833 if (session != null) {
834 if (DEBUG) {
835 Log.d(TAG, "Sending media key to " + session.getSessionInfo());
836 }
837 if (needWakeLock) {
838 mKeyEventReceiver.aquireWakeLockLocked();
839 }
840 // If we don't need a wakelock use -1 as the id so we
841 // won't release it later
842 session.sendMediaButton(keyEvent,
843 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
844 mKeyEventReceiver);
845 } else {
846 if (needWakeLock) {
847 mMediaEventWakeLock.acquire();
848 }
849 if (DEBUG) {
850 Log.d(TAG, "Sending media key ordered broadcast");
851 }
852 // Fallback to legacy behavior
853 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
854 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
855 if (needWakeLock) {
856 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
857 WAKELOCK_RELEASE_ON_FINISHED);
858 }
859 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
860 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
861 }
862 }
863
864 private void startVoiceInput(boolean needWakeLock) {
865 Intent voiceIntent = null;
866 // select which type of search to launch:
867 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
868 // - device locked or screen off: action is
869 // ACTION_VOICE_SEARCH_HANDS_FREE
870 // with EXTRA_SECURE set to true if the device is securely locked
871 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
872 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
873 if (!isLocked && pm.isScreenOn()) {
874 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
875 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
876 } else {
877 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
878 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
879 isLocked && mKeyguardManager.isKeyguardSecure());
880 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
881 }
882 // start the search activity
883 if (needWakeLock) {
884 mMediaEventWakeLock.acquire();
885 }
886 try {
887 if (voiceIntent != null) {
888 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
889 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
890 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
891 }
892 } catch (ActivityNotFoundException e) {
893 Log.w(TAG, "No activity for search: " + e);
894 } finally {
895 if (needWakeLock) {
896 mMediaEventWakeLock.release();
897 }
898 }
899 }
900
901 private boolean isVoiceKey(int keyCode) {
902 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
903 }
904
RoboErik418c10c2014-05-19 09:25:25 -0700905 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
906
907 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
908 private final Handler mHandler;
909 private int mRefCount = 0;
910 private int mLastTimeoutId = 0;
911
912 public KeyEventWakeLockReceiver(Handler handler) {
913 super(handler);
914 mHandler = handler;
915 }
916
917 public void onTimeout() {
918 synchronized (mLock) {
919 if (mRefCount == 0) {
920 // We've already released it, so just return
921 return;
922 }
923 mLastTimeoutId++;
924 mRefCount = 0;
925 releaseWakeLockLocked();
926 }
927 }
928
929 public void aquireWakeLockLocked() {
930 if (mRefCount == 0) {
931 mMediaEventWakeLock.acquire();
932 }
933 mRefCount++;
934 mHandler.removeCallbacks(this);
935 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
936
937 }
938
939 @Override
940 public void run() {
941 onTimeout();
942 }
943
RoboErik8a2cfc32014-05-16 11:19:38 -0700944 @Override
945 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -0700946 if (resultCode < mLastTimeoutId) {
947 // Ignore results from calls that were before the last
948 // timeout, just in case.
949 return;
950 } else {
951 synchronized (mLock) {
952 if (mRefCount > 0) {
953 mRefCount--;
954 if (mRefCount == 0) {
955 releaseWakeLockLocked();
956 }
957 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700958 }
959 }
960 }
RoboErik418c10c2014-05-19 09:25:25 -0700961
962 private void releaseWakeLockLocked() {
963 mMediaEventWakeLock.release();
964 mHandler.removeCallbacks(this);
965 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700966 };
967
968 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
969 @Override
970 public void onReceive(Context context, Intent intent) {
971 if (intent == null) {
972 return;
973 }
974 Bundle extras = intent.getExtras();
975 if (extras == null) {
976 return;
977 }
978 synchronized (mLock) {
979 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
980 && mMediaEventWakeLock.isHeld()) {
981 mMediaEventWakeLock.release();
982 }
983 }
984 }
985 };
RoboErik01fe6612014-02-13 14:19:04 -0800986 }
987
RoboErik2e7a9162014-06-04 16:53:45 -0700988 final class MessageHandler extends Handler {
989 private static final int MSG_SESSIONS_CHANGED = 1;
990
991 @Override
992 public void handleMessage(Message msg) {
993 switch (msg.what) {
994 case MSG_SESSIONS_CHANGED:
995 pushSessionsChanged(msg.arg1);
996 break;
997 }
998 }
999
1000 public void post(int what, int arg1, int arg2) {
1001 obtainMessage(what, arg1, arg2).sendToTarget();
1002 }
1003 }
RoboErik01fe6612014-02-13 14:19:04 -08001004}