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