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