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