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