MediaSessionService.java revision 6f0e4ddd66fcdcc13944d8970d0b560e2626508b
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 (compName != null) {
387            final String enabledNotifListeners = Settings.Secure.getStringForUser(mContentResolver,
388                    Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
389                    userId);
390            if (enabledNotifListeners != null) {
391                final String[] components = enabledNotifListeners.split(":");
392                for (int i = 0; i < components.length; i++) {
393                    final ComponentName component =
394                            ComponentName.unflattenFromString(components[i]);
395                    if (component != null) {
396                        if (compName.equals(component)) {
397                            if (DEBUG) {
398                                Log.d(TAG, "ok to get sessions: " + component +
399                                        " is authorized notification listener");
400                            }
401                            return true;
402                        }
403                    }
404                }
405            }
406            if (DEBUG) {
407                Log.d(TAG, "not ok to get sessions, " + compName +
408                        " is not in list of ENABLED_NOTIFICATION_LISTENERS for user " + userId);
409            }
410        }
411        return false;
412    }
413
414    private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
415            String callerPackageName, ISessionCallback cb, String tag) throws RemoteException {
416        synchronized (mLock) {
417            return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb, tag);
418        }
419    }
420
421    /*
422     * When a session is created the following things need to happen.
423     * 1. Its callback binder needs a link to death
424     * 2. It needs to be added to all sessions.
425     * 3. It needs to be added to the priority stack.
426     * 4. It needs to be added to the relevant user record.
427     */
428    private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
429            String callerPackageName, ISessionCallback cb, String tag) {
430
431        final MediaSessionRecord session = new MediaSessionRecord(callerPid, callerUid, userId,
432                callerPackageName, cb, tag, this, mHandler);
433        try {
434            cb.asBinder().linkToDeath(session, 0);
435        } catch (RemoteException e) {
436            throw new RuntimeException("Media Session owner died prematurely.", e);
437        }
438
439        mAllSessions.add(session);
440        mPriorityStack.addSession(session);
441
442        UserRecord user = getOrCreateUser(userId);
443        user.addSessionLocked(session);
444
445        mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0);
446
447        if (DEBUG) {
448            Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag);
449        }
450        return session;
451    }
452
453    private UserRecord getOrCreateUser(int userId) {
454        UserRecord user = mUserRecords.get(userId);
455        if (user == null) {
456            user = new UserRecord(getContext(), userId);
457            mUserRecords.put(userId, user);
458        }
459        return user;
460    }
461
462    private int findIndexOfSessionForIdLocked(String sessionId) {
463        for (int i = mAllSessions.size() - 1; i >= 0; i--) {
464            MediaSessionRecord session = mAllSessions.get(i);
465            if (TextUtils.equals(session.getSessionInfo().getId(), sessionId)) {
466                return i;
467            }
468        }
469        return -1;
470    }
471
472    private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
473        for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
474            if (mSessionsListeners.get(i).mListener == listener) {
475                return i;
476            }
477        }
478        return -1;
479    }
480
481    private boolean isSessionDiscoverable(MediaSessionRecord record) {
482        // TODO probably want to check more than if it's active.
483        return record.isActive();
484    }
485
486    private void pushSessionsChanged(int userId) {
487        synchronized (mLock) {
488            List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId);
489            int size = records.size();
490            if (size > 0) {
491                persistMediaButtonReceiverLocked(records.get(0));
492            }
493            ArrayList<MediaSessionToken> tokens = new ArrayList<MediaSessionToken>();
494            for (int i = 0; i < size; i++) {
495                tokens.add(new MediaSessionToken(records.get(i).getControllerBinder()));
496            }
497            for (int i = mSessionsListeners.size() - 1; i >= 0; i--) {
498                SessionsListenerRecord record = mSessionsListeners.get(i);
499                if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) {
500                    try {
501                        record.mListener.onActiveSessionsChanged(tokens);
502                    } catch (RemoteException e) {
503                        Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing",
504                                e);
505                        mSessionsListeners.remove(i);
506                    }
507                }
508            }
509        }
510    }
511
512    private void persistMediaButtonReceiverLocked(MediaSessionRecord record) {
513        ComponentName receiver = record.getMediaButtonReceiver();
514        if (receiver != null) {
515            Settings.System.putStringForUser(mContentResolver,
516                    Settings.System.MEDIA_BUTTON_RECEIVER,
517                    receiver == null ? "" : receiver.flattenToString(),
518                    UserHandle.USER_CURRENT);
519        }
520    }
521
522    private MediaRouteProviderProxy.RoutesListener mRoutesCallback
523            = new MediaRouteProviderProxy.RoutesListener() {
524        @Override
525        public void onRoutesUpdated(String sessionId, ArrayList<RouteInfo> routes,
526                int reqId) {
527            // TODO for now select the first route to test, eventually add the
528            // new routes to the dialog if it is still open
529            synchronized (mLock) {
530                int index = findIndexOfSessionForIdLocked(sessionId);
531                if (index != -1 && routes != null && routes.size() > 0) {
532                    MediaSessionRecord record = mAllSessions.get(index);
533                    RouteInfo route = routes.get(0);
534                    record.selectRoute(route);
535                    UserRecord user = mUserRecords.get(record.getUserId());
536                    MediaRouteProviderProxy provider = user.getProviderLocked(route.getProvider());
537                    provider.addSession(record);
538                }
539            }
540        }
541
542        @Override
543        public void onRouteConnected(String sessionId, RouteInfo route,
544                RouteRequest options, RouteConnectionRecord connection) {
545            synchronized (mLock) {
546                int index = findIndexOfSessionForIdLocked(sessionId);
547                if (index != -1) {
548                    MediaSessionRecord session = mAllSessions.get(index);
549                    session.setRouteConnected(route, options.getConnectionOptions(), connection);
550                }
551            }
552        }
553    };
554
555    /**
556     * Information about a particular user. The contents of this object is
557     * guarded by mLock.
558     */
559    final class UserRecord {
560        private final int mUserId;
561        private final MediaRouteProviderWatcher mRouteProviderWatcher;
562        private final ArrayList<MediaRouteProviderProxy> mProviders
563                = new ArrayList<MediaRouteProviderProxy>();
564        private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
565
566        public UserRecord(Context context, int userId) {
567            mUserId = userId;
568            mRouteProviderWatcher = new MediaRouteProviderWatcher(context,
569                    mProviderWatcherCallback, mHandler, userId);
570        }
571
572        public void startLocked() {
573            mRouteProviderWatcher.start();
574        }
575
576        public void stopLocked() {
577            mRouteProviderWatcher.stop();
578            updateInterestLocked();
579        }
580
581        public void destroyLocked() {
582            for (int i = mSessions.size() - 1; i >= 0; i--) {
583                MediaSessionRecord session = mSessions.get(i);
584                MediaSessionService.this.destroySessionLocked(session);
585                if (session.isConnected()) {
586                    session.disconnect(MediaSession.DISCONNECT_REASON_USER_STOPPING);
587                }
588            }
589        }
590
591        public ArrayList<MediaRouteProviderProxy> getProvidersLocked() {
592            return mProviders;
593        }
594
595        public ArrayList<MediaSessionRecord> getSessionsLocked() {
596            return mSessions;
597        }
598
599        public void addSessionLocked(MediaSessionRecord session) {
600            mSessions.add(session);
601            updateInterestLocked();
602        }
603
604        public void removeSessionLocked(MediaSessionRecord session) {
605            mSessions.remove(session);
606            RouteInfo route = session.getRoute();
607            if (route != null) {
608                MediaRouteProviderProxy provider = getProviderLocked(route.getProvider());
609                if (provider != null) {
610                    provider.removeSession(session);
611                }
612            }
613            updateInterestLocked();
614        }
615
616        public void dumpLocked(PrintWriter pw, String prefix) {
617            pw.println(prefix + "Record for user " + mUserId);
618            String indent = prefix + "  ";
619            int size = mProviders.size();
620            pw.println(indent + size + " Providers:");
621            for (int i = 0; i < size; i++) {
622                mProviders.get(i).dump(pw, indent);
623            }
624            pw.println();
625            size = mSessions.size();
626            pw.println(indent + size + " Sessions:");
627            for (int i = 0; i < size; i++) {
628                // Just print the session info, the full session dump will
629                // already be in the list of all sessions.
630                pw.println(indent + mSessions.get(i).getSessionInfo());
631            }
632        }
633
634        public void updateInterestLocked() {
635            // TODO go through the sessions and build up the set of interfaces
636            // we're interested in. Update the provider watcher.
637            // For now, just express interest in all providers for the current
638            // user
639            boolean interested = mUserId == mCurrentUserId;
640            for (int i = mProviders.size() - 1; i >= 0; i--) {
641                mProviders.get(i).setInterested(interested);
642            }
643        }
644
645        private MediaRouteProviderProxy getProviderLocked(String providerId) {
646            for (int i = mProviders.size() - 1; i >= 0; i--) {
647                MediaRouteProviderProxy provider = mProviders.get(i);
648                if (TextUtils.equals(providerId, provider.getId())) {
649                    return provider;
650                }
651            }
652            return null;
653        }
654
655        private MediaRouteProviderWatcher.Callback mProviderWatcherCallback
656                = new MediaRouteProviderWatcher.Callback() {
657            @Override
658            public void removeProvider(MediaRouteProviderProxy provider) {
659                synchronized (mLock) {
660                    mProviders.remove(provider);
661                    provider.setRoutesListener(null);
662                    provider.setInterested(false);
663                }
664            }
665
666            @Override
667            public void addProvider(MediaRouteProviderProxy provider) {
668                synchronized (mLock) {
669                    mProviders.add(provider);
670                    provider.setRoutesListener(mRoutesCallback);
671                    provider.setInterested(true);
672                }
673            }
674        };
675    }
676
677    final class SessionsListenerRecord implements IBinder.DeathRecipient {
678        private final IActiveSessionsListener mListener;
679        private final int mUserId;
680
681        public SessionsListenerRecord(IActiveSessionsListener listener, int userId) {
682            mListener = listener;
683            mUserId = userId;
684        }
685
686        @Override
687        public void binderDied() {
688            synchronized (mLock) {
689                mSessionsListeners.remove(this);
690            }
691        }
692    }
693
694    class SessionManagerImpl extends ISessionManager.Stub {
695        private static final String EXTRA_WAKELOCK_ACQUIRED =
696                "android.media.AudioService.WAKELOCK_ACQUIRED";
697        private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; // magic number
698
699        private boolean mVoiceButtonDown = false;
700        private boolean mVoiceButtonHandled = false;
701
702        @Override
703        public ISession createSession(String packageName, ISessionCallback cb, String tag,
704                int userId) throws RemoteException {
705            final int pid = Binder.getCallingPid();
706            final int uid = Binder.getCallingUid();
707            final long token = Binder.clearCallingIdentity();
708            try {
709                enforcePackageName(packageName, uid);
710                int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
711                        false /* allowAll */, true /* requireFull */, "createSession", packageName);
712                if (cb == null) {
713                    throw new IllegalArgumentException("Controller callback cannot be null");
714                }
715                return createSessionInternal(pid, uid, resolvedUserId, packageName, cb, tag)
716                        .getSessionBinder();
717            } finally {
718                Binder.restoreCallingIdentity(token);
719            }
720        }
721
722        @Override
723        public List<IBinder> getSessions(ComponentName componentName, int userId) {
724            final int pid = Binder.getCallingPid();
725            final int uid = Binder.getCallingUid();
726            final long token = Binder.clearCallingIdentity();
727
728            try {
729                int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
730                ArrayList<IBinder> binders = new ArrayList<IBinder>();
731                synchronized (mLock) {
732                    ArrayList<MediaSessionRecord> records = mPriorityStack
733                            .getActiveSessions(resolvedUserId);
734                    int size = records.size();
735                    for (int i = 0; i < size; i++) {
736                        binders.add(records.get(i).getControllerBinder().asBinder());
737                    }
738                }
739                return binders;
740            } finally {
741                Binder.restoreCallingIdentity(token);
742            }
743        }
744
745        @Override
746        public void addSessionsListener(IActiveSessionsListener listener,
747                ComponentName componentName, int userId) throws RemoteException {
748            final int pid = Binder.getCallingPid();
749            final int uid = Binder.getCallingUid();
750            final long token = Binder.clearCallingIdentity();
751
752            try {
753                int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid);
754                synchronized (mLock) {
755                    int index = findIndexOfSessionsListenerLocked(listener);
756                    if (index != -1) {
757                        Log.w(TAG, "ActiveSessionsListener is already added, ignoring");
758                        return;
759                    }
760                    SessionsListenerRecord record = new SessionsListenerRecord(listener,
761                            resolvedUserId);
762                    try {
763                        listener.asBinder().linkToDeath(record, 0);
764                    } catch (RemoteException e) {
765                        Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e);
766                        return;
767                    }
768                    mSessionsListeners.add(record);
769                }
770            } finally {
771                Binder.restoreCallingIdentity(token);
772            }
773        }
774
775        @Override
776        public void removeSessionsListener(IActiveSessionsListener listener)
777                throws RemoteException {
778            synchronized (mLock) {
779                int index = findIndexOfSessionsListenerLocked(listener);
780                if (index != -1) {
781                    SessionsListenerRecord record = mSessionsListeners.remove(index);
782                    try {
783                        record.mListener.asBinder().unlinkToDeath(record, 0);
784                    } catch (Exception e) {
785                        // ignore exceptions, the record is being removed
786                    }
787                }
788            }
789        }
790
791        /**
792         * Handles the dispatching of the media button events to one of the
793         * registered listeners, or if there was none, broadcast an
794         * ACTION_MEDIA_BUTTON intent to the rest of the system.
795         *
796         * @param keyEvent a non-null KeyEvent whose key code is one of the
797         *            supported media buttons
798         * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held
799         *            while this key event is dispatched.
800         */
801        @Override
802        public void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
803            if (keyEvent == null || !KeyEvent.isMediaKey(keyEvent.getKeyCode())) {
804                Log.w(TAG, "Attempted to dispatch null or non-media key event.");
805                return;
806            }
807            final int pid = Binder.getCallingPid();
808            final int uid = Binder.getCallingUid();
809            final long token = Binder.clearCallingIdentity();
810
811            try {
812                synchronized (mLock) {
813                    MediaSessionRecord session = mPriorityStack
814                            .getDefaultMediaButtonSession(mCurrentUserId);
815                    if (isVoiceKey(keyEvent.getKeyCode())) {
816                        handleVoiceKeyEventLocked(keyEvent, needWakeLock, session);
817                    } else {
818                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
819                    }
820                }
821            } finally {
822                Binder.restoreCallingIdentity(token);
823            }
824        }
825
826        @Override
827        public void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags)
828                throws RemoteException {
829            final int pid = Binder.getCallingPid();
830            final int uid = Binder.getCallingUid();
831            final long token = Binder.clearCallingIdentity();
832            try {
833                synchronized (mLock) {
834                    MediaSessionRecord session = mPriorityStack
835                            .getDefaultVolumeSession(mCurrentUserId);
836                    dispatchAdjustVolumeByLocked(suggestedStream, delta, flags, session);
837                }
838            } finally {
839                Binder.restoreCallingIdentity(token);
840            }
841        }
842
843        @Override
844        public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
845            if (getContext().checkCallingOrSelfPermission(Manifest.permission.DUMP)
846                    != PackageManager.PERMISSION_GRANTED) {
847                pw.println("Permission Denial: can't dump MediaSessionService from from pid="
848                        + Binder.getCallingPid()
849                        + ", uid=" + Binder.getCallingUid());
850                return;
851            }
852
853            pw.println("MEDIA SESSION SERVICE (dumpsys media_session)");
854            pw.println();
855
856            synchronized (mLock) {
857                pw.println("Session for calls:" + mPrioritySession);
858                if (mPrioritySession != null) {
859                    mPrioritySession.dump(pw, "");
860                }
861                int count = mAllSessions.size();
862                pw.println(count + " Sessions:");
863                for (int i = 0; i < count; i++) {
864                    mAllSessions.get(i).dump(pw, "");
865                    pw.println();
866                }
867                mPriorityStack.dump(pw, "");
868
869                pw.println("User Records:");
870                count = mUserRecords.size();
871                for (int i = 0; i < count; i++) {
872                    UserRecord user = mUserRecords.get(i);
873                    user.dumpLocked(pw, "");
874                }
875            }
876        }
877
878        private int verifySessionsRequest(ComponentName componentName, int userId, final int pid,
879                final int uid) {
880            String packageName = null;
881            if (componentName != null) {
882                // If they gave us a component name verify they own the
883                // package
884                packageName = componentName.getPackageName();
885                enforcePackageName(packageName, uid);
886            }
887            // Check that they can make calls on behalf of the user and
888            // get the final user id
889            int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
890                    true /* allowAll */, true /* requireFull */, "getSessions", packageName);
891            // Check if they have the permissions or their component is
892            // enabled for the user they're calling from.
893            enforceMediaPermissions(componentName, pid, uid, resolvedUserId);
894            return resolvedUserId;
895        }
896
897        private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags,
898                MediaSessionRecord session) {
899            int direction = 0;
900            int steps = delta;
901            if (delta > 0) {
902                direction = 1;
903            } else if (delta < 0) {
904                direction = -1;
905                steps = -delta;
906            }
907            if (DEBUG) {
908                String sessionInfo = session == null ? null : session.getSessionInfo().toString();
909                Log.d(TAG, "Adjusting session " + sessionInfo + " by " + delta + ". flags=" + flags
910                        + ", suggestedStream=" + suggestedStream);
911
912            }
913            if (session == null) {
914                try {
915                    if (delta == 0) {
916                        mAudioService.adjustSuggestedStreamVolume(delta, suggestedStream, flags,
917                                getContext().getOpPackageName());
918                    } else {
919                        for (int i = 0; i < steps; i++) {
920                            mAudioService.adjustSuggestedStreamVolume(direction, suggestedStream,
921                                    flags, getContext().getOpPackageName());
922                        }
923                    }
924                } catch (RemoteException e) {
925                    Log.e(TAG, "Error adjusting default volume.", e);
926                }
927            } else {
928                if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_LOCAL) {
929                    try {
930                        if (delta == 0) {
931                            mAudioService.adjustSuggestedStreamVolume(delta,
932                                    session.getAudioStream(), flags,
933                                    getContext().getOpPackageName());
934                        } else {
935                            for (int i = 0; i < steps; i++) {
936                                mAudioService.adjustSuggestedStreamVolume(direction,
937                                        session.getAudioStream(), flags,
938                                        getContext().getOpPackageName());
939                            }
940                        }
941                    } catch (RemoteException e) {
942                        Log.e(TAG, "Error adjusting volume for stream "
943                                + session.getAudioStream(), e);
944                    }
945                } else if (session.getPlaybackType() == MediaSession.VOLUME_TYPE_REMOTE) {
946                    session.adjustVolumeBy(delta);
947                }
948            }
949        }
950
951        private void handleVoiceKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
952                MediaSessionRecord session) {
953            if (session != null && session.hasFlag(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY)) {
954                // If the phone app has priority just give it the event
955                dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
956                return;
957            }
958            int action = keyEvent.getAction();
959            boolean isLongPress = (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0;
960            if (action == KeyEvent.ACTION_DOWN) {
961                if (keyEvent.getRepeatCount() == 0) {
962                    mVoiceButtonDown = true;
963                    mVoiceButtonHandled = false;
964                } else if (mVoiceButtonDown && !mVoiceButtonHandled && isLongPress) {
965                    mVoiceButtonHandled = true;
966                    startVoiceInput(needWakeLock);
967                }
968            } else if (action == KeyEvent.ACTION_UP) {
969                if (mVoiceButtonDown) {
970                    mVoiceButtonDown = false;
971                    if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
972                        // Resend the down then send this event through
973                        KeyEvent downEvent = KeyEvent.changeAction(keyEvent, KeyEvent.ACTION_DOWN);
974                        dispatchMediaKeyEventLocked(downEvent, needWakeLock, session);
975                        dispatchMediaKeyEventLocked(keyEvent, needWakeLock, session);
976                    }
977                }
978            }
979        }
980
981        private void dispatchMediaKeyEventLocked(KeyEvent keyEvent, boolean needWakeLock,
982                MediaSessionRecord session) {
983            if (session != null) {
984                if (DEBUG) {
985                    Log.d(TAG, "Sending media key to " + session.getSessionInfo());
986                }
987                if (needWakeLock) {
988                    mKeyEventReceiver.aquireWakeLockLocked();
989                }
990                // If we don't need a wakelock use -1 as the id so we
991                // won't release it later
992                session.sendMediaButton(keyEvent,
993                        needWakeLock ? mKeyEventReceiver.mLastTimeoutId : -1,
994                        mKeyEventReceiver);
995            } else {
996                if (needWakeLock) {
997                    mMediaEventWakeLock.acquire();
998                }
999                if (DEBUG) {
1000                    Log.d(TAG, "Sending media key ordered broadcast");
1001                }
1002                // Fallback to legacy behavior
1003                Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
1004                keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
1005                if (needWakeLock) {
1006                    keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED,
1007                            WAKELOCK_RELEASE_ON_FINISHED);
1008                }
1009                getContext().sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
1010                        null, mKeyEventDone, mHandler, Activity.RESULT_OK, null, null);
1011            }
1012        }
1013
1014        private void startVoiceInput(boolean needWakeLock) {
1015            Intent voiceIntent = null;
1016            // select which type of search to launch:
1017            // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1018            // - device locked or screen off: action is
1019            // ACTION_VOICE_SEARCH_HANDS_FREE
1020            // with EXTRA_SECURE set to true if the device is securely locked
1021            PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
1022            boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1023            if (!isLocked && pm.isScreenOn()) {
1024                voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1025                Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1026            } else {
1027                voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1028                voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1029                        isLocked && mKeyguardManager.isKeyguardSecure());
1030                Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1031            }
1032            // start the search activity
1033            if (needWakeLock) {
1034                mMediaEventWakeLock.acquire();
1035            }
1036            try {
1037                if (voiceIntent != null) {
1038                    voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1039                            | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
1040                    getContext().startActivityAsUser(voiceIntent, UserHandle.CURRENT);
1041                }
1042            } catch (ActivityNotFoundException e) {
1043                Log.w(TAG, "No activity for search: " + e);
1044            } finally {
1045                if (needWakeLock) {
1046                    mMediaEventWakeLock.release();
1047                }
1048            }
1049        }
1050
1051        private boolean isVoiceKey(int keyCode) {
1052            return keyCode == KeyEvent.KEYCODE_HEADSETHOOK;
1053        }
1054
1055        private KeyEventWakeLockReceiver mKeyEventReceiver = new KeyEventWakeLockReceiver(mHandler);
1056
1057        class KeyEventWakeLockReceiver extends ResultReceiver implements Runnable {
1058            private final Handler mHandler;
1059            private int mRefCount = 0;
1060            private int mLastTimeoutId = 0;
1061
1062            public KeyEventWakeLockReceiver(Handler handler) {
1063                super(handler);
1064                mHandler = handler;
1065            }
1066
1067            public void onTimeout() {
1068                synchronized (mLock) {
1069                    if (mRefCount == 0) {
1070                        // We've already released it, so just return
1071                        return;
1072                    }
1073                    mLastTimeoutId++;
1074                    mRefCount = 0;
1075                    releaseWakeLockLocked();
1076                }
1077            }
1078
1079            public void aquireWakeLockLocked() {
1080                if (mRefCount == 0) {
1081                    mMediaEventWakeLock.acquire();
1082                }
1083                mRefCount++;
1084                mHandler.removeCallbacks(this);
1085                mHandler.postDelayed(this, WAKELOCK_TIMEOUT);
1086
1087            }
1088
1089            @Override
1090            public void run() {
1091                onTimeout();
1092            }
1093
1094            @Override
1095            protected void onReceiveResult(int resultCode, Bundle resultData) {
1096                if (resultCode < mLastTimeoutId) {
1097                    // Ignore results from calls that were before the last
1098                    // timeout, just in case.
1099                    return;
1100                } else {
1101                    synchronized (mLock) {
1102                        if (mRefCount > 0) {
1103                            mRefCount--;
1104                            if (mRefCount == 0) {
1105                                releaseWakeLockLocked();
1106                            }
1107                        }
1108                    }
1109                }
1110            }
1111
1112            private void releaseWakeLockLocked() {
1113                mMediaEventWakeLock.release();
1114                mHandler.removeCallbacks(this);
1115            }
1116        };
1117
1118        BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1119            @Override
1120            public void onReceive(Context context, Intent intent) {
1121                if (intent == null) {
1122                    return;
1123                }
1124                Bundle extras = intent.getExtras();
1125                if (extras == null) {
1126                    return;
1127                }
1128                synchronized (mLock) {
1129                    if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)
1130                            && mMediaEventWakeLock.isHeld()) {
1131                        mMediaEventWakeLock.release();
1132                    }
1133                }
1134            }
1135        };
1136    }
1137
1138    final class MessageHandler extends Handler {
1139        private static final int MSG_SESSIONS_CHANGED = 1;
1140
1141        @Override
1142        public void handleMessage(Message msg) {
1143            switch (msg.what) {
1144                case MSG_SESSIONS_CHANGED:
1145                    pushSessionsChanged(msg.arg1);
1146                    break;
1147            }
1148        }
1149
1150        public void post(int what, int arg1, int arg2) {
1151            obtainMessage(what, arg1, arg2).sendToTarget();
1152        }
1153    }
1154}
1155