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