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