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