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