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