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