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