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