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