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