MediaSessionService.java revision 8a2cfc309ab9126e90022916967c65a793c034f0
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.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.Context;
25import android.content.Intent;
26import android.content.pm.PackageManager;
27import android.media.routeprovider.RouteRequest;
28import android.media.session.ISession;
29import android.media.session.ISessionCallback;
30import android.media.session.ISessionManager;
31import android.media.session.RouteInfo;
32import android.media.session.RouteOptions;
33import android.media.session.Session;
34import android.os.Binder;
35import android.os.Bundle;
36import android.os.Handler;
37import android.os.IBinder;
38import android.os.PowerManager;
39import android.os.RemoteException;
40import android.os.ResultReceiver;
41import android.os.UserHandle;
42import android.provider.Settings;
43import android.text.TextUtils;
44import android.util.Log;
45import android.util.SparseArray;
46import android.view.KeyEvent;
47
48import com.android.server.SystemService;
49import com.android.server.Watchdog;
50import com.android.server.Watchdog.Monitor;
51
52import java.io.FileDescriptor;
53import java.io.PrintWriter;
54import java.util.ArrayList;
55import java.util.List;
56
57/**
58 * System implementation of MediaSessionManager
59 */
60public class MediaSessionService extends SystemService implements Monitor {
61    private static final String TAG = "MediaSessionService";
62    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
63
64    private final SessionManagerImpl mSessionManagerImpl;
65    // private final MediaRouteProviderWatcher mRouteProviderWatcher;
66    private final MediaSessionStack mPriorityStack;
67
68    private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>();
69    private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
70    // private final ArrayList<MediaRouteProviderProxy> mProviders
71    // = new ArrayList<MediaRouteProviderProxy>();
72    private final Object mLock = new Object();
73    private final Handler mHandler = new Handler();
74    private final PowerManager.WakeLock mMediaEventWakeLock;
75
76    private MediaSessionRecord mPrioritySession;
77    private int mCurrentUserId = -1;
78
79    // Used to keep track of the current request to show routes for a specific
80    // session so we drop late callbacks properly.
81    private int mShowRoutesRequestId = 0;
82
83    // TODO refactor to have per user state for providers. See
84    // MediaRouterService for an example
85
86    public MediaSessionService(Context context) {
87        super(context);
88        mSessionManagerImpl = new SessionManagerImpl();
89        mPriorityStack = new MediaSessionStack();
90        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
91        mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
92    }
93
94    @Override
95    public void onStart() {
96        publishBinderService(Context.MEDIA_SESSION_SERVICE, mSessionManagerImpl);
97        Watchdog.getInstance().addMonitor(this);
98        updateUser();
99    }
100
101    /**
102     * Should trigger showing the Media route picker dialog. Right now it just
103     * kicks off a query to all the providers to get routes.
104     *
105     * @param record The session to show the picker for.
106     */
107    public void showRoutePickerForSession(MediaSessionRecord record) {
108        // TODO for now just toggle the route to test (we will only have one
109        // match for now)
110        synchronized (mLock) {
111            if (!mAllSessions.contains(record)) {
112                Log.d(TAG, "Unknown session tried to show route picker. Ignoring.");
113                return;
114            }
115            RouteInfo current = record.getRoute();
116            UserRecord user = mUserRecords.get(record.getUserId());
117            if (current != null) {
118                // For now send null to mean the local route
119                MediaRouteProviderProxy proxy = user.getProviderLocked(current.getProvider());
120                if (proxy != null) {
121                    proxy.removeSession(record);
122                }
123                record.selectRoute(null);
124                return;
125            }
126            ArrayList<MediaRouteProviderProxy> providers = user.getProvidersLocked();
127            mShowRoutesRequestId++;
128            for (int i = providers.size() - 1; i >= 0; i--) {
129                MediaRouteProviderProxy provider = providers.get(i);
130                provider.getRoutes(record, mShowRoutesRequestId);
131            }
132        }
133    }
134
135    /**
136     * Connect a session to the given route.
137     *
138     * @param session The session to connect.
139     * @param route The route to connect to.
140     * @param options The options to use for the connection.
141     */
142    public void connectToRoute(MediaSessionRecord session, RouteInfo route,
143            RouteOptions options) {
144        synchronized (mLock) {
145            if (!mAllSessions.contains(session)) {
146                Log.d(TAG, "Unknown session attempting to connect to route. Ignoring");
147                return;
148            }
149            UserRecord user = mUserRecords.get(session.getUserId());
150            if (user == null) {
151                Log.wtf(TAG, "connectToRoute: User " + session.getUserId() + " does not exist.");
152                return;
153            }
154            MediaRouteProviderProxy proxy = user.getProviderLocked(route.getProvider());
155            if (proxy == null) {
156                Log.w(TAG, "Provider for route " + route.getName() + " does not exist.");
157                return;
158            }
159            RouteRequest request = new RouteRequest(session.getSessionInfo(), options, true);
160            proxy.connectToRoute(session, route, request);
161        }
162    }
163
164    public void updateSession(MediaSessionRecord record) {
165        synchronized (mLock) {
166            if (!mAllSessions.contains(record)) {
167                Log.d(TAG, "Unknown session updated. Ignoring.");
168                return;
169            }
170            mPriorityStack.onSessionStateChange(record);
171            if (record.isSystemPriority()) {
172                if (record.isActive()) {
173                    if (mPrioritySession != null) {
174                        Log.w(TAG, "Replacing existing priority session with a new session");
175                    }
176                    mPrioritySession = record;
177                } else {
178                    if (mPrioritySession == record) {
179                        mPrioritySession = null;
180                    }
181                }
182            }
183        }
184    }
185
186    public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
187        synchronized (mLock) {
188            if (!mAllSessions.contains(record)) {
189                Log.d(TAG, "Unknown session changed playback state. Ignoring.");
190                return;
191            }
192            mPriorityStack.onPlaystateChange(record, oldState, newState);
193        }
194    }
195
196    @Override
197    public void onStartUser(int userHandle) {
198        updateUser();
199    }
200
201    @Override
202    public void onSwitchUser(int userHandle) {
203        updateUser();
204    }
205
206    @Override
207    public void onStopUser(int userHandle) {
208        synchronized (mLock) {
209            UserRecord user = mUserRecords.get(userHandle);
210            if (user != null) {
211                destroyUserLocked(user);
212            }
213        }
214    }
215
216    @Override
217    public void monitor() {
218        synchronized (mLock) {
219            // Check for deadlock
220        }
221    }
222
223    protected void enforcePhoneStatePermission(int pid, int uid) {
224        if (getContext().checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE, pid, uid)
225                != PackageManager.PERMISSION_GRANTED) {
226            throw new SecurityException("Must hold the MODIFY_PHONE_STATE permission.");
227        }
228    }
229
230    void sessionDied(MediaSessionRecord session) {
231        synchronized (mLock) {
232            destroySessionLocked(session);
233        }
234    }
235
236    void destroySession(MediaSessionRecord session) {
237        synchronized (mLock) {
238            destroySessionLocked(session);
239        }
240    }
241
242    private void updateUser() {
243        synchronized (mLock) {
244            int userId = ActivityManager.getCurrentUser();
245            if (mCurrentUserId != userId) {
246                final int oldUserId = mCurrentUserId;
247                mCurrentUserId = userId; // do this first
248
249                UserRecord oldUser = mUserRecords.get(oldUserId);
250                if (oldUser != null) {
251                    oldUser.stopLocked();
252                }
253
254                UserRecord newUser = getOrCreateUser(userId);
255                newUser.startLocked();
256            }
257        }
258    }
259
260    /**
261     * Stop the user and unbind from everything.
262     *
263     * @param user The user to dispose of
264     */
265    private void destroyUserLocked(UserRecord user) {
266        user.stopLocked();
267        user.destroyLocked();
268        mUserRecords.remove(user.mUserId);
269    }
270
271    /*
272     * When a session is removed several things need to happen.
273     * 1. We need to remove it from the relevant user.
274     * 2. We need to remove it from the priority stack.
275     * 3. We need to remove it from all sessions.
276     * 4. If this is the system priority session we need to clear it.
277     * 5. We need to unlink to death from the cb binder
278     * 6. We need to tell the session to do any final cleanup (onDestroy)
279     */
280    private void destroySessionLocked(MediaSessionRecord session) {
281        int userId = session.getUserId();
282        UserRecord user = mUserRecords.get(userId);
283        if (user != null) {
284            user.removeSessionLocked(session);
285        }
286
287        mPriorityStack.removeSession(session);
288        mAllSessions.remove(session);
289        if (session == mPrioritySession) {
290            mPrioritySession = null;
291        }
292
293        try {
294            session.getCallback().asBinder().unlinkToDeath(session, 0);
295        } catch (Exception e) {
296            // ignore exceptions while destroying a session.
297        }
298        session.onDestroy();
299    }
300
301    private void enforcePackageName(String packageName, int uid) {
302        if (TextUtils.isEmpty(packageName)) {
303            throw new IllegalArgumentException("packageName may not be empty");
304        }
305        String[] packages = getContext().getPackageManager().getPackagesForUid(uid);
306        final int packageCount = packages.length;
307        for (int i = 0; i < packageCount; i++) {
308            if (packageName.equals(packages[i])) {
309                return;
310            }
311        }
312        throw new IllegalArgumentException("packageName is not owned by the calling process");
313    }
314
315    /**
316     * Checks a caller's authorization to register an IRemoteControlDisplay.
317     * Authorization is granted if one of the following is true:
318     * <ul>
319     * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL
320     * permission</li>
321     * <li>the caller's listener is one of the enabled notification listeners
322     * for the caller's user</li>
323     * </ul>
324     */
325    private void enforceMediaPermissions(ComponentName compName, int pid, int uid,
326            int resolvedUserId) {
327        if (getContext()
328                .checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
329                    != PackageManager.PERMISSION_GRANTED
330                && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
331                        resolvedUserId)) {
332            throw new SecurityException("Missing permission to control media.");
333        }
334    }
335
336    /**
337     * This checks if the component is an enabled notification listener for the
338     * specified user. Enabled components may only operate on behalf of the user
339     * they're running as.
340     *
341     * @param compName The component that is enabled.
342     * @param userId The user id of the caller.
343     * @param forUserId The user id they're making the request on behalf of.
344     * @return True if the component is enabled, false otherwise
345     */
346    private boolean isEnabledNotificationListener(ComponentName compName, int userId,
347            int forUserId) {
348        if (userId != forUserId) {
349            // You may not access another user's content as an enabled listener.
350            return false;
351        }
352        if (compName != null) {
353            final String enabledNotifListeners = Settings.Secure.getStringForUser(
354                    getContext().getContentResolver(),
355                    Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
356                    userId);
357            if (enabledNotifListeners != null) {
358                final String[] components = enabledNotifListeners.split(":");
359                for (int i = 0; i < components.length; i++) {
360                    final ComponentName component =
361                            ComponentName.unflattenFromString(components[i]);
362                    if (component != null) {
363                        if (compName.equals(component)) {
364                            if (DEBUG) {
365                                Log.d(TAG, "ok to get sessions: " + component +
366                                        " is authorized notification listener");
367                            }
368                            return true;
369                        }
370                    }
371                }
372            }
373            if (DEBUG) {
374                Log.d(TAG, "not ok to get sessions, " + compName +
375                        " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
376            }
377        }
378        return false;
379    }
380
381    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
382            String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
383        synchronized (mLock) {
384            return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
385        }
386    }
387
388    /*
389     * When a session is created the following things need to happen.
390     * 1. Its callback binder needs a link to death
391     * 2. It needs to be added to all sessions.
392     * 3. It needs to be added to the priority stack.
393     * 4. It needs to be added to the relevant user record.
394     */
395    private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
396            String callerPackageName, ISessionCallback cb, String tag) {
397
398        final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
399                callerPackageName, cb, tag, this, mHandler);
400        try {
401            cb.asBinder().linkToDeath(session, 0);
402        } catch (RemoteException e) {
403            throw new RuntimeException("Media Session owner died prematurely.", e);
404        }
405
406        mAllSessions.add(session);
407        mPriorityStack.addSession(session);
408
409        UserRecord user = getOrCreateUser(userId);
410        user.addSessionLocked(session);
411
412        if (DEBUG) {
413            Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
414        }
415        return session;
416    }
417
418    private UserRecord getOrCreateUser(int userId) {
419        UserRecord user = mUserRecords.get(userId);
420        if (user == null) {
421            user = new UserRecord(getContext(), userId);
422            mUserRecords.put(userId, user);
423        }
424        return user;
425    }
426
427    private int findIndexOfSessionForIdLocked(String sessionId) {
428        for (int i = mAllSessions.size() - 1; i >= 0; i--) {
429            MediaSessionRecord session = mAllSessions.get(i);
430            if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
431                return i;
432            }
433        }
434        return -1;
435    }
436
437    private boolean isSessionDiscoverable(MediaSessionRecord record) {
438        // TODO probably want to check more than if it's active.
439        return record.isActive();
440    }
441
442    private MediaRouteProviderProxy.RoutesListener mRoutesCallback
443            = new MediaRouteProviderProxy.RoutesListener() {
444        @Override
445        public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
446                int reqId) {
447            // TODO for now select the first route to test, eventually add the
448            // new routes to the dialog if it is still open
449            synchronized (mLock) {
450                int index = findIndexOfSessionForIdLocked(sessionId);
451                if (index != -1 && routes != null && routes.size() > 0) {
452                    MediaSessionRecord record = mAllSessions.get(index);
453                    RouteInfo route = routes.get(0);
454                    record.selectRoute(route);
455                    UserRecord user = mUserRecords.get(record.getUserId());
456                    MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
457                    provider.addSession(record);
458                }
459            }
460        }
461
462        @Override
463        public void onRouteConnected(String sessionId, RouteInfo route,
464                RouteRequest options, RouteConnectionRecord connection) {
465            synchronized (mLock) {
466                int index = findIndexOfSessionForIdLocked(sessionId);
467                if (index != -1) {
468                    MediaSessionRecord session = mAllSessions.get(index);
469                    session.setRouteConnected(route, options.getConnectionOptions(), connection);
470                }
471            }
472        }
473    };
474
475    /**
476     * Information about a particular user. The contents of this object is
477     * guarded by mLock.
478     */
479    final class UserRecord {
480        private final int mUserId;
481        private final MediaRouteProviderWatcher mRouteProviderWatcher;
482        private final ArrayList<MediaRouteProviderProxy> mProviders
483                = new ArrayList<MediaRouteProviderProxy>();
484        private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
485
486        public UserRecord(Context context, int userId) {
487            mUserId = userId;
488            mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
489                    mProviderWatcherCallback, mHandler, userId);
490        }
491
492        public void startLocked() {
493            mRouteProviderWatcher.start();
494        }
495
496        public void stopLocked() {
497            mRouteProviderWatcher.stop();
498            updateInterestLocked();
499        }
500
501        public void destroyLocked() {
502            for (int i = mSessions.size() - 1; i >= 0; i--) {
503                MediaSessionRecord session = mSessions.get(i);
504                MediaSessionService.this.destroySessionLocked(session);
505                if (session.isConnected()) {
506                    session.disconnect(Session.DISCONNECT_REASON_USER_STOPPING);
507                }
508            }
509        }
510
511        public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
512            return mProviders;
513        }
514
515        public ArrayList<MediaSessionRecord> getSessionsLocked() {
516            return mSessions;
517        }
518
519        public void addSessionLocked(MediaSessionRecord session) {
520            mSessions.add(session);
521            updateInterestLocked();
522        }
523
524        public void removeSessionLocked(MediaSessionRecord session) {
525            mSessions.remove(session);
526            RouteInfo route = session.getRoute();
527            if (route != null) {
528                MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
529                if (provider != null) {
530                    provider.removeSession(session);
531                }
532            }
533            updateInterestLocked();
534        }
535
536        public void dumpLocked(PrintWriter pw, String prefix) {
537            pw.println(prefix + "Record for user " + mUserId);
538            String indent = prefix + "  ";
539            int size = mProviders.size();
540            pw.println(indent + size + " Providers:");
541            for (int i = 0; i < size; i++) {
542                mProviders.get(i).dump(pw, indent);
543            }
544            pw.println();
545            size = mSessions.size();
546            pw.println(indent + size + " Sessions:");
547            for (int i = 0; i < size; i++) {
548                // Just print the session info, the full session dump will
549                // already be in the list of all sessions.
550                pw.println(indent + mSessions.get(i).getSessionInfo());
551            }
552        }
553
554        public void updateInterestLocked() {
555            // TODO go through the sessions and build up the set of interfaces
556            // we're interested in. Update the provider watcher.
557            // For now, just express interest in all providers for the current
558            // user
559            boolean interested = mUserId == mCurrentUserId;
560            for (int i = mProviders.size() - 1; i >= 0; i--) {
561                mProviders.get(i).setInterested(interested);
562            }
563        }
564
565        private MediaRouteProviderProxy getProviderLocked(String providerId) {
566            for (int i = mProviders.size() - 1; i >= 0; i--) {
567                MediaRouteProviderProxy provider = mProviders.get(i);
568                if (TextUtils.equals(providerId, provider.getId())) {
569                    return provider;
570                }
571            }
572            return null;
573        }
574
575        private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
576                = new MediaRouteProviderWatcher.Callback() {
577            @Override
578            public void removeProvider(MediaRouteProviderProxy provider) {
579                synchronized (mLock) {
580                    mProviders.remove(provider);
581                    provider.setRoutesListener(null);
582                    provider.setInterested(false);
583                }
584            }
585
586            @Override
587            public void addProvider(MediaRouteProviderProxy provider) {
588                synchronized (mLock) {
589                    mProviders.add(provider);
590                    provider.setRoutesListener(mRoutesCallback);
591                    provider.setInterested(true);
592                }
593            }
594        };
595    }
596
597    class SessionManagerImpl extends ISessionManager.Stub {
598        private static final String EXTRA_WAKELOCK_ACQUIRED =
599                "android.media.AudioService.WAKELOCK_ACQUIRED";
600        private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
601
602        @Override
603        public ISession createSession(String packageName, ISessionCallback cb, String tag,
604                int userId) throws RemoteException {
605            final int pid = Binder.getCallingPid();
606            final int uid = Binder.getCallingUid();
607            final long token = Binder.clearCallingIdentity();
608            try {
609                enforcePackageName(packageName, uid);
610                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
611                        false /* allowAll */, true /* requireFull */, "createSession", packageName);
612                if (cb == null) {
613                    throw new IllegalArgumentException("Controller callback cannot be null");
614                }
615                return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
616                        .getSessionBinder();
617            } finally {
618                Binder.restoreCallingIdentity(token);
619            }
620        }
621
622        @Override
623        public List<IBinder> getSessions(ComponentName componentName, int userId) {
624            final int pid = Binder.getCallingPid();
625            final int uid = Binder.getCallingUid();
626            final long token = Binder.clearCallingIdentity();
627
628            try {
629                String packageName = null;
630                if (componentName != null) {
631                    // If they gave us a component name verify they own the
632                    // package
633                    packageName = componentName.getPackageName();
634                    enforcePackageName(packageName, uid);
635                }
636                // Check that they can make calls on behalf of the user and
637                // get the final user id
638                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
639                        true /* allowAll */, true /* requireFull */, "getSessions", packageName);
640                // Check if they have the permissions or their component is
641                // enabled for the user they're calling from.
642                enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
643                ArrayList<IBinder> binders = new ArrayList<IBinder>();
644                synchronized (mLock) {
645                    ArrayList<MediaSessionRecord> records = mPriorityStack
646                            .getActiveSessions(resolvedUserId);
647                    int size = records.size();
648                    for (int i = 0; i < size; i++) {
649                        binders.add(records.get(i).getControllerBinder().asBinder());
650                    }
651                }
652                return binders;
653            } finally {
654                Binder.restoreCallingIdentity(token);
655            }
656        }
657
658        /**
659         * Handles the dispatching of the media button events to one of the
660         * registered listeners, or if there was none, broadcast an
661         * ACTION_MEDIA_BUTTON intent to the rest of the system.
662         *
663         * @param keyEvent a non-null KeyEvent whose key code is one of the
664         *            supported media buttons
665         * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
666         *            while this key event is dispatched.
667         */
668        @Override
669        public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
670            if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
671                Log.w(TAG, "Attempted to dispatch null or non-media key event.");
672                return;
673            }
674            final int pid = Binder.getCallingPid();
675            final int uid = Binder.getCallingUid();
676            final long token = Binder.clearCallingIdentity();
677
678            try {
679                if (needWakeLock) {
680                    mMediaEventWakeLock.acquire();
681                }
682                synchronized (mLock) {
683                    MediaSessionRecord mbSession = mPriorityStack
684                            .getDefaultMediaButtonSession(mCurrentUserId);
685                    if (mbSession != null) {
686                        if (DEBUG) {
687                            Log.d(TAG, "Sending media key to " + mbSession.getSessionInfo());
688                        }
689                        mbSession.sendMediaButton(keyEvent,
690                                needWakeLock ? mKeyEventDoneReceiver : null);
691                    } else {
692                        if (DEBUG) {
693                            Log.d(TAG, "Sending media key ordered broadcast");
694                        }
695                        // Fallback to legacy behavior
696                        Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
697                        keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
698                        if (needWakeLock) {
699                            keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
700                                    WAKELOCK_RELEASE_ON_FINISHED);
701                        }
702                        getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
703                                null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
704                    }
705                }
706            } finally {
707                Binder.restoreCallingIdentity(token);
708            }
709        }
710
711        @Override
712        public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
713            if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
714                    != PackageManager.PERMISSION_GRANTED) {
715                pw.println("Permission Denial: can't dump MediaSessionService from from pid="
716                        + Binder.getCallingPid()
717                        + ", uid=" + Binder.getCallingUid());
718                return;
719            }
720
721            pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
722            pw.println();
723
724            synchronized (mLock) {
725                pw.println("Session for calls:" + mPrioritySession);
726                if (mPrioritySession != null) {
727                    mPrioritySession.dump(pw, "");
728                }
729                int count = mAllSessions.size();
730                pw.println(count + " Sessions:");
731                for (int i = 0; i < count; i++) {
732                    mAllSessions.get(i).dump(pw, "");
733                    pw.println();
734                }
735                mPriorityStack.dump(pw, "");
736
737                pw.println("User Records:");
738                count = mUserRecords.size();
739                for (int i = 0; i < count; i++) {
740                    UserRecord user = mUserRecords.get(i);
741                    user.dumpLocked(pw, "");
742                }
743            }
744        }
745
746        ResultReceiver mKeyEventDoneReceiver = new ResultReceiver(mHandler) {
747            @Override
748            protected void onReceiveResult(int resultCode, Bundle resultData) {
749                synchronized (mLock) {
750                    if (mMediaEventWakeLock.isHeld()) {
751                        mMediaEventWakeLock.release();
752                    }
753                }
754            }
755        };
756
757        BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
758            @Override
759            public void onReceive(Context context, Intent intent) {
760                if (intent == null) {
761                    return;
762                }
763                Bundle extras = intent.getExtras();
764                if (extras == null) {
765                    return;
766                }
767                synchronized (mLock) {
768                    if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
769                            && mMediaEventWakeLock.isHeld()) {
770                        mMediaEventWakeLock.release();
771                    }
772                }
773            }
774        };
775    }
776
777}
778