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