MediaSessionRecord.java revision 07c7077c54717dbbf2c401ea32d00fa6df6d77c6
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.content.Intent;
20import android.media.routeprovider.RouteRequest;
21import android.media.session.ISessionController;
22import android.media.session.ISessionControllerCallback;
23import android.media.session.ISession;
24import android.media.session.ISessionCallback;
25import android.media.session.SessionController;
26import android.media.session.MediaMetadata;
27import android.media.session.RouteCommand;
28import android.media.session.RouteInfo;
29import android.media.session.RouteOptions;
30import android.media.session.RouteEvent;
31import android.media.session.Session;
32import android.media.session.SessionInfo;
33import android.media.session.RouteInterface;
34import android.media.session.PlaybackState;
35import android.media.Rating;
36import android.os.Bundle;
37import android.os.Handler;
38import android.os.IBinder;
39import android.os.Looper;
40import android.os.Message;
41import android.os.RemoteException;
42import android.os.ResultReceiver;
43import android.text.TextUtils;
44import android.util.Log;
45import android.util.Pair;
46import android.util.Slog;
47import android.view.KeyEvent;
48
49import java.util.ArrayList;
50import java.util.List;
51import java.util.UUID;
52
53/**
54 * This is the system implementation of a Session. Apps will interact with the
55 * MediaSession wrapper class instead.
56 */
57public class MediaSessionRecord implements IBinder.DeathRecipient {
58    private static final String TAG = "MediaSessionRecord";
59
60    private final MessageHandler mHandler;
61
62    private final int mPid;
63    private final SessionInfo mSessionInfo;
64    private final String mTag;
65    private final ControllerStub mController;
66    private final SessionStub mSession;
67    private final SessionCb mSessionCb;
68    private final MediaSessionService mService;
69
70    private final Object mLock = new Object();
71    private final ArrayList<ISessionControllerCallback> mControllerCallbacks =
72            new ArrayList<ISessionControllerCallback>();
73    private final ArrayList<RouteRequest> mRequests = new ArrayList<RouteRequest>();
74
75    private boolean mTransportPerformerEnabled = false;
76    private RouteInfo mRoute;
77    private RouteOptions mRequest;
78    private RouteConnectionRecord mConnection;
79    // TODO define a RouteState class with relevant info
80    private int mRouteState;
81
82    // TransportPerformer fields
83
84    private MediaMetadata mMetadata;
85    private PlaybackState mPlaybackState;
86    private int mRatingType;
87    // End TransportPerformer fields
88
89    private boolean mIsPublished = false;
90
91    public MediaSessionRecord(int pid, String packageName, ISessionCallback cb, String tag,
92            MediaSessionService service, Handler handler) {
93        mPid = pid;
94        mSessionInfo = new SessionInfo(UUID.randomUUID().toString(), packageName);
95        mTag = tag;
96        mController = new ControllerStub();
97        mSession = new SessionStub();
98        mSessionCb = new SessionCb(cb);
99        mService = service;
100        mHandler = new MessageHandler(handler.getLooper());
101    }
102
103    /**
104     * Get the binder for the {@link Session}.
105     *
106     * @return The session binder apps talk to.
107     */
108    public ISession getSessionBinder() {
109        return mSession;
110    }
111
112    /**
113     * Get the binder for the {@link SessionController}.
114     *
115     * @return The controller binder apps talk to.
116     */
117    public ISessionController getControllerBinder() {
118        return mController;
119    }
120
121    /**
122     * Get the set of route requests this session is interested in.
123     *
124     * @return The list of RouteRequests
125     */
126    public List<RouteRequest> getRouteRequests() {
127        return mRequests;
128    }
129
130    /**
131     * Get the route this session is currently on.
132     *
133     * @return The route the session is on.
134     */
135    public RouteInfo getRoute() {
136        return mRoute;
137    }
138
139    /**
140     * Get the info for this session.
141     *
142     * @return Info that identifies this session.
143     */
144    public SessionInfo getSessionInfo() {
145        return mSessionInfo;
146    }
147
148    /**
149     * Set the selected route. This does not connect to the route, just notifies
150     * the app that a new route has been selected.
151     *
152     * @param route The route that was selected.
153     */
154    public void selectRoute(RouteInfo route) {
155        synchronized (mLock) {
156            if (route != mRoute) {
157                if (mConnection != null) {
158                    mConnection.disconnect();
159                    mConnection = null;
160                }
161            }
162            mRoute = route;
163        }
164        mSessionCb.sendRouteChange(route);
165    }
166
167    /**
168     * Update the state of the route this session is using and notify the
169     * session.
170     *
171     * @param state The new state of the route.
172     */
173    public void setRouteState(int state) {
174        mSessionCb.sendRouteStateChange(state);
175    }
176
177    /**
178     * Send an event to this session from the route it is using.
179     *
180     * @param event The event to send.
181     */
182    public void sendRouteEvent(RouteEvent event) {
183        mSessionCb.sendRouteEvent(event);
184    }
185
186    /**
187     * Set the connection to use for the selected route and notify the app it is
188     * now connected.
189     *
190     * @param route The route the connection is to.
191     * @param request The request that was used to connect.
192     * @param connection The connection to the route.
193     * @return True if this connection is still valid, false if it is stale.
194     */
195    public boolean setRouteConnected(RouteInfo route, RouteOptions request,
196            RouteConnectionRecord connection) {
197        synchronized (mLock) {
198            if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
199                Log.w(TAG, "setRouteConnected: connected route is stale");
200                // TODO figure out disconnection path
201                return false;
202            }
203            if (request != mRequest) {
204                Log.w(TAG, "setRouteConnected: connection request is stale");
205                // TODO figure out disconnection path
206                return false;
207            }
208            mConnection = connection;
209            mConnection.setListener(mConnectionListener);
210            mSessionCb.sendRouteConnected();
211        }
212        return true;
213    }
214
215    /**
216     * Check if this session has been published by the app yet.
217     *
218     * @return True if it has been published, false otherwise.
219     */
220    public boolean isPublished() {
221        return mIsPublished;
222    }
223
224    @Override
225    public void binderDied() {
226        mService.sessionDied(this);
227    }
228
229    private void onDestroy() {
230        mService.destroySession(this);
231    }
232
233    private void pushPlaybackStateUpdate() {
234        synchronized (mLock) {
235            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
236                ISessionControllerCallback cb = mControllerCallbacks.get(i);
237                try {
238                    cb.onPlaybackStateChanged(mPlaybackState);
239                } catch (RemoteException e) {
240                    Log.w(TAG, "Removing dead callback in pushPlaybackStateUpdate.", e);
241                    mControllerCallbacks.remove(i);
242                }
243            }
244        }
245    }
246
247    private void pushMetadataUpdate() {
248        synchronized (mLock) {
249            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
250                ISessionControllerCallback cb = mControllerCallbacks.get(i);
251                try {
252                    cb.onMetadataChanged(mMetadata);
253                } catch (RemoteException e) {
254                    Log.w(TAG, "Removing dead callback in pushMetadataUpdate.", e);
255                    mControllerCallbacks.remove(i);
256                }
257            }
258        }
259    }
260
261    private void pushRouteUpdate() {
262        synchronized (mLock) {
263            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
264                ISessionControllerCallback cb = mControllerCallbacks.get(i);
265                try {
266                    cb.onRouteChanged(mRoute);
267                } catch (RemoteException e) {
268                    Log.w(TAG, "Removing dead callback in pushRouteUpdate.", e);
269                    mControllerCallbacks.remove(i);
270                }
271            }
272        }
273    }
274
275    private void pushEvent(String event, Bundle data) {
276        synchronized (mLock) {
277            for (int i = mControllerCallbacks.size() - 1; i >= 0; i--) {
278                ISessionControllerCallback cb = mControllerCallbacks.get(i);
279                try {
280                    cb.onEvent(event, data);
281                } catch (RemoteException e) {
282                    Log.w(TAG, "Error with callback in pushEvent.", e);
283                }
284            }
285        }
286    }
287
288    private void pushRouteCommand(RouteCommand command, ResultReceiver cb) {
289        synchronized (mLock) {
290            if (mRoute == null || !TextUtils.equals(command.getRouteInfo(), mRoute.getId())) {
291                if (cb != null) {
292                    cb.send(RouteInterface.RESULT_ROUTE_IS_STALE, null);
293                    return;
294                }
295            }
296            if (mConnection != null) {
297                mConnection.sendCommand(command, cb);
298            } else if (cb != null) {
299                cb.send(RouteInterface.RESULT_NOT_CONNECTED, null);
300            }
301        }
302    }
303
304    private final RouteConnectionRecord.Listener mConnectionListener
305            = new RouteConnectionRecord.Listener() {
306        @Override
307        public void onEvent(RouteEvent event) {
308            RouteEvent eventForSession = new RouteEvent(null, event.getIface(),
309                    event.getEvent(), event.getExtras());
310            mSessionCb.sendRouteEvent(eventForSession);
311        }
312
313        @Override
314        public void disconnect() {
315            // TODO
316        }
317    };
318
319    private final class SessionStub extends ISession.Stub {
320        @Override
321        public void destroy() {
322            onDestroy();
323        }
324
325        @Override
326        public void sendEvent(String event, Bundle data) {
327            mHandler.post(MessageHandler.MSG_SEND_EVENT, event, data);
328        }
329
330        @Override
331        public ISessionController getController() {
332            return mController;
333        }
334
335        @Override
336        public void publish() {
337            mIsPublished = true; // TODO push update to service
338        }
339        @Override
340        public void setTransportPerformerEnabled() {
341            mTransportPerformerEnabled = true;
342        }
343
344        @Override
345        public void setMetadata(MediaMetadata metadata) {
346            mMetadata = metadata;
347            mHandler.post(MessageHandler.MSG_UPDATE_METADATA);
348        }
349
350        @Override
351        public void setPlaybackState(PlaybackState state) {
352            mPlaybackState = state;
353            mHandler.post(MessageHandler.MSG_UPDATE_PLAYBACK_STATE);
354        }
355
356        @Override
357        public void setRatingType(int type) {
358            mRatingType = type;
359        }
360
361        @Override
362        public void sendRouteCommand(RouteCommand command, ResultReceiver cb) {
363            mHandler.post(MessageHandler.MSG_SEND_COMMAND,
364                    new Pair<RouteCommand, ResultReceiver>(command, cb));
365        }
366
367        @Override
368        public boolean setRoute(RouteInfo route) throws RemoteException {
369            // TODO decide if allowed to set route and if the route exists
370            return false;
371        }
372
373        @Override
374        public void connectToRoute(RouteInfo route, RouteOptions request)
375                throws RemoteException {
376            if (mRoute == null || !TextUtils.equals(route.getId(), mRoute.getId())) {
377                throw new RemoteException("RouteInfo does not match current route");
378            }
379            mService.connectToRoute(MediaSessionRecord.this, route, request);
380            mRequest = request;
381        }
382
383        @Override
384        public void setRouteOptions(List<RouteOptions> options) throws RemoteException {
385            mRequests.clear();
386            for (int i = options.size() - 1; i >= 0; i--) {
387                RouteRequest request = new RouteRequest(mSessionInfo, options.get(i),
388                        false);
389                mRequests.add(request);
390            }
391        }
392    }
393
394    class SessionCb {
395        private final ISessionCallback mCb;
396
397        public SessionCb(ISessionCallback cb) {
398            mCb = cb;
399        }
400
401        public void sendMediaButton(KeyEvent keyEvent) {
402            Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
403            mediaButtonIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
404            try {
405                mCb.onMediaButton(mediaButtonIntent);
406            } catch (RemoteException e) {
407                Slog.e(TAG, "Remote failure in sendMediaRequest.", e);
408            }
409        }
410
411        public void sendCommand(String command, Bundle extras, ResultReceiver cb) {
412            try {
413                mCb.onCommand(command, extras, cb);
414            } catch (RemoteException e) {
415                Slog.e(TAG, "Remote failure in sendCommand.", e);
416            }
417        }
418
419        public void sendRouteChange(RouteInfo route) {
420            try {
421                mCb.onRequestRouteChange(route);
422            } catch (RemoteException e) {
423                Slog.e(TAG, "Remote failure in sendRouteChange.", e);
424            }
425        }
426
427        public void sendRouteStateChange(int state) {
428            try {
429                mCb.onRouteStateChange(state);
430            } catch (RemoteException e) {
431                Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
432            }
433        }
434
435        public void sendRouteEvent(RouteEvent event) {
436            try {
437                mCb.onRouteEvent(event);
438            } catch (RemoteException e) {
439                Slog.e(TAG, "Remote failure in sendRouteEvent.", e);
440            }
441        }
442
443        public void sendRouteConnected() {
444            try {
445                mCb.onRouteConnected(mRoute, mRequest);
446            } catch (RemoteException e) {
447                Slog.e(TAG, "Remote failure in sendRouteStateChange.", e);
448            }
449        }
450
451        public void play() {
452            try {
453                mCb.onPlay();
454            } catch (RemoteException e) {
455                Slog.e(TAG, "Remote failure in play.", e);
456            }
457        }
458
459        public void pause() {
460            try {
461                mCb.onPause();
462            } catch (RemoteException e) {
463                Slog.e(TAG, "Remote failure in pause.", e);
464            }
465        }
466
467        public void stop() {
468            try {
469                mCb.onStop();
470            } catch (RemoteException e) {
471                Slog.e(TAG, "Remote failure in stop.", e);
472            }
473        }
474
475        public void next() {
476            try {
477                mCb.onNext();
478            } catch (RemoteException e) {
479                Slog.e(TAG, "Remote failure in next.", e);
480            }
481        }
482
483        public void previous() {
484            try {
485                mCb.onPrevious();
486            } catch (RemoteException e) {
487                Slog.e(TAG, "Remote failure in previous.", e);
488            }
489        }
490
491        public void fastForward() {
492            try {
493                mCb.onFastForward();
494            } catch (RemoteException e) {
495                Slog.e(TAG, "Remote failure in fastForward.", e);
496            }
497        }
498
499        public void rewind() {
500            try {
501                mCb.onRewind();
502            } catch (RemoteException e) {
503                Slog.e(TAG, "Remote failure in rewind.", e);
504            }
505        }
506
507        public void seekTo(long pos) {
508            try {
509                mCb.onSeekTo(pos);
510            } catch (RemoteException e) {
511                Slog.e(TAG, "Remote failure in seekTo.", e);
512            }
513        }
514
515        public void rate(Rating rating) {
516            try {
517                mCb.onRate(rating);
518            } catch (RemoteException e) {
519                Slog.e(TAG, "Remote failure in rate.", e);
520            }
521        }
522    }
523
524    class ControllerStub extends ISessionController.Stub {
525        @Override
526        public void sendCommand(String command, Bundle extras, ResultReceiver cb)
527                throws RemoteException {
528            mSessionCb.sendCommand(command, extras, cb);
529        }
530
531        @Override
532        public void sendMediaButton(KeyEvent mediaButtonIntent) {
533            mSessionCb.sendMediaButton(mediaButtonIntent);
534        }
535
536        @Override
537        public void registerCallbackListener(ISessionControllerCallback cb) {
538            synchronized (mLock) {
539                if (!mControllerCallbacks.contains(cb)) {
540                    mControllerCallbacks.add(cb);
541                }
542            }
543        }
544
545        @Override
546        public void unregisterCallbackListener(ISessionControllerCallback cb)
547                throws RemoteException {
548            synchronized (mLock) {
549                mControllerCallbacks.remove(cb);
550            }
551        }
552
553        @Override
554        public void play() throws RemoteException {
555            mSessionCb.play();
556        }
557
558        @Override
559        public void pause() throws RemoteException {
560            mSessionCb.pause();
561        }
562
563        @Override
564        public void stop() throws RemoteException {
565            mSessionCb.stop();
566        }
567
568        @Override
569        public void next() throws RemoteException {
570            mSessionCb.next();
571        }
572
573        @Override
574        public void previous() throws RemoteException {
575            mSessionCb.previous();
576        }
577
578        @Override
579        public void fastForward() throws RemoteException {
580            mSessionCb.fastForward();
581        }
582
583        @Override
584        public void rewind() throws RemoteException {
585            mSessionCb.rewind();
586        }
587
588        @Override
589        public void seekTo(long pos) throws RemoteException {
590            mSessionCb.seekTo(pos);
591        }
592
593        @Override
594        public void rate(Rating rating) throws RemoteException {
595            mSessionCb.rate(rating);
596        }
597
598
599        @Override
600        public MediaMetadata getMetadata() {
601            return mMetadata;
602        }
603
604        @Override
605        public PlaybackState getPlaybackState() {
606            return mPlaybackState;
607        }
608
609        @Override
610        public int getRatingType() {
611            return mRatingType;
612        }
613
614        @Override
615        public boolean isTransportControlEnabled() {
616            return mTransportPerformerEnabled;
617        }
618
619        @Override
620        public void showRoutePicker() {
621            mService.showRoutePickerForSession(MediaSessionRecord.this);
622        }
623    }
624
625    private class MessageHandler extends Handler {
626        private static final int MSG_UPDATE_METADATA = 1;
627        private static final int MSG_UPDATE_PLAYBACK_STATE = 2;
628        private static final int MSG_UPDATE_ROUTE = 3;
629        private static final int MSG_SEND_EVENT = 4;
630        private static final int MSG_UPDATE_ROUTE_FILTERS = 5;
631        private static final int MSG_SEND_COMMAND = 6;
632
633        public MessageHandler(Looper looper) {
634            super(looper);
635        }
636        @Override
637        public void handleMessage(Message msg) {
638            switch (msg.what) {
639                case MSG_UPDATE_METADATA:
640                    pushMetadataUpdate();
641                    break;
642                case MSG_UPDATE_PLAYBACK_STATE:
643                    pushPlaybackStateUpdate();
644                    break;
645                case MSG_UPDATE_ROUTE:
646                    pushRouteUpdate();
647                    break;
648                case MSG_SEND_EVENT:
649                    pushEvent((String) msg.obj, msg.getData());
650                    break;
651                case MSG_SEND_COMMAND:
652                    Pair<RouteCommand, ResultReceiver> cmd =
653                            (Pair<RouteCommand, ResultReceiver>) msg.obj;
654                    pushRouteCommand(cmd.first, cmd.second);
655                    break;
656            }
657        }
658
659        public void post(int what) {
660            post(what, null);
661        }
662
663        public void post(int what, Object obj) {
664            obtainMessage(what, obj).sendToTarget();
665        }
666
667        public void post(int what, Object obj, Bundle data) {
668            Message msg = obtainMessage(what, obj);
669            msg.setData(data);
670            msg.sendToTarget();
671        }
672    }
673
674}
675