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