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