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