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