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