MediaSessionService.java revision 7aef77bbf5b983b9f949936ed6cd174251697ca8
1/*
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
19import android.Manifest;
20import android.app.Activity;
21import android.app.ActivityManager;
22import android.app.KeyguardManager;
23import android.app.PendingIntent;
24import android.app.PendingIntent.CanceledException;
25import android.content.ActivityNotFoundException;
26import android.content.BroadcastReceiver;
27import android.content.ComponentName;
28import android.content.ContentResolver;
29import android.content.Context;
30import android.content.Intent;
31import android.content.pm.PackageManager;
32import android.database.ContentObserver;
33import android.media.AudioManager;
34import android.media.IAudioService;
35import android.media.IRemoteVolumeController;
36import android.media.session.IActiveSessionsListener;
37import android.media.session.ISession;
38import android.media.session.ISessionCallback;
39import android.media.session.ISessionManager;
40import android.media.session.MediaSession;
41import android.net.Uri;
42import android.os.Binder;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.IBinder;
46import android.os.Message;
47import android.os.PowerManager;
48import android.os.RemoteException;
49import android.os.ResultReceiver;
50import android.os.ServiceManager;
51import android.os.UserHandle;
52import android.provider.Settings;
53import android.speech.RecognizerIntent;
54import android.text.TextUtils;
55import android.util.Log;
56import android.util.SparseArray;
57import android.view.KeyEvent;
58
59import com.android.server.SystemService;
60import com.android.server.Watchdog;
61import com.android.server.Watchdog.Monitor;
62
63import java.io.FileDescriptor;
64import java.io.PrintWriter;
65import java.util.ArrayList;
66import java.util.List;
67
68/**
69 * System implementation of MediaSessionManager
70 */
71public class MediaSessionService extends SystemService implements Monitor {
72    private static final String TAG = "MediaSessionService";
73    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
74
75    private static final int WAKELOCK_TIMEOUT = 5000;
76
77    private final SessionManagerImpl mSessionManagerImpl;
78    private final MediaSessionStack mPriorityStack;
79
80    private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
81    private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
82    private final ArrayList<SessionsListenerRecord> mSessionsListeners
83            = new ArrayList<SessionsListenerRecord>();
84    private final Object mLock = new Object();
85    private final MessageHandler mHandler = new MessageHandler();
86    private final PowerManager.WakeLock mMediaEventWakeLock;
87
88    private KeyguardManager mKeyguardManager;
89    private IAudioService mAudioService;
90    private ContentResolver mContentResolver;
91    private SettingsObserver mSettingsObserver;
92
93    private MediaSessionRecord mPrioritySession;
94    private int mCurrentUserId = -1;
95
96    // Used to notify system UI when remote volume was changed. TODO find a
97    // better way to handle this.
98    private IRemoteVolumeController mRvc;
99
100    public MediaSessionService(Context context) {
101        super(context);
102        mSessionManagerImpl = new SessionManagerImpl();
103        mPriorityStack = new MediaSessionStack();
104        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
105        mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
106    }
107
108    @Override
109    public void onStart() {
110        publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
111        Watchdog.getInstance().addMonitor(this);
112        updateUser();
113        mKeyguardManager =
114                (KeyguardManager) getContext().getSystemService(Context.KEYGUARD_SERVICE);
115        mAudioService = getAudioService();
116        mContentResolver = getContext().getContentResolver();
117        mSettingsObserver = new SettingsObserver();
118        mSettingsObserver.observe();
119    }
120
121    private IAudioService getAudioService() {
122        IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
123        return IAudioService.Stub.asInterface(b);
124    }
125
126    public void updateSession(MediaSessionRecord record) {
127        synchronized (mLock) {
128            if (!mAllSessions.contains(record)) {
129                Log.d(TAG, "Unknown session updated. Ignoring.");
130                return;
131            }
132            mPriorityStack.onSessionStateChange(record);
133            if (record.isSystemPriority()) {
134                if (record.isActive()) {
135                    if (mPrioritySession != null) {
136                        Log.w(TAG, "Replacing existing priority session with a new session");
137                    }
138                    mPrioritySession = record;
139                } else {
140                    if (mPrioritySession == record) {
141                        mPrioritySession = null;
142                    }
143                }
144            }
145        }
146        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
147    }
148
149    public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
150        boolean updateSessions = false;
151        synchronized (mLock) {
152            if (!mAllSessions.contains(record)) {
153                Log.d(TAG, "Unknown session changed playback state. Ignoring.");
154                return;
155            }
156            updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState);
157        }
158        if (updateSessions) {
159            mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0);
160        }
161    }
162
163    public void onSessionPlaybackTypeChanged(MediaSessionRecord record) {
164        synchronized (mLock) {
165            if (!mAllSessions.contains(record)) {
166                Log.d(TAG, "Unknown session changed playback type. Ignoring.");
167                return;
168            }
169            pushRemoteVolumeUpdateLocked(record.getUserId());
170        }
171    }
172
173    @Override
174    public void onStartUser(int userHandle) {
175        updateUser();
176    }
177
178    @Override
179    public void onSwitchUser(int userHandle) {
180        updateUser();
181    }
182
183    @Override
184    public void onStopUser(int userHandle) {
185        synchronized (mLock) {
186            UserRecord user = mUserRecords.get(userHandle);
187            if (user != null) {
188                destroyUserLocked(user);
189            }
190        }
191    }
192
193    @Override
194    public void monitor() {
195        synchronized (mLock) {
196            // Check for deadlock
197        }
198    }
199
200    protected void enforcePhoneStatePermission(int pid, int uid) {
201        if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
202                != PackageManager.PERMISSION_GRANTED) {
203            throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
204        }
205    }
206
207    void sessionDied(MediaSessionRecord session) {
208        synchronized (mLock) {
209            destroySessionLocked(session);
210        }
211    }
212
213    void destroySession(MediaSessionRecord session) {
214        synchronized (mLock) {
215            destroySessionLocked(session);
216        }
217    }
218
219    private void updateUser() {
220        synchronized (mLock) {
221            int userId = ActivityManager.getCurrentUser();
222            if (mCurrentUserId != userId) {
223                final int oldUserId = mCurrentUserId;
224                mCurrentUserId = userId; // do this first
225
226                UserRecord oldUser = mUserRecords.get(oldUserId);
227                if (oldUser != null) {
228                    oldUser.stopLocked();
229                }
230
231                UserRecord newUser = getOrCreateUser(userId);
232                newUser.startLocked();
233            }
234        }
235    }
236
237    private void updateActiveSessionListeners() {
238        synchronized (mLock) {
239            for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
240                SessionsListenerRecord listener = mSessionsListeners.get(i);
241                try {
242                    enforceMediaPermissions(listener.mComponentName, listener.mPid, listener.mUid,
243                            listener.mUserId);
244                } catch (SecurityException e) {
245                    Log.i(TAG, "ActiveSessionsListener " + listener.mComponentName
246                            + " is no longer authorized. Disconnecting.");
247                    mSessionsListeners.remove(i);
248                    try {
249                        listener.mListener
250                                .onActiveSessionsChanged(new ArrayList<MediaSession.Token>());
251                    } catch (Exception e1) {
252                        // ignore
253                    }
254                }
255            }
256        }
257    }
258
259    /**
260     * Stop the user and unbind from everything.
261     *
262     * @param user The user to dispose of
263     */
264    private void destroyUserLocked(UserRecord user) {
265        user.stopLocked();
266        user.destroyLocked();
267        mUserRecords.remove(user.mUserId);
268    }
269
270    /*
271     * When a session is removed several things need to happen.
272     * 1. We need to remove it from the relevant user.
273     * 2. We need to remove it from the priority stack.
274     * 3. We need to remove it from all sessions.
275     * 4. If this is the system priority session we need to clear it.
276     * 5. We need to unlink to death from the cb binder
277     * 6. We need to tell the session to do any final cleanup (onDestroy)
278     */
279    private void destroySessionLocked(MediaSessionRecord session) {
280        int userId = session.getUserId();
281        UserRecord user = mUserRecords.get(userId);
282        if (user != null) {
283            user.removeSessionLocked(session);
284        }
285
286        mPriorityStack.removeSession(session);
287        mAllSessions.remove(session);
288        if (session == mPrioritySession) {
289            mPrioritySession = null;
290        }
291
292        try {
293            session.getCallback().asBinder().unlinkToDeath(session, 0);
294        } catch (Exception e) {
295            // ignore exceptions while destroying a session.
296        }
297        session.onDestroy();
298
299        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0);
300    }
301
302    private void enforcePackageName(String packageName, int uid) {
303        if (TextUtils.isEmpty(packageName)) {
304            throw new IllegalArgumentException("packageName may not be empty");
305        }
306        String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
307        final int packageCount = packages.length;
308        for (int i = 0; i < packageCount; i++) {
309            if (packageName.equals(packages[i])) {
310                return;
311            }
312        }
313        throw new IllegalArgumentException("packageName is not owned by the calling process");
314    }
315
316    /**
317     * Checks a caller's authorization to register an IRemoteControlDisplay.
318     * Authorization is granted if one of the following is true:
319     * <ul>
320     * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
321     * permission</li>
322     * <li>the caller's listener is one of the enabled notification listeners
323     * for the caller's user</li>
324     * </ul>
325     */
326    private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
327            int resolvedUserId) {
328        if (getContext()
329                .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
330                    != PackageManager.PERMISSION_GRANTED
331                && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
332                        resolvedUserId)) {
333            throw new SecurityException("Missing permission to control media.");
334        }
335    }
336
337    private void enforceStatusBarPermission(String action, int pid, int uid) {
338        if (getContext().checkPermission(android.Manifest.permission.STATUS_BAR_SERVICE,
339                pid, uid) != PackageManager.PERMISSION_GRANTED) {
340            throw new SecurityException("Only system ui may " + action);
341        }
342    }
343
344    /**
345     * This checks if the component is an enabled notification listener for the
346     * specified user. Enabled components may only operate on behalf of the user
347     * they're running as.
348     *
349     * @param compName The component that is enabled.
350     * @param userId The user id of the caller.
351     * @param forUserId The user id they're making the request on behalf of.
352     * @return True if the component is enabled, false otherwise
353     */
354    private boolean isEnabledNotificationListener(ComponentName compName, int userId,
355            int forUserId) {
356        if (userId != forUserId) {
357            // You may not access another user's content as an enabled listener.
358            return false;
359        }
360        if (DEBUG) {
361            Log.d(TAG, "Checking if enabled notification listener " + compName);
362        }
363        if (compName != null) {
364            final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
365                    Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
366                    userId);
367            if (enabledNotifListeners != null) {
368                final String[] components = enabledNotifListeners.split(":");
369                for (int i = 0; i < components.length; i++) {
370                    final ComponentName component =
371                            ComponentName.unflattenFromString(components[i]);
372                    if (component != null) {
373                        if (compName.equals(component)) {
374                            if (DEBUG) {
375                                Log.d(TAG, "ok to get sessions: " + component +
376                                        " is authorized notification listener");
377                            }
378                            return true;
379                        }
380                    }
381                }
382            }
383            if (DEBUG) {
384                Log.d(TAG, "not ok to get sessions, " + compName +
385                        " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
386            }
387        }
388        return false;
389    }
390
391    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
392            String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
393        synchronized (mLock) {
394            return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
395        }
396    }
397
398    /*
399     * When a session is created the following things need to happen.
400     * 1. Its callback binder needs a link to death
401     * 2. It needs to be added to all sessions.
402     * 3. It needs to be added to the priority stack.
403     * 4. It needs to be added to the relevant user record.
404     */
405    private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
406            String callerPackageName, ISessionCallback cb, String tag) {
407
408        final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
409                callerPackageName, cb, tag, this, mHandler);
410        try {
411            cb.asBinder().linkToDeath(session, 0);
412        } catch (RemoteException e) {
413            throw new RuntimeException("Media Session owner died prematurely.", e);
414        }
415
416        mAllSessions.add(session);
417        mPriorityStack.addSession(session);
418
419        UserRecord user = getOrCreateUser(userId);
420        user.addSessionLocked(session);
421
422        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
423
424        if (DEBUG) {
425            Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
426        }
427        return session;
428    }
429
430    private UserRecord getOrCreateUser(int userId) {
431        UserRecord user = mUserRecords.get(userId);
432        if (user == null) {
433            user = new UserRecord(getContext(), userId);
434            mUserRecords.put(userId, user);
435        }
436        return user;
437    }
438
439    private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
440        for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
441            if (mSessionsListeners.get(i).mListener == listener) {
442                return i;
443            }
444        }
445        return -1;
446    }
447
448    private boolean isSessionDiscoverable(MediaSessionRecord record) {
449        // TODO probably want to check more than if it's active.
450        return record.isActive();
451    }
452
453    private void pushSessionsChanged(int userId) {
454        synchronized (mLock) {
455            List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
456            int size = records.size();
457            if (size > 0) {
458                rememberMediaButtonReceiverLocked(records.get(0));
459            }
460            ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
461            for (int i = 0; i < size; i++) {
462                tokens.add(new MediaSession.Token(records.get(i).getControllerBinder()));
463            }
464            pushRemoteVolumeUpdateLocked(userId);
465            for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
466                SessionsListenerRecord record = mSessionsListeners.get(i);
467                if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
468                    try {
469                        record.mListener.onActiveSessionsChanged(tokens);
470                    } catch (RemoteException e) {
471                        Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
472                                e);
473                        mSessionsListeners.remove(i);
474                    }
475                }
476            }
477        }
478    }
479
480    private void pushRemoteVolumeUpdateLocked(int userId) {
481        if (mRvc != null) {
482            try {
483                MediaSessionRecord record = mPriorityStack.getDefaultRemoteSession(userId);
484                mRvc.updateRemoteController(record == null ? null : record.getControllerBinder());
485            } catch (RemoteException e) {
486                Log.wtf(TAG, "Error sending default remote volume to sys ui.", e);
487            }
488        }
489    }
490
491    private void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
492        PendingIntent receiver = record.getMediaButtonReceiver();
493        UserRecord user = mUserRecords.get(record.getUserId());
494        if (receiver != null && user != null) {
495            user.mLastMediaButtonReceiver = receiver;
496        }
497    }
498
499    /**
500     * Information about a particular user. The contents of this object is
501     * guarded by mLock.
502     */
503    final class UserRecord {
504        private final int mUserId;
505        private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
506        private PendingIntent mLastMediaButtonReceiver;
507
508        public UserRecord(Context context, int userId) {
509            mUserId = userId;
510        }
511
512        public void startLocked() {
513            // nothing for now
514        }
515
516        public void stopLocked() {
517            // nothing for now
518        }
519
520        public void destroyLocked() {
521            for (int i = mSessions.size() - 1; i >= 0; i--) {
522                MediaSessionRecord session = mSessions.get(i);
523                MediaSessionService.this.destroySessionLocked(session);
524            }
525        }
526
527        public ArrayList<MediaSessionRecord> getSessionsLocked() {
528            return mSessions;
529        }
530
531        public void addSessionLocked(MediaSessionRecord session) {
532            mSessions.add(session);
533        }
534
535        public void removeSessionLocked(MediaSessionRecord session) {
536            mSessions.remove(session);
537        }
538
539        public void dumpLocked(PrintWriter pw, String prefix) {
540            pw.println(prefix + "Record for user " + mUserId);
541            String indent = prefix + "  ";
542            pw.println(indent + "MediaButtonReceiver:" + mLastMediaButtonReceiver);
543            int size = mSessions.size();
544            pw.println(indent + size + " Sessions:");
545            for (int i = 0; i < size; i++) {
546                // Just print the short version, the full session dump will
547                // already be in the list of all sessions.
548                pw.println(indent + mSessions.get(i).toString());
549            }
550        }
551    }
552
553    final class SessionsListenerRecord implements IBinder.DeathRecipient {
554        private final IActiveSessionsListener mListener;
555        private final ComponentName mComponentName;
556        private final int mUserId;
557        private final int mPid;
558        private final int mUid;
559
560        public SessionsListenerRecord(IActiveSessionsListener listener,
561                ComponentName componentName,
562                int userId, int pid, int uid) {
563            mListener = listener;
564            mComponentName = componentName;
565            mUserId = userId;
566            mPid = pid;
567            mUid = uid;
568        }
569
570        @Override
571        public void binderDied() {
572            synchronized (mLock) {
573                mSessionsListeners.remove(this);
574            }
575        }
576    }
577
578    final class SettingsObserver extends ContentObserver {
579        private final Uri mSecureSettingsUri = Settings.Secure.getUriFor(
580                Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
581
582        private SettingsObserver() {
583            super(null);
584        }
585
586        private void observe() {
587            mContentResolver.registerContentObserver(mSecureSettingsUri,
588                    false, this, UserHandle.USER_ALL);
589        }
590
591        @Override
592        public void onChange(boolean selfChange, Uri uri) {
593            updateActiveSessionListeners();
594        }
595    }
596
597    class SessionManagerImpl extends ISessionManager.Stub {
598        private static final String EXTRA_WAKELOCK_ACQUIRED =
599                "android.media.AudioService.WAKELOCK_ACQUIRED";
600        private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
601
602        private boolean mVoiceButtonDown = false;
603        private boolean mVoiceButtonHandled = false;
604
605        @Override
606        public ISession createSession(String packageName, ISessionCallback cb, String tag,
607                int userId) throws RemoteException {
608            final int pid = Binder.getCallingPid();
609            final int uid = Binder.getCallingUid();
610            final long token = Binder.clearCallingIdentity();
611            try {
612                enforcePackageName(packageName, uid);
613                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
614                        false /* allowAll */, true /* requireFull */, "createSession", packageName);
615                if (cb == null) {
616                    throw new IllegalArgumentException("Controller callback cannot be null");
617                }
618                return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
619                        .getSessionBinder();
620            } finally {
621                Binder.restoreCallingIdentity(token);
622            }
623        }
624
625        @Override
626        public List<IBinder> getSessions(ComponentName componentName, int userId) {
627            final int pid = Binder.getCallingPid();
628            final int uid = Binder.getCallingUid();
629            final long token = Binder.clearCallingIdentity();
630
631            try {
632                int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
633                ArrayList<IBinder> binders = new ArrayList<IBinder>();
634                synchronized (mLock) {
635                    ArrayList<MediaSessionRecord> records = mPriorityStack
636                            .getActiveSessions(resolvedUserId);
637                    int size = records.size();
638                    for (int i = 0; i < size; i++) {
639                        binders.add(records.get(i).getControllerBinder().asBinder());
640                    }
641                }
642                return binders;
643            } finally {
644                Binder.restoreCallingIdentity(token);
645            }
646        }
647
648        @Override
649        public void addSessionsListener(IActiveSessionsListener listener,
650                ComponentName componentName, int userId) throws RemoteException {
651            final int pid = Binder.getCallingPid();
652            final int uid = Binder.getCallingUid();
653            final long token = Binder.clearCallingIdentity();
654
655            try {
656                int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
657                synchronized (mLock) {
658                    int index = findIndexOfSessionsListenerLocked(listener);
659                    if (index != -1) {
660                        Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
661                        return;
662                    }
663                    SessionsListenerRecord record = new SessionsListenerRecord(listener,
664                            componentName, resolvedUserId, pid, uid);
665                    try {
666                        listener.asBinder().linkToDeath(record, 0);
667                    } catch (RemoteException e) {
668                        Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
669                        return;
670                    }
671                    mSessionsListeners.add(record);
672                }
673            } finally {
674                Binder.restoreCallingIdentity(token);
675            }
676        }
677
678        @Override
679        public void removeSessionsListener(IActiveSessionsListener listener)
680                throws RemoteException {
681            synchronized (mLock) {
682                int index = findIndexOfSessionsListenerLocked(listener);
683                if (index != -1) {
684                    SessionsListenerRecord record = mSessionsListeners.remove(index);
685                    try {
686                        record.mListener.asBinder().unlinkToDeath(record, 0);
687                    } catch (Exception e) {
688                        // ignore exceptions, the record is being removed
689                    }
690                }
691            }
692        }
693
694        /**
695         * Handles the dispatching of the media button events to one of the
696         * registered listeners, or if there was none, broadcast an
697         * ACTION_MEDIA_BUTTON intent to the rest of the system.
698         *
699         * @param keyEvent a non-null KeyEvent whose key code is one of the
700         *            supported media buttons
701         * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
702         *            while this key event is dispatched.
703         */
704        @Override
705        public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
706            if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
707                Log.w(TAG, "Attempted to dispatch null or non-media key event.");
708                return;
709            }
710            final int pid = Binder.getCallingPid();
711            final int uid = Binder.getCallingUid();
712            final long token = Binder.clearCallingIdentity();
713
714            try {
715                synchronized (mLock) {
716                    MediaSessionRecord session = mPriorityStack
717                            .getDefaultMediaButtonSession(mCurrentUserId);
718                    if (isVoiceKey(keyEvent.getKeyCode())) {
719                        handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
720                    } else {
721                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
722                    }
723                }
724            } finally {
725                Binder.restoreCallingIdentity(token);
726            }
727        }
728
729        @Override
730        public void dispatchAdjustVolume(int suggestedStream, int delta, int flags)
731                throws RemoteException {
732            final int pid = Binder.getCallingPid();
733            final int uid = Binder.getCallingUid();
734            final long token = Binder.clearCallingIdentity();
735            try {
736                synchronized (mLock) {
737                    MediaSessionRecord session = mPriorityStack
738                            .getDefaultVolumeSession(mCurrentUserId);
739                    dispatchAdjustVolumeLocked(suggestedStream, delta, flags, session);
740                }
741            } finally {
742                Binder.restoreCallingIdentity(token);
743            }
744        }
745
746        @Override
747        public void setRemoteVolumeController(IRemoteVolumeController rvc) {
748            final int pid = Binder.getCallingPid();
749            final int uid = Binder.getCallingUid();
750            final long token = Binder.clearCallingIdentity();
751            try {
752                enforceStatusBarPermission("listen for volume changes", pid, uid);
753                mRvc = rvc;
754            } finally {
755                Binder.restoreCallingIdentity(token);
756            }
757        }
758
759        @Override
760        public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
761            if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
762                    != PackageManager.PERMISSION_GRANTED) {
763                pw.println("Permission Denial: can't dump MediaSessionService from from pid="
764                        + Binder.getCallingPid()
765                        + ", uid=" + Binder.getCallingUid());
766                return;
767            }
768
769            pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
770            pw.println();
771
772            synchronized (mLock) {
773                pw.println("Session for calls:" + mPrioritySession);
774                if (mPrioritySession != null) {
775                    mPrioritySession.dump(pw, "");
776                }
777                int count = mAllSessions.size();
778                pw.println(count + " Sessions:");
779                for (int i = 0; i < count; i++) {
780                    mAllSessions.get(i).dump(pw, "");
781                    pw.println();
782                }
783                mPriorityStack.dump(pw, "");
784
785                pw.println("User Records:");
786                count = mUserRecords.size();
787                for (int i = 0; i < count; i++) {
788                    UserRecord user = mUserRecords.get(i);
789                    user.dumpLocked(pw, "");
790                }
791            }
792        }
793
794        private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
795                final int uid) {
796            String packageName = null;
797            if (componentName != null) {
798                // If they gave us a component name verify they own the
799                // package
800                packageName = componentName.getPackageName();
801                enforcePackageName(packageName, uid);
802            }
803            // Check that they can make calls on behalf of the user and
804            // get the final user id
805            int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
806                    true /* allowAll */, true /* requireFull */, "getSessions", packageName);
807            // Check if they have the permissions or their component is
808            // enabled for the user they're calling from.
809            enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
810            return resolvedUserId;
811        }
812
813        private void dispatchAdjustVolumeLocked(int suggestedStream, int direction, int flags,
814                MediaSessionRecord session) {
815            if (DEBUG) {
816                String description = session == null ? null : session.toString();
817                Log.d(TAG, "Adjusting session " + description + " by " + direction + ". flags="
818                        + flags + ", suggestedStream=" + suggestedStream);
819
820            }
821            if (session == null) {
822                if ((flags & AudioManager.FLAG_ACTIVE_MEDIA_ONLY) != 0) {
823                    if (DEBUG) {
824                        Log.d(TAG, "No active session to adjust, skipping media only volume event");
825                    }
826                    return;
827                }
828                try {
829                    mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream, flags,
830                            getContext().getOpPackageName());
831                } catch (RemoteException e) {
832                    Log.e(TAG, "Error adjusting default volume.", e);
833                }
834            } else {
835                session.adjustVolume(direction, flags);
836                if (session.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE
837                        && mRvc != null) {
838                    try {
839                        mRvc.remoteVolumeChanged(session.getControllerBinder(), flags);
840                    } catch (Exception e) {
841                        Log.wtf(TAG, "Error sending volume change to system UI.", e);
842                    }
843                }
844            }
845        }
846
847        private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
848                MediaSessionRecord session) {
849            if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
850                // If the phone app has priority just give it the event
851                dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
852                return;
853            }
854            int action = keyEvent.getAction();
855            boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
856            if (action == KeyEvent.ACTION_DOWN) {
857                if (keyEvent.getRepeatCount() == 0) {
858                    mVoiceButtonDown = true;
859                    mVoiceButtonHandled = false;
860                } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
861                    mVoiceButtonHandled = true;
862                    startVoiceInput(needWakeLock);
863                }
864            } else if (action == KeyEvent.ACTION_UP) {
865                if (mVoiceButtonDown) {
866                    mVoiceButtonDown = false;
867                    if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
868                        // Resend the down then send this event through
869                        KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
870                        dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
871                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
872                    }
873                }
874            }
875        }
876
877        private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
878                MediaSessionRecord session) {
879            if (session != null) {
880                if (DEBUG) {
881                    Log.d(TAG, "Sending media key to " + session.toString());
882                }
883                if (needWakeLock) {
884                    mKeyEventReceiver.aquireWakeLockLocked();
885                }
886                // If we don't need a wakelock use -1 as the id so we
887                // won't release it later
888                session.sendMediaButton(keyEvent,
889                        needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
890                        mKeyEventReceiver);
891            } else {
892                // Launch the last PendingIntent we had with priority
893                int userId = ActivityManager.getCurrentUser();
894                UserRecord user = mUserRecords.get(userId);
895                if (user.mLastMediaButtonReceiver != null) {
896                    if (DEBUG) {
897                        Log.d(TAG, "Sending media key to last known PendingIntent");
898                    }
899                    if (needWakeLock) {
900                        mKeyEventReceiver.aquireWakeLockLocked();
901                    }
902                    Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
903                    mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
904                    try {
905                        user.mLastMediaButtonReceiver.send(getContext(),
906                                needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
907                                mediaButtonIntent, mKeyEventReceiver, null);
908                    } catch (CanceledException e) {
909                        Log.i(TAG, "Error sending key event to media button receiver "
910                                + user.mLastMediaButtonReceiver, e);
911                    }
912                } else {
913                    if (DEBUG) {
914                        Log.d(TAG, "Sending media key ordered broadcast");
915                    }
916                    if (needWakeLock) {
917                        mMediaEventWakeLock.acquire();
918                    }
919                    // Fallback to legacy behavior
920                    Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
921                    keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
922                    if (needWakeLock) {
923                        keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
924                                WAKELOCK_RELEASE_ON_FINISHED);
925                    }
926                    getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
927                            null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
928                }
929            }
930        }
931
932        private void startVoiceInput(boolean needWakeLock) {
933            Intent voiceIntent = null;
934            // select which type of search to launch:
935            // - screen on and device unlocked: action is ACTION_WEB_SEARCH
936            // - device locked or screen off: action is
937            // ACTION_VOICE_SEARCH_HANDS_FREE
938            // with EXTRA_SECURE set to true if the device is securely locked
939            PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
940            boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
941            if (!isLocked && pm.isScreenOn()) {
942                voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
943                Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
944            } else {
945                voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
946                voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
947                        isLocked && mKeyguardManager.isKeyguardSecure());
948                Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
949            }
950            // start the search activity
951            if (needWakeLock) {
952                mMediaEventWakeLock.acquire();
953            }
954            try {
955                if (voiceIntent != null) {
956                    voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
957                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
958                    getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
959                }
960            } catch (ActivityNotFoundException e) {
961                Log.w(TAG, "No activity for search: " + e);
962            } finally {
963                if (needWakeLock) {
964                    mMediaEventWakeLock.release();
965                }
966            }
967        }
968
969        private boolean isVoiceKey(int keyCode) {
970            return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
971        }
972
973        private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
974
975        class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable,
976                PendingIntent.OnFinished {
977            private final Handler mHandler;
978            private int mRefCount = 0;
979            private int mLastTimeoutId = 0;
980
981            public KeyEventWakeLockReceiver(Handler handler) {
982                super(handler);
983                mHandler = handler;
984            }
985
986            public void onTimeout() {
987                synchronized (mLock) {
988                    if (mRefCount == 0) {
989                        // We've already released it, so just return
990                        return;
991                    }
992                    mLastTimeoutId++;
993                    mRefCount = 0;
994                    releaseWakeLockLocked();
995                }
996            }
997
998            public void aquireWakeLockLocked() {
999                if (mRefCount == 0) {
1000                    mMediaEventWakeLock.acquire();
1001                }
1002                mRefCount++;
1003                mHandler.removeCallbacks(this);
1004                mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1005
1006            }
1007
1008            @Override
1009            public void run() {
1010                onTimeout();
1011            }
1012
1013            @Override
1014            protected void onReceiveResult(int resultCode, Bundle resultData) {
1015                if (resultCode < mLastTimeoutId) {
1016                    // Ignore results from calls that were before the last
1017                    // timeout, just in case.
1018                    return;
1019                } else {
1020                    synchronized (mLock) {
1021                        if (mRefCount > 0) {
1022                            mRefCount--;
1023                            if (mRefCount == 0) {
1024                                releaseWakeLockLocked();
1025                            }
1026                        }
1027                    }
1028                }
1029            }
1030
1031            private void releaseWakeLockLocked() {
1032                mMediaEventWakeLock.release();
1033                mHandler.removeCallbacks(this);
1034            }
1035
1036            @Override
1037            public void onSendFinished(PendingIntent pendingIntent, Intent intent, int resultCode,
1038                    String resultData, Bundle resultExtras) {
1039                onReceiveResult(resultCode, null);
1040            }
1041        };
1042
1043        BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1044            @Override
1045            public void onReceive(Context context, Intent intent) {
1046                if (intent == null) {
1047                    return;
1048                }
1049                Bundle extras = intent.getExtras();
1050                if (extras == null) {
1051                    return;
1052                }
1053                synchronized (mLock) {
1054                    if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1055                            && mMediaEventWakeLock.isHeld()) {
1056                        mMediaEventWakeLock.release();
1057                    }
1058                }
1059            }
1060        };
1061    }
1062
1063    final class MessageHandler extends Handler {
1064        private static final int MSG_SESSIONS_CHANGED = 1;
1065
1066        @Override
1067        public void handleMessage(Message msg) {
1068            switch (msg.what) {
1069                case MSG_SESSIONS_CHANGED:
1070                    pushSessionsChanged(msg.arg1);
1071                    break;
1072            }
1073        }
1074
1075        public void post(int what, int arg1, int arg2) {
1076            obtainMessage(what, arg1, arg2).sendToTarget();
1077        }
1078    }
1079}
1080