blob: ce2899d3f24eb6bd5448d53d8df1f15cbbfccc92 [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;
RoboErik3c45c292014-07-08 16:47:31 -070032import android.media.AudioManager;
RoboErikb69ffd42014-05-30 14:57:59 -070033import android.media.IAudioService;
RoboErik19c95182014-06-23 15:38:48 -070034import android.media.IRemoteVolumeController;
RoboErik2e7a9162014-06-04 16:53:45 -070035import android.media.session.IActiveSessionsListener;
RoboErik07c70772014-03-20 13:33:52 -070036import android.media.session.ISession;
37import android.media.session.ISessionCallback;
38import android.media.session.ISessionManager;
Jeff Browndba34ba2014-06-24 20:46:03 -070039import android.media.session.MediaSession;
RoboErik01fe6612014-02-13 14:19:04 -080040import android.os.Binder;
RoboErik8a2cfc32014-05-16 11:19:38 -070041import android.os.Bundle;
RoboErik8ae0f342014-02-24 18:02:08 -080042import android.os.Handler;
RoboErike7880d82014-04-30 12:48:25 -070043import android.os.IBinder;
RoboErik2e7a9162014-06-04 16:53:45 -070044import android.os.Message;
RoboErik8a2cfc32014-05-16 11:19:38 -070045import android.os.PowerManager;
RoboErik01fe6612014-02-13 14:19:04 -080046import android.os.RemoteException;
RoboErik8a2cfc32014-05-16 11:19:38 -070047import android.os.ResultReceiver;
RoboErikb69ffd42014-05-30 14:57:59 -070048import android.os.ServiceManager;
RoboErike7880d82014-04-30 12:48:25 -070049import android.os.UserHandle;
50import android.provider.Settings;
RoboErik9a9d0b52014-05-20 14:53:39 -070051import android.speech.RecognizerIntent;
RoboErik01fe6612014-02-13 14:19:04 -080052import android.text.TextUtils;
53import android.util.Log;
RoboErik4646d282014-05-13 10:13:04 -070054import android.util.SparseArray;
RoboErik8a2cfc32014-05-16 11:19:38 -070055import android.view.KeyEvent;
RoboErik01fe6612014-02-13 14:19:04 -080056
57import com.android.server.SystemService;
RoboErika278ea72014-04-24 14:49:01 -070058import com.android.server.Watchdog;
59import com.android.server.Watchdog.Monitor;
RoboErik01fe6612014-02-13 14:19:04 -080060
RoboErika278ea72014-04-24 14:49:01 -070061import java.io.FileDescriptor;
62import java.io.PrintWriter;
RoboErik01fe6612014-02-13 14:19:04 -080063import java.util.ArrayList;
RoboErike7880d82014-04-30 12:48:25 -070064import java.util.List;
RoboErik01fe6612014-02-13 14:19:04 -080065
66/**
67 * System implementation of MediaSessionManager
68 */
RoboErika278ea72014-04-24 14:49:01 -070069public class MediaSessionService extends SystemService implements Monitor {
RoboErik01fe6612014-02-13 14:19:04 -080070 private static final String TAG = "MediaSessionService";
71 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
72
RoboErik418c10c2014-05-19 09:25:25 -070073 private static final int WAKELOCK_TIMEOUT = 5000;
74
RoboErik01fe6612014-02-13 14:19:04 -080075 private final SessionManagerImpl mSessionManagerImpl;
RoboErika8f95142014-05-05 14:23:49 -070076 private final MediaSessionStack mPriorityStack;
RoboErik01fe6612014-02-13 14:19:04 -080077
RoboErik4646d282014-05-13 10:13:04 -070078 private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
79 private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
RoboErik2e7a9162014-06-04 16:53:45 -070080 private final ArrayList<SessionsListenerRecord> mSessionsListeners
81 = new ArrayList<SessionsListenerRecord>();
RoboErik01fe6612014-02-13 14:19:04 -080082 private final Object mLock = new Object();
RoboErik2e7a9162014-06-04 16:53:45 -070083 private final MessageHandler mHandler = new MessageHandler();
RoboErik8a2cfc32014-05-16 11:19:38 -070084 private final PowerManager.WakeLock mMediaEventWakeLock;
RoboErik01fe6612014-02-13 14:19:04 -080085
RoboErik9a9d0b52014-05-20 14:53:39 -070086 private KeyguardManager mKeyguardManager;
RoboErikb69ffd42014-05-30 14:57:59 -070087 private IAudioService mAudioService;
RoboErik6f0e4dd2014-06-17 16:56:27 -070088 private ContentResolver mContentResolver;
RoboErik9a9d0b52014-05-20 14:53:39 -070089
RoboErike7880d82014-04-30 12:48:25 -070090 private MediaSessionRecord mPrioritySession;
RoboErik4646d282014-05-13 10:13:04 -070091 private int mCurrentUserId = -1;
RoboErike7880d82014-04-30 12:48:25 -070092
RoboErik19c95182014-06-23 15:38:48 -070093 // Used to notify system UI when remote volume was changed. TODO find a
94 // better way to handle this.
95 private IRemoteVolumeController mRvc;
96
RoboErik01fe6612014-02-13 14:19:04 -080097 public MediaSessionService(Context context) {
98 super(context);
99 mSessionManagerImpl = new SessionManagerImpl();
RoboErika8f95142014-05-05 14:23:49 -0700100 mPriorityStack = new MediaSessionStack();
RoboErik8a2cfc32014-05-16 11:19:38 -0700101 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
102 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
RoboErik01fe6612014-02-13 14:19:04 -0800103 }
104
105 @Override
106 public void onStart() {
107 publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
RoboErika278ea72014-04-24 14:49:01 -0700108 Watchdog.getInstance().addMonitor(this);
RoboErik4646d282014-05-13 10:13:04 -0700109 updateUser();
RoboErik9a9d0b52014-05-20 14:53:39 -0700110 mKeyguardManager =
111 (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
RoboErikb69ffd42014-05-30 14:57:59 -0700112 mAudioService = getAudioService();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700113 mContentResolver = getContext().getContentResolver();
RoboErikb69ffd42014-05-30 14:57:59 -0700114 }
115
116 private IAudioService getAudioService() {
117 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
118 return IAudioService.Stub.asInterface(b);
RoboErik07c70772014-03-20 13:33:52 -0700119 }
120
RoboErika8f95142014-05-05 14:23:49 -0700121 public void updateSession(MediaSessionRecord record) {
RoboErike7880d82014-04-30 12:48:25 -0700122 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700123 if (!mAllSessions.contains(record)) {
124 Log.d(TAG, "Unknown session updated. Ignoring.");
125 return;
126 }
RoboErika8f95142014-05-05 14:23:49 -0700127 mPriorityStack.onSessionStateChange(record);
RoboErike7880d82014-04-30 12:48:25 -0700128 if (record.isSystemPriority()) {
RoboErika8f95142014-05-05 14:23:49 -0700129 if (record.isActive()) {
130 if (mPrioritySession != null) {
131 Log.w(TAG, "Replacing existing priority session with a new session");
132 }
133 mPrioritySession = record;
134 } else {
135 if (mPrioritySession == record) {
136 mPrioritySession = null;
137 }
RoboErike7880d82014-04-30 12:48:25 -0700138 }
RoboErike7880d82014-04-30 12:48:25 -0700139 }
140 }
RoboErik2e7a9162014-06-04 16:53:45 -0700141 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErike7880d82014-04-30 12:48:25 -0700142 }
143
RoboErika8f95142014-05-05 14:23:49 -0700144 public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErik2e7a9162014-06-04 16:53:45 -0700145 boolean updateSessions = false;
RoboErika8f95142014-05-05 14:23:49 -0700146 synchronized (mLock) {
RoboErik4646d282014-05-13 10:13:04 -0700147 if (!mAllSessions.contains(record)) {
148 Log.d(TAG, "Unknown session changed playback state. Ignoring.");
149 return;
150 }
RoboErik2e7a9162014-06-04 16:53:45 -0700151 updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
152 }
153 if (updateSessions) {
154 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
RoboErika8f95142014-05-05 14:23:49 -0700155 }
156 }
157
RoboErik19c95182014-06-23 15:38:48 -0700158 public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
159 synchronized (mLock) {
160 if (!mAllSessions.contains(record)) {
161 Log.d(TAG, "Unknown session changed playback type. Ignoring.");
162 return;
163 }
164 pushRemoteVolumeUpdateLocked(record.getUserId());
165 }
166 }
167
RoboErika278ea72014-04-24 14:49:01 -0700168 @Override
RoboErik4646d282014-05-13 10:13:04 -0700169 public void onStartUser(int userHandle) {
170 updateUser();
171 }
172
173 @Override
174 public void onSwitchUser(int userHandle) {
175 updateUser();
176 }
177
178 @Override
179 public void onStopUser(int userHandle) {
180 synchronized (mLock) {
181 UserRecord user = mUserRecords.get(userHandle);
182 if (user != null) {
183 destroyUserLocked(user);
184 }
185 }
186 }
187
188 @Override
RoboErika278ea72014-04-24 14:49:01 -0700189 public void monitor() {
190 synchronized (mLock) {
191 // Check for deadlock
192 }
193 }
194
RoboErik4646d282014-05-13 10:13:04 -0700195 protected void enforcePhoneStatePermission(int pid, int uid) {
196 if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
197 != PackageManager.PERMISSION_GRANTED) {
198 throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
199 }
200 }
201
RoboErik01fe6612014-02-13 14:19:04 -0800202 void sessionDied(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700203 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800204 destroySessionLocked(session);
205 }
206 }
207
208 void destroySession(MediaSessionRecord session) {
RoboErika278ea72014-04-24 14:49:01 -0700209 synchronized (mLock) {
RoboErik01fe6612014-02-13 14:19:04 -0800210 destroySessionLocked(session);
211 }
212 }
213
RoboErik4646d282014-05-13 10:13:04 -0700214 private void updateUser() {
215 synchronized (mLock) {
216 int userId = ActivityManager.getCurrentUser();
217 if (mCurrentUserId != userId) {
218 final int oldUserId = mCurrentUserId;
219 mCurrentUserId = userId; // do this first
220
221 UserRecord oldUser = mUserRecords.get(oldUserId);
222 if (oldUser != null) {
223 oldUser.stopLocked();
224 }
225
226 UserRecord newUser = getOrCreateUser(userId);
227 newUser.startLocked();
228 }
229 }
230 }
231
232 /**
233 * Stop the user and unbind from everything.
234 *
235 * @param user The user to dispose of
236 */
237 private void destroyUserLocked(UserRecord user) {
238 user.stopLocked();
239 user.destroyLocked();
240 mUserRecords.remove(user.mUserId);
241 }
242
243 /*
244 * When a session is removed several things need to happen.
245 * 1. We need to remove it from the relevant user.
246 * 2. We need to remove it from the priority stack.
247 * 3. We need to remove it from all sessions.
248 * 4. If this is the system priority session we need to clear it.
249 * 5. We need to unlink to death from the cb binder
250 * 6. We need to tell the session to do any final cleanup (onDestroy)
251 */
RoboErik01fe6612014-02-13 14:19:04 -0800252 private void destroySessionLocked(MediaSessionRecord session) {
RoboErik4646d282014-05-13 10:13:04 -0700253 int userId = session.getUserId();
254 UserRecord user = mUserRecords.get(userId);
255 if (user != null) {
256 user.removeSessionLocked(session);
257 }
258
RoboErika8f95142014-05-05 14:23:49 -0700259 mPriorityStack.removeSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700260 mAllSessions.remove(session);
RoboErike7880d82014-04-30 12:48:25 -0700261 if (session == mPrioritySession) {
262 mPrioritySession = null;
263 }
RoboErik4646d282014-05-13 10:13:04 -0700264
265 try {
266 session.getCallback().asBinder().unlinkToDeath(session, 0);
267 } catch (Exception e) {
268 // ignore exceptions while destroying a session.
269 }
270 session.onDestroy();
RoboErik2e7a9162014-06-04 16:53:45 -0700271
272 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
RoboErik01fe6612014-02-13 14:19:04 -0800273 }
274
275 private void enforcePackageName(String packageName, int uid) {
276 if (TextUtils.isEmpty(packageName)) {
277 throw new IllegalArgumentException("packageName may not be empty");
278 }
279 String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
280 final int packageCount = packages.length;
281 for (int i = 0; i < packageCount; i++) {
282 if (packageName.equals(packages[i])) {
283 return;
284 }
285 }
286 throw new IllegalArgumentException("packageName is not owned by the calling process");
287 }
288
RoboErike7880d82014-04-30 12:48:25 -0700289 /**
290 * Checks a caller's authorization to register an IRemoteControlDisplay.
291 * Authorization is granted if one of the following is true:
292 * <ul>
293 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
294 * permission</li>
RoboErika5b02322014-05-07 17:05:49 -0700295 * <li>the caller's listener is one of the enabled notification listeners
296 * for the caller's user</li>
RoboErike7880d82014-04-30 12:48:25 -0700297 * </ul>
298 */
RoboErika5b02322014-05-07 17:05:49 -0700299 private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
300 int resolvedUserId) {
RoboErike7880d82014-04-30 12:48:25 -0700301 if (getContext()
302 .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
303 != PackageManager.PERMISSION_GRANTED
RoboErika5b02322014-05-07 17:05:49 -0700304 && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
305 resolvedUserId)) {
RoboErike7880d82014-04-30 12:48:25 -0700306 throw new SecurityException("Missing permission to control media.");
307 }
308 }
309
RoboErik19c95182014-06-23 15:38:48 -0700310 private void enforceStatusBarPermission(String action, int pid, int uid) {
311 if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
312 pid, uid) != PackageManager.PERMISSION_GRANTED) {
313 throw new SecurityException("Only system ui may " + action);
314 }
315 }
316
RoboErika5b02322014-05-07 17:05:49 -0700317 /**
318 * This checks if the component is an enabled notification listener for the
319 * specified user. Enabled components may only operate on behalf of the user
320 * they're running as.
321 *
322 * @param compName The component that is enabled.
323 * @param userId The user id of the caller.
324 * @param forUserId The user id they're making the request on behalf of.
325 * @return True if the component is enabled, false otherwise
326 */
327 private boolean isEnabledNotificationListener(ComponentName compName, int userId,
328 int forUserId) {
329 if (userId != forUserId) {
330 // You may not access another user's content as an enabled listener.
331 return false;
332 }
RoboErik51fa6bc2014-06-20 14:59:58 -0700333 if (DEBUG) {
334 Log.d(TAG, "Checking if enabled notification listener " + compName);
335 }
RoboErike7880d82014-04-30 12:48:25 -0700336 if (compName != null) {
RoboErik6f0e4dd2014-06-17 16:56:27 -0700337 final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
RoboErike7880d82014-04-30 12:48:25 -0700338 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
RoboErika5b02322014-05-07 17:05:49 -0700339 userId);
RoboErike7880d82014-04-30 12:48:25 -0700340 if (enabledNotifListeners != null) {
341 final String[] components = enabledNotifListeners.split(":");
342 for (int i = 0; i < components.length; i++) {
343 final ComponentName component =
344 ComponentName.unflattenFromString(components[i]);
345 if (component != null) {
346 if (compName.equals(component)) {
347 if (DEBUG) {
348 Log.d(TAG, "ok to get sessions: " + component +
349 " is authorized notification listener");
350 }
351 return true;
352 }
353 }
354 }
355 }
356 if (DEBUG) {
357 Log.d(TAG, "not ok to get sessions, " + compName +
RoboErika5b02322014-05-07 17:05:49 -0700358 " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
RoboErike7880d82014-04-30 12:48:25 -0700359 }
360 }
361 return false;
362 }
363
RoboErika5b02322014-05-07 17:05:49 -0700364 private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
RoboErik4646d282014-05-13 10:13:04 -0700365 String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800366 synchronized (mLock) {
RoboErika5b02322014-05-07 17:05:49 -0700367 return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
RoboErik01fe6612014-02-13 14:19:04 -0800368 }
369 }
370
RoboErik4646d282014-05-13 10:13:04 -0700371 /*
372 * When a session is created the following things need to happen.
RoboErik8a2cfc32014-05-16 11:19:38 -0700373 * 1. Its callback binder needs a link to death
RoboErik4646d282014-05-13 10:13:04 -0700374 * 2. It needs to be added to all sessions.
375 * 3. It needs to be added to the priority stack.
376 * 4. It needs to be added to the relevant user record.
377 */
RoboErika5b02322014-05-07 17:05:49 -0700378 private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
379 String callerPackageName, ISessionCallback cb, String tag) {
RoboErik4646d282014-05-13 10:13:04 -0700380
RoboErika5b02322014-05-07 17:05:49 -0700381 final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
382 callerPackageName, cb, tag, this, mHandler);
RoboErik01fe6612014-02-13 14:19:04 -0800383 try {
384 cb.asBinder().linkToDeath(session, 0);
385 } catch (RemoteException e) {
386 throw new RuntimeException("Media Session owner died prematurely.", e);
387 }
RoboErik4646d282014-05-13 10:13:04 -0700388
389 mAllSessions.add(session);
RoboErika8f95142014-05-05 14:23:49 -0700390 mPriorityStack.addSession(session);
RoboErik4646d282014-05-13 10:13:04 -0700391
392 UserRecord user = getOrCreateUser(userId);
393 user.addSessionLocked(session);
394
RoboErik2e7a9162014-06-04 16:53:45 -0700395 mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
396
RoboErik01fe6612014-02-13 14:19:04 -0800397 if (DEBUG) {
RoboErika5b02322014-05-07 17:05:49 -0700398 Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
RoboErik01fe6612014-02-13 14:19:04 -0800399 }
400 return session;
401 }
402
RoboErik4646d282014-05-13 10:13:04 -0700403 private UserRecord getOrCreateUser(int userId) {
404 UserRecord user = mUserRecords.get(userId);
405 if (user == null) {
406 user = new UserRecord(getContext(), userId);
407 mUserRecords.put(userId, user);
408 }
409 return user;
410 }
411
RoboErik2e7a9162014-06-04 16:53:45 -0700412 private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
413 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
414 if (mSessionsListeners.get(i).mListener == listener) {
415 return i;
416 }
417 }
418 return -1;
419 }
420
RoboErike7880d82014-04-30 12:48:25 -0700421 private boolean isSessionDiscoverable(MediaSessionRecord record) {
RoboErik4646d282014-05-13 10:13:04 -0700422 // TODO probably want to check more than if it's active.
RoboErika8f95142014-05-05 14:23:49 -0700423 return record.isActive();
RoboErike7880d82014-04-30 12:48:25 -0700424 }
425
RoboErik2e7a9162014-06-04 16:53:45 -0700426 private void pushSessionsChanged(int userId) {
427 synchronized (mLock) {
428 List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
429 int size = records.size();
RoboErik6f0e4dd2014-06-17 16:56:27 -0700430 if (size > 0) {
RoboErikb214efb2014-07-24 13:20:30 -0700431 rememberMediaButtonReceiverLocked(records.get(0));
RoboErik6f0e4dd2014-06-17 16:56:27 -0700432 }
Jeff Browndba34ba2014-06-24 20:46:03 -0700433 ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
RoboErik2e7a9162014-06-04 16:53:45 -0700434 for (int i = 0; i < size; i++) {
Jeff Browndba34ba2014-06-24 20:46:03 -0700435 tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
RoboErik2e7a9162014-06-04 16:53:45 -0700436 }
RoboErik19c95182014-06-23 15:38:48 -0700437 pushRemoteVolumeUpdateLocked(userId);
RoboErik2e7a9162014-06-04 16:53:45 -0700438 for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
439 SessionsListenerRecord record = mSessionsListeners.get(i);
440 if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
441 try {
442 record.mListener.onActiveSessionsChanged(tokens);
443 } catch (RemoteException e) {
444 Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
445 e);
446 mSessionsListeners.remove(i);
447 }
448 }
449 }
450 }
451 }
452
RoboErik19c95182014-06-23 15:38:48 -0700453 private void pushRemoteVolumeUpdateLocked(int userId) {
454 if (mRvc != null) {
455 try {
456 MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
457 mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
458 } catch (RemoteException e) {
459 Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
460 }
461 }
462 }
463
RoboErikb214efb2014-07-24 13:20:30 -0700464 private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
465 PendingIntent receiver = record.getMediaButtonReceiver();
466 UserRecord user = mUserRecords.get(record.getUserId());
467 if (receiver != null && user != null) {
468 user.mLastMediaButtonReceiver = receiver;
RoboErik6f0e4dd2014-06-17 16:56:27 -0700469 }
470 }
471
RoboErik4646d282014-05-13 10:13:04 -0700472 /**
473 * Information about a particular user. The contents of this object is
474 * guarded by mLock.
475 */
476 final class UserRecord {
477 private final int mUserId;
RoboErik4646d282014-05-13 10:13:04 -0700478 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
RoboErikb214efb2014-07-24 13:20:30 -0700479 private PendingIntent mLastMediaButtonReceiver;
RoboErik4646d282014-05-13 10:13:04 -0700480
481 public UserRecord(Context context, int userId) {
482 mUserId = userId;
RoboErik4646d282014-05-13 10:13:04 -0700483 }
484
485 public void startLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700486 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700487 }
488
489 public void stopLocked() {
Jeff Brown01a500e2014-07-10 22:50:50 -0700490 // nothing for now
RoboErik4646d282014-05-13 10:13:04 -0700491 }
492
493 public void destroyLocked() {
494 for (int i = mSessions.size() - 1; i >= 0; i--) {
495 MediaSessionRecord session = mSessions.get(i);
496 MediaSessionService.this.destroySessionLocked(session);
RoboErik4646d282014-05-13 10:13:04 -0700497 }
498 }
499
RoboErik4646d282014-05-13 10:13:04 -0700500 public ArrayList<MediaSessionRecord> getSessionsLocked() {
501 return mSessions;
502 }
503
504 public void addSessionLocked(MediaSessionRecord session) {
505 mSessions.add(session);
RoboErik4646d282014-05-13 10:13:04 -0700506 }
507
508 public void removeSessionLocked(MediaSessionRecord session) {
509 mSessions.remove(session);
RoboErik4646d282014-05-13 10:13:04 -0700510 }
511
512 public void dumpLocked(PrintWriter pw, String prefix) {
513 pw.println(prefix + "Record for user " + mUserId);
514 String indent = prefix + " ";
RoboErikb214efb2014-07-24 13:20:30 -0700515 pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
Jeff Brown01a500e2014-07-10 22:50:50 -0700516 int size = mSessions.size();
RoboErik4646d282014-05-13 10:13:04 -0700517 pw.println(indent + size + " Sessions:");
518 for (int i = 0; i < size; i++) {
519 // Just print the session info, the full session dump will
520 // already be in the list of all sessions.
521 pw.println(indent + mSessions.get(i).getSessionInfo());
522 }
523 }
RoboErik4646d282014-05-13 10:13:04 -0700524 }
525
RoboErik2e7a9162014-06-04 16:53:45 -0700526 final class SessionsListenerRecord implements IBinder.DeathRecipient {
527 private final IActiveSessionsListener mListener;
528 private final int mUserId;
529
530 public SessionsListenerRecord(IActiveSessionsListener listener, int userId) {
531 mListener = listener;
532 mUserId = userId;
533 }
534
535 @Override
536 public void binderDied() {
537 synchronized (mLock) {
538 mSessionsListeners.remove(this);
539 }
540 }
541 }
542
RoboErik07c70772014-03-20 13:33:52 -0700543 class SessionManagerImpl extends ISessionManager.Stub {
RoboErik8a2cfc32014-05-16 11:19:38 -0700544 private static final String EXTRA_WAKELOCK_ACQUIRED =
545 "android.media.AudioService.WAKELOCK_ACQUIRED";
546 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
547
RoboErik9a9d0b52014-05-20 14:53:39 -0700548 private boolean mVoiceButtonDown = false;
549 private boolean mVoiceButtonHandled = false;
550
RoboErik07c70772014-03-20 13:33:52 -0700551 @Override
RoboErika5b02322014-05-07 17:05:49 -0700552 public ISession createSession(String packageName, ISessionCallback cb, String tag,
553 int userId) throws RemoteException {
RoboErik01fe6612014-02-13 14:19:04 -0800554 final int pid = Binder.getCallingPid();
555 final int uid = Binder.getCallingUid();
556 final long token = Binder.clearCallingIdentity();
557 try {
558 enforcePackageName(packageName, uid);
RoboErika5b02322014-05-07 17:05:49 -0700559 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
560 false /* allowAll */, true /* requireFull */, "createSession", packageName);
RoboErik01fe6612014-02-13 14:19:04 -0800561 if (cb == null) {
562 throw new IllegalArgumentException("Controller callback cannot be null");
563 }
RoboErika5b02322014-05-07 17:05:49 -0700564 return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
565 .getSessionBinder();
RoboErike7880d82014-04-30 12:48:25 -0700566 } finally {
567 Binder.restoreCallingIdentity(token);
568 }
569 }
570
571 @Override
RoboErika5b02322014-05-07 17:05:49 -0700572 public List<IBinder> getSessions(ComponentName componentName, int userId) {
RoboErike7880d82014-04-30 12:48:25 -0700573 final int pid = Binder.getCallingPid();
574 final int uid = Binder.getCallingUid();
575 final long token = Binder.clearCallingIdentity();
576
577 try {
RoboErik2e7a9162014-06-04 16:53:45 -0700578 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
RoboErike7880d82014-04-30 12:48:25 -0700579 ArrayList<IBinder> binders = new ArrayList<IBinder>();
580 synchronized (mLock) {
RoboErika8f95142014-05-05 14:23:49 -0700581 ArrayList<MediaSessionRecord> records = mPriorityStack
RoboErika5b02322014-05-07 17:05:49 -0700582 .getActiveSessions(resolvedUserId);
RoboErika8f95142014-05-05 14:23:49 -0700583 int size = records.size();
584 for (int i = 0; i < size; i++) {
585 binders.add(records.get(i).getControllerBinder().asBinder());
RoboErike7880d82014-04-30 12:48:25 -0700586 }
587 }
588 return binders;
RoboErik01fe6612014-02-13 14:19:04 -0800589 } finally {
590 Binder.restoreCallingIdentity(token);
591 }
592 }
RoboErika278ea72014-04-24 14:49:01 -0700593
RoboErik2e7a9162014-06-04 16:53:45 -0700594 @Override
595 public void addSessionsListener(IActiveSessionsListener listener,
596 ComponentName componentName, int userId) throws RemoteException {
597 final int pid = Binder.getCallingPid();
598 final int uid = Binder.getCallingUid();
599 final long token = Binder.clearCallingIdentity();
600
601 try {
602 int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
603 synchronized (mLock) {
604 int index = findIndexOfSessionsListenerLocked(listener);
605 if (index != -1) {
606 Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
607 return;
608 }
609 SessionsListenerRecord record = new SessionsListenerRecord(listener,
610 resolvedUserId);
611 try {
612 listener.asBinder().linkToDeath(record, 0);
613 } catch (RemoteException e) {
614 Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
615 return;
616 }
617 mSessionsListeners.add(record);
618 }
619 } finally {
620 Binder.restoreCallingIdentity(token);
621 }
622 }
623
624 @Override
625 public void removeSessionsListener(IActiveSessionsListener listener)
626 throws RemoteException {
627 synchronized (mLock) {
628 int index = findIndexOfSessionsListenerLocked(listener);
629 if (index != -1) {
630 SessionsListenerRecord record = mSessionsListeners.remove(index);
631 try {
632 record.mListener.asBinder().unlinkToDeath(record, 0);
633 } catch (Exception e) {
634 // ignore exceptions, the record is being removed
635 }
636 }
637 }
638 }
639
RoboErik8a2cfc32014-05-16 11:19:38 -0700640 /**
641 * Handles the dispatching of the media button events to one of the
642 * registered listeners, or if there was none, broadcast an
643 * ACTION_MEDIA_BUTTON intent to the rest of the system.
644 *
645 * @param keyEvent a non-null KeyEvent whose key code is one of the
646 * supported media buttons
647 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
648 * while this key event is dispatched.
649 */
650 @Override
651 public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
652 if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
653 Log.w(TAG, "Attempted to dispatch null or non-media key event.");
654 return;
655 }
656 final int pid = Binder.getCallingPid();
657 final int uid = Binder.getCallingUid();
658 final long token = Binder.clearCallingIdentity();
659
660 try {
RoboErik8a2cfc32014-05-16 11:19:38 -0700661 synchronized (mLock) {
RoboErik9a9d0b52014-05-20 14:53:39 -0700662 MediaSessionRecord session = mPriorityStack
RoboErik8a2cfc32014-05-16 11:19:38 -0700663 .getDefaultMediaButtonSession(mCurrentUserId);
RoboErik9a9d0b52014-05-20 14:53:39 -0700664 if (isVoiceKey(keyEvent.getKeyCode())) {
665 handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700666 } else {
RoboErik9a9d0b52014-05-20 14:53:39 -0700667 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
RoboErik8a2cfc32014-05-16 11:19:38 -0700668 }
669 }
670 } finally {
671 Binder.restoreCallingIdentity(token);
672 }
673 }
674
RoboErika278ea72014-04-24 14:49:01 -0700675 @Override
RoboErik1ff5b162014-07-15 17:23:18 -0700676 public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
RoboErikb69ffd42014-05-30 14:57:59 -0700677 throws RemoteException {
678 final int pid = Binder.getCallingPid();
679 final int uid = Binder.getCallingUid();
680 final long token = Binder.clearCallingIdentity();
681 try {
682 synchronized (mLock) {
683 MediaSessionRecord session = mPriorityStack
684 .getDefaultVolumeSession(mCurrentUserId);
RoboErik1ff5b162014-07-15 17:23:18 -0700685 dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
RoboErikb69ffd42014-05-30 14:57:59 -0700686 }
687 } finally {
688 Binder.restoreCallingIdentity(token);
689 }
690 }
691
692 @Override
RoboErik19c95182014-06-23 15:38:48 -0700693 public void setRemoteVolumeController(IRemoteVolumeController rvc) {
694 final int pid = Binder.getCallingPid();
695 final int uid = Binder.getCallingUid();
696 final long token = Binder.clearCallingIdentity();
697 try {
698 enforceStatusBarPermission("listen for volume changes", pid, uid);
699 mRvc = rvc;
700 } finally {
701 Binder.restoreCallingIdentity(token);
702 }
703 }
704
705 @Override
RoboErika278ea72014-04-24 14:49:01 -0700706 public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
707 if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
708 != PackageManager.PERMISSION_GRANTED) {
709 pw.println("Permission Denial: can't dump MediaSessionService from from pid="
710 + Binder.getCallingPid()
711 + ", uid=" + Binder.getCallingUid());
712 return;
713 }
714
715 pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
716 pw.println();
717
718 synchronized (mLock) {
RoboErike7880d82014-04-30 12:48:25 -0700719 pw.println("Session for calls:" + mPrioritySession);
720 if (mPrioritySession != null) {
721 mPrioritySession.dump(pw, "");
722 }
RoboErik4646d282014-05-13 10:13:04 -0700723 int count = mAllSessions.size();
RoboErika8f95142014-05-05 14:23:49 -0700724 pw.println(count + " Sessions:");
RoboErika278ea72014-04-24 14:49:01 -0700725 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700726 mAllSessions.get(i).dump(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700727 pw.println();
RoboErika278ea72014-04-24 14:49:01 -0700728 }
RoboErika5b02322014-05-07 17:05:49 -0700729 mPriorityStack.dump(pw, "");
RoboErika8f95142014-05-05 14:23:49 -0700730
RoboErik4646d282014-05-13 10:13:04 -0700731 pw.println("User Records:");
732 count = mUserRecords.size();
RoboErika278ea72014-04-24 14:49:01 -0700733 for (int i = 0; i < count; i++) {
RoboErik4646d282014-05-13 10:13:04 -0700734 UserRecord user = mUserRecords.get(i);
735 user.dumpLocked(pw, "");
RoboErika278ea72014-04-24 14:49:01 -0700736 }
737 }
738 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700739
RoboErik2e7a9162014-06-04 16:53:45 -0700740 private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
741 final int uid) {
742 String packageName = null;
743 if (componentName != null) {
744 // If they gave us a component name verify they own the
745 // package
746 packageName = componentName.getPackageName();
747 enforcePackageName(packageName, uid);
748 }
749 // Check that they can make calls on behalf of the user and
750 // get the final user id
751 int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
752 true /* allowAll */, true /* requireFull */, "getSessions", packageName);
753 // Check if they have the permissions or their component is
754 // enabled for the user they're calling from.
755 enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
756 return resolvedUserId;
757 }
758
RoboErik1ff5b162014-07-15 17:23:18 -0700759 private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
RoboErikb69ffd42014-05-30 14:57:59 -0700760 MediaSessionRecord session) {
RoboErikb69ffd42014-05-30 14:57:59 -0700761 if (DEBUG) {
762 String sessionInfo = session == null ? null : session.getSessionInfo().toString();
RoboErik1ff5b162014-07-15 17:23:18 -0700763 Log.d(TAG, "Adjusting session " + sessionInfo + " by " + direction + ". flags=" + flags
RoboErikb69ffd42014-05-30 14:57:59 -0700764 + ", suggestedStream=" + suggestedStream);
765
766 }
767 if (session == null) {
RoboErik3c45c292014-07-08 16:47:31 -0700768 if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0) {
769 if (DEBUG) {
770 Log.d(TAG, "No active session to adjust, skipping media only volume event");
RoboErik3c45c292014-07-08 16:47:31 -0700771 }
RoboErikb7c014c2014-07-22 15:58:22 -0700772 return;
RoboErik3c45c292014-07-08 16:47:31 -0700773 }
RoboErik0791e172014-06-08 10:52:32 -0700774 try {
RoboErik1ff5b162014-07-15 17:23:18 -0700775 mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
776 getContext().getOpPackageName());
RoboErik0791e172014-06-08 10:52:32 -0700777 } catch (RemoteException e) {
778 Log.e(TAG, "Error adjusting default volume.", e);
RoboErikb69ffd42014-05-30 14:57:59 -0700779 }
780 } else {
RoboErik1ff5b162014-07-15 17:23:18 -0700781 session.adjustVolume(direction, flags);
RoboErik851d2d52014-06-27 18:02:40 -0700782 if (session.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE
783 && mRvc != null) {
RoboErik19c95182014-06-23 15:38:48 -0700784 try {
785 mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
786 } catch (Exception e) {
787 Log.wtf(TAG, "Error sending volume change to system UI.", e);
788 }
789 }
RoboErikb69ffd42014-05-30 14:57:59 -0700790 }
791 }
792
RoboErik9a9d0b52014-05-20 14:53:39 -0700793 private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
794 MediaSessionRecord session) {
795 if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
796 // If the phone app has priority just give it the event
797 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
798 return;
799 }
800 int action = keyEvent.getAction();
801 boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
802 if (action == KeyEvent.ACTION_DOWN) {
803 if (keyEvent.getRepeatCount() == 0) {
804 mVoiceButtonDown = true;
805 mVoiceButtonHandled = false;
806 } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
807 mVoiceButtonHandled = true;
808 startVoiceInput(needWakeLock);
809 }
810 } else if (action == KeyEvent.ACTION_UP) {
811 if (mVoiceButtonDown) {
812 mVoiceButtonDown = false;
813 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
814 // Resend the down then send this event through
815 KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
816 dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
817 dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
818 }
819 }
820 }
821 }
822
823 private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
824 MediaSessionRecord session) {
825 if (session != null) {
826 if (DEBUG) {
827 Log.d(TAG, "Sending media key to " + session.getSessionInfo());
828 }
829 if (needWakeLock) {
830 mKeyEventReceiver.aquireWakeLockLocked();
831 }
832 // If we don't need a wakelock use -1 as the id so we
833 // won't release it later
834 session.sendMediaButton(keyEvent,
835 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
836 mKeyEventReceiver);
837 } else {
RoboErikb214efb2014-07-24 13:20:30 -0700838 // Launch the last PendingIntent we had with priority
839 int userId = ActivityManager.getCurrentUser();
840 UserRecord user = mUserRecords.get(userId);
841 if (user.mLastMediaButtonReceiver != null) {
842 if (DEBUG) {
843 Log.d(TAG, "Sending media key to last known PendingIntent");
844 }
845 if (needWakeLock) {
846 mKeyEventReceiver.aquireWakeLockLocked();
847 }
848 Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
849 mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
850 try {
851 user.mLastMediaButtonReceiver.send(getContext(),
852 needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
853 mediaButtonIntent, mKeyEventReceiver, null);
854 } catch (CanceledException e) {
855 Log.i(TAG, "Error sending key event to media button receiver "
856 + user.mLastMediaButtonReceiver, e);
857 }
858 } else {
859 if (DEBUG) {
860 Log.d(TAG, "Sending media key ordered broadcast");
861 }
862 if (needWakeLock) {
863 mMediaEventWakeLock.acquire();
864 }
865 // Fallback to legacy behavior
866 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
867 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
868 if (needWakeLock) {
869 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
870 WAKELOCK_RELEASE_ON_FINISHED);
871 }
872 getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
873 null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
RoboErik9a9d0b52014-05-20 14:53:39 -0700874 }
RoboErik9a9d0b52014-05-20 14:53:39 -0700875 }
876 }
877
878 private void startVoiceInput(boolean needWakeLock) {
879 Intent voiceIntent = null;
880 // select which type of search to launch:
881 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
882 // - device locked or screen off: action is
883 // ACTION_VOICE_SEARCH_HANDS_FREE
884 // with EXTRA_SECURE set to true if the device is securely locked
885 PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
886 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
887 if (!isLocked && pm.isScreenOn()) {
888 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
889 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
890 } else {
891 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
892 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
893 isLocked && mKeyguardManager.isKeyguardSecure());
894 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
895 }
896 // start the search activity
897 if (needWakeLock) {
898 mMediaEventWakeLock.acquire();
899 }
900 try {
901 if (voiceIntent != null) {
902 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
903 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
904 getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
905 }
906 } catch (ActivityNotFoundException e) {
907 Log.w(TAG, "No activity for search: " + e);
908 } finally {
909 if (needWakeLock) {
910 mMediaEventWakeLock.release();
911 }
912 }
913 }
914
915 private boolean isVoiceKey(int keyCode) {
916 return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
917 }
918
RoboErik418c10c2014-05-19 09:25:25 -0700919 private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
920
RoboErikb214efb2014-07-24 13:20:30 -0700921 class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
922 PendingIntent.OnFinished {
RoboErik418c10c2014-05-19 09:25:25 -0700923 private final Handler mHandler;
924 private int mRefCount = 0;
925 private int mLastTimeoutId = 0;
926
927 public KeyEventWakeLockReceiver(Handler handler) {
928 super(handler);
929 mHandler = handler;
930 }
931
932 public void onTimeout() {
933 synchronized (mLock) {
934 if (mRefCount == 0) {
935 // We've already released it, so just return
936 return;
937 }
938 mLastTimeoutId++;
939 mRefCount = 0;
940 releaseWakeLockLocked();
941 }
942 }
943
944 public void aquireWakeLockLocked() {
945 if (mRefCount == 0) {
946 mMediaEventWakeLock.acquire();
947 }
948 mRefCount++;
949 mHandler.removeCallbacks(this);
950 mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
951
952 }
953
954 @Override
955 public void run() {
956 onTimeout();
957 }
958
RoboErik8a2cfc32014-05-16 11:19:38 -0700959 @Override
960 protected void onReceiveResult(int resultCode, Bundle resultData) {
RoboErik418c10c2014-05-19 09:25:25 -0700961 if (resultCode < mLastTimeoutId) {
962 // Ignore results from calls that were before the last
963 // timeout, just in case.
964 return;
965 } else {
966 synchronized (mLock) {
967 if (mRefCount > 0) {
968 mRefCount--;
969 if (mRefCount == 0) {
970 releaseWakeLockLocked();
971 }
972 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700973 }
974 }
975 }
RoboErik418c10c2014-05-19 09:25:25 -0700976
977 private void releaseWakeLockLocked() {
978 mMediaEventWakeLock.release();
979 mHandler.removeCallbacks(this);
980 }
RoboErikb214efb2014-07-24 13:20:30 -0700981
982 @Override
983 public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
984 String resultData, Bundle resultExtras) {
985 onReceiveResult(resultCode, null);
986 }
RoboErik8a2cfc32014-05-16 11:19:38 -0700987 };
988
989 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
990 @Override
991 public void onReceive(Context context, Intent intent) {
992 if (intent == null) {
993 return;
994 }
995 Bundle extras = intent.getExtras();
996 if (extras == null) {
997 return;
998 }
999 synchronized (mLock) {
1000 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1001 && mMediaEventWakeLock.isHeld()) {
1002 mMediaEventWakeLock.release();
1003 }
1004 }
1005 }
1006 };
RoboErik01fe6612014-02-13 14:19:04 -08001007 }
1008
RoboErik2e7a9162014-06-04 16:53:45 -07001009 final class MessageHandler extends Handler {
1010 private static final int MSG_SESSIONS_CHANGED = 1;
1011
1012 @Override
1013 public void handleMessage(Message msg) {
1014 switch (msg.what) {
1015 case MSG_SESSIONS_CHANGED:
1016 pushSessionsChanged(msg.arg1);
1017 break;
1018 }
1019 }
1020
1021 public void post(int what, int arg1, int arg2) {
1022 obtainMessage(what, arg1, arg2).sendToTarget();
1023 }
1024 }
RoboErik01fe6612014-02-13 14:19:04 -08001025}