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