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