SampleMediaRouteProvider.java revision 5d429bc3a8195d6f37cf2f7da0935972950539b4
1/*
2 * Copyright (C) 2013 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.example.android.supportv7.media;
18
19import com.example.android.supportv7.R;
20
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.IntentFilter.MalformedMimeTypeException;
25import android.content.IntentSender;
26import android.content.res.Resources;
27import android.graphics.Bitmap;
28import android.media.AudioManager;
29import android.media.MediaRouter;
30import android.net.Uri;
31import android.os.Bundle;
32import android.app.PendingIntent;
33import android.support.v7.media.MediaControlIntent;
34import android.support.v7.media.MediaItemStatus;
35import android.support.v7.media.MediaRouteProvider;
36import android.support.v7.media.MediaRouter.ControlRequestCallback;
37import android.support.v7.media.MediaRouteProviderDescriptor;
38import android.support.v7.media.MediaRouteDescriptor;
39import android.support.v7.media.MediaSessionStatus;
40import android.util.Log;
41import android.view.Gravity;
42import android.view.Surface;
43import android.view.SurfaceHolder;
44
45import java.util.ArrayList;
46
47/**
48 * Demonstrates how to create a custom media route provider.
49 *
50 * @see SampleMediaRouteProviderService
51 */
52final class SampleMediaRouteProvider extends MediaRouteProvider {
53    private static final String TAG = "SampleMediaRouteProvider";
54
55    private static final String FIXED_VOLUME_ROUTE_ID = "fixed";
56    private static final String VARIABLE_VOLUME_BASIC_ROUTE_ID = "variable_basic";
57    private static final String VARIABLE_VOLUME_QUEUING_ROUTE_ID = "variable_queuing";
58    private static final String VARIABLE_VOLUME_SESSION_ROUTE_ID = "variable_session";
59    private static final int VOLUME_MAX = 10;
60
61    /**
62     * A custom media control intent category for special requests that are
63     * supported by this provider's routes.
64     */
65    public static final String CATEGORY_SAMPLE_ROUTE =
66            "com.example.android.supportv7.media.CATEGORY_SAMPLE_ROUTE";
67
68    /**
69     * A custom media control intent action for special requests that are
70     * supported by this provider's routes.
71     * </p>
72     *
73     * @see #TRACK_INFO_DESC
74     * @see #TRACK_INFO_SNAPSHOT
75     */
76    public static final String ACTION_GET_TRACK_INFO =
77            "com.example.android.supportv7.media.ACTION_GET_TRACK_INFO";
78
79    /**
80     * {@link #ACTION_GET_TRACK_INFO} result data: a string of information about
81     * the currently playing media item
82     */
83    public static final String TRACK_INFO_DESC =
84            "com.example.android.supportv7.media.EXTRA_TRACK_INFO_DESC";
85
86    /**
87     * {@link #ACTION_GET_TRACK_INFO} result data: a bitmap containing a snapshot
88     * of the currently playing media item
89     */
90    public static final String TRACK_INFO_SNAPSHOT =
91            "com.example.android.supportv7.media.EXTRA_TRACK_INFO_SNAPSHOT";
92
93    private static final ArrayList<IntentFilter> CONTROL_FILTERS_BASIC;
94    private static final ArrayList<IntentFilter> CONTROL_FILTERS_QUEUING;
95    private static final ArrayList<IntentFilter> CONTROL_FILTERS_SESSION;
96
97    static {
98        IntentFilter f1 = new IntentFilter();
99        f1.addCategory(CATEGORY_SAMPLE_ROUTE);
100        f1.addAction(ACTION_GET_TRACK_INFO);
101
102        IntentFilter f2 = new IntentFilter();
103        f2.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
104        f2.addAction(MediaControlIntent.ACTION_PLAY);
105        f2.addDataScheme("http");
106        f2.addDataScheme("https");
107        f2.addDataScheme("rtsp");
108        f2.addDataScheme("file");
109        addDataTypeUnchecked(f2, "video/*");
110
111        IntentFilter f3 = new IntentFilter();
112        f3.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
113        f3.addAction(MediaControlIntent.ACTION_SEEK);
114        f3.addAction(MediaControlIntent.ACTION_GET_STATUS);
115        f3.addAction(MediaControlIntent.ACTION_PAUSE);
116        f3.addAction(MediaControlIntent.ACTION_RESUME);
117        f3.addAction(MediaControlIntent.ACTION_STOP);
118
119        IntentFilter f4 = new IntentFilter();
120        f4.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
121        f4.addAction(MediaControlIntent.ACTION_ENQUEUE);
122        f4.addDataScheme("http");
123        f4.addDataScheme("https");
124        f4.addDataScheme("rtsp");
125        f4.addDataScheme("file");
126        addDataTypeUnchecked(f4, "video/*");
127
128        IntentFilter f5 = new IntentFilter();
129        f5.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
130        f5.addAction(MediaControlIntent.ACTION_REMOVE);
131
132        IntentFilter f6 = new IntentFilter();
133        f6.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
134        f6.addAction(MediaControlIntent.ACTION_START_SESSION);
135        f6.addAction(MediaControlIntent.ACTION_GET_SESSION_STATUS);
136        f6.addAction(MediaControlIntent.ACTION_END_SESSION);
137
138        CONTROL_FILTERS_BASIC = new ArrayList<IntentFilter>();
139        CONTROL_FILTERS_BASIC.add(f1);
140        CONTROL_FILTERS_BASIC.add(f2);
141        CONTROL_FILTERS_BASIC.add(f3);
142
143        CONTROL_FILTERS_QUEUING =
144                new ArrayList<IntentFilter>(CONTROL_FILTERS_BASIC);
145        CONTROL_FILTERS_QUEUING.add(f4);
146        CONTROL_FILTERS_QUEUING.add(f5);
147
148        CONTROL_FILTERS_SESSION =
149                new ArrayList<IntentFilter>(CONTROL_FILTERS_QUEUING);
150        CONTROL_FILTERS_SESSION.add(f6);
151    }
152
153    private static void addDataTypeUnchecked(IntentFilter filter, String type) {
154        try {
155            filter.addDataType(type);
156        } catch (MalformedMimeTypeException ex) {
157            throw new RuntimeException(ex);
158        }
159    }
160
161    private int mVolume = 5;
162    private int mEnqueueCount;
163
164    public SampleMediaRouteProvider(Context context) {
165        super(context);
166
167        publishRoutes();
168    }
169
170    @Override
171    public RouteController onCreateRouteController(String routeId) {
172        return new SampleRouteController(routeId);
173    }
174
175    private void publishRoutes() {
176        Resources r = getContext().getResources();
177        Intent settingsIntent = new Intent(Intent.ACTION_MAIN);
178        settingsIntent.setClass(getContext(), SampleMediaRouteSettingsActivity.class);
179        IntentSender is = PendingIntent.getActivity(getContext(), 99, settingsIntent,
180                Intent.FLAG_ACTIVITY_NEW_TASK).getIntentSender();
181
182        MediaRouteDescriptor routeDescriptor1 = new MediaRouteDescriptor.Builder(
183                FIXED_VOLUME_ROUTE_ID,
184                r.getString(R.string.fixed_volume_route_name))
185                .setDescription(r.getString(R.string.sample_route_description))
186                .addControlFilters(CONTROL_FILTERS_BASIC)
187                .setPlaybackStream(AudioManager.STREAM_MUSIC)
188                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
189                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED)
190                .setVolume(VOLUME_MAX)
191                .setCanDisconnect(true)
192                .setSettingsActivity(is)
193                .build();
194
195        MediaRouteDescriptor routeDescriptor2 = new MediaRouteDescriptor.Builder(
196                VARIABLE_VOLUME_BASIC_ROUTE_ID,
197                r.getString(R.string.variable_volume_basic_route_name))
198                .setDescription(r.getString(R.string.sample_route_description))
199                .addControlFilters(CONTROL_FILTERS_BASIC)
200                .setPlaybackStream(AudioManager.STREAM_MUSIC)
201                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
202                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
203                .setVolumeMax(VOLUME_MAX)
204                .setVolume(mVolume)
205                .setSettingsActivity(is)
206                .build();
207
208        MediaRouteDescriptor routeDescriptor3 = new MediaRouteDescriptor.Builder(
209                VARIABLE_VOLUME_QUEUING_ROUTE_ID,
210                r.getString(R.string.variable_volume_queuing_route_name))
211                .setDescription(r.getString(R.string.sample_route_description))
212                .addControlFilters(CONTROL_FILTERS_QUEUING)
213                .setPlaybackStream(AudioManager.STREAM_MUSIC)
214                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
215                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
216                .setVolumeMax(VOLUME_MAX)
217                .setVolume(mVolume)
218                .setCanDisconnect(true)
219                .build();
220
221        MediaRouteDescriptor routeDescriptor4 = new MediaRouteDescriptor.Builder(
222                VARIABLE_VOLUME_SESSION_ROUTE_ID,
223                r.getString(R.string.variable_volume_session_route_name))
224                .setDescription(r.getString(R.string.sample_route_description))
225                .addControlFilters(CONTROL_FILTERS_SESSION)
226                .setPlaybackStream(AudioManager.STREAM_MUSIC)
227                .setPlaybackType(MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE)
228                .setVolumeHandling(MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE)
229                .setVolumeMax(VOLUME_MAX)
230                .setVolume(mVolume)
231                .build();
232
233        MediaRouteProviderDescriptor providerDescriptor =
234                new MediaRouteProviderDescriptor.Builder()
235                .addRoute(routeDescriptor1)
236                .addRoute(routeDescriptor2)
237                .addRoute(routeDescriptor3)
238                .addRoute(routeDescriptor4)
239                .build();
240        setDescriptor(providerDescriptor);
241    }
242
243    private final class SampleRouteController extends MediaRouteProvider.RouteController {
244        private final String mRouteId;
245        private final SessionManager mSessionManager = new SessionManager("mrp");
246        private final Player mPlayer;
247        private PendingIntent mSessionReceiver;
248
249        public SampleRouteController(String routeId) {
250            mRouteId = routeId;
251            mPlayer = Player.create(getContext(), null);
252            mSessionManager.setPlayer(mPlayer);
253            mSessionManager.setCallback(new SessionManager.Callback() {
254                @Override
255                public void onStatusChanged() {
256                }
257
258                @Override
259                public void onItemChanged(PlaylistItem item) {
260                    handleStatusChange(item);
261                }
262            });
263            setVolumeInternal(mVolume);
264            Log.d(TAG, mRouteId + ": Controller created");
265        }
266
267        @Override
268        public void onRelease() {
269            Log.d(TAG, mRouteId + ": Controller released");
270            mPlayer.release();
271        }
272
273        @Override
274        public void onSelect() {
275            Log.d(TAG, mRouteId + ": Selected");
276            mPlayer.connect(null);
277        }
278
279        @Override
280        public void onUnselect() {
281            Log.d(TAG, mRouteId + ": Unselected");
282            mPlayer.release();
283        }
284
285        @Override
286        public void onSetVolume(int volume) {
287            Log.d(TAG, mRouteId + ": Set volume to " + volume);
288            if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) {
289                setVolumeInternal(volume);
290            }
291        }
292
293        @Override
294        public void onUpdateVolume(int delta) {
295            Log.d(TAG, mRouteId + ": Update volume by " + delta);
296            if (!mRouteId.equals(FIXED_VOLUME_ROUTE_ID)) {
297                setVolumeInternal(mVolume + delta);
298            }
299        }
300
301        @Override
302        public boolean onControlRequest(Intent intent, ControlRequestCallback callback) {
303            Log.d(TAG, mRouteId + ": Received control request " + intent);
304            String action = intent.getAction();
305            if (intent.hasCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
306                boolean success = false;
307                if (action.equals(MediaControlIntent.ACTION_PLAY)) {
308                    success = handlePlay(intent, callback);
309                } else if (action.equals(MediaControlIntent.ACTION_ENQUEUE)) {
310                    success = handleEnqueue(intent, callback);
311                } else if (action.equals(MediaControlIntent.ACTION_REMOVE)) {
312                    success = handleRemove(intent, callback);
313                } else if (action.equals(MediaControlIntent.ACTION_SEEK)) {
314                    success = handleSeek(intent, callback);
315                } else if (action.equals(MediaControlIntent.ACTION_GET_STATUS)) {
316                    success = handleGetStatus(intent, callback);
317                } else if (action.equals(MediaControlIntent.ACTION_PAUSE)) {
318                    success = handlePause(intent, callback);
319                } else if (action.equals(MediaControlIntent.ACTION_RESUME)) {
320                    success = handleResume(intent, callback);
321                } else if (action.equals(MediaControlIntent.ACTION_STOP)) {
322                    success = handleStop(intent, callback);
323                } else if (action.equals(MediaControlIntent.ACTION_START_SESSION)) {
324                    success = handleStartSession(intent, callback);
325                } else if (action.equals(MediaControlIntent.ACTION_GET_SESSION_STATUS)) {
326                    success = handleGetSessionStatus(intent, callback);
327                } else if (action.equals(MediaControlIntent.ACTION_END_SESSION)) {
328                    success = handleEndSession(intent, callback);
329                }
330                Log.d(TAG, mSessionManager.toString());
331                return success;
332            }
333
334            if (action.equals(ACTION_GET_TRACK_INFO)
335                    && intent.hasCategory(CATEGORY_SAMPLE_ROUTE)) {
336                Bundle data = new Bundle();
337                PlaylistItem item = mSessionManager.getCurrentItem();
338                if (item != null) {
339                    data.putString(TRACK_INFO_DESC, item.toString());
340                    data.putParcelable(TRACK_INFO_SNAPSHOT, mPlayer.getSnapshot());
341                }
342                if (callback != null) {
343                    callback.onResult(data);
344                }
345                return true;
346            }
347            return false;
348        }
349
350        private void setVolumeInternal(int volume) {
351            if (volume >= 0 && volume <= VOLUME_MAX) {
352                mVolume = volume;
353                Log.d(TAG, mRouteId + ": New volume is " + mVolume);
354                AudioManager audioManager =
355                        (AudioManager)getContext().getSystemService(Context.AUDIO_SERVICE);
356                audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
357                publishRoutes();
358            }
359        }
360
361        private boolean handlePlay(Intent intent, ControlRequestCallback callback) {
362            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
363            if (sid != null && !sid.equals(mSessionManager.getSessionId())) {
364                Log.d(TAG, "handlePlay fails because of bad sid="+sid);
365                return false;
366            }
367            if (mSessionManager.hasSession()) {
368                mSessionManager.stop();
369            }
370            return handleEnqueue(intent, callback);
371        }
372
373        private boolean handleEnqueue(Intent intent, ControlRequestCallback callback) {
374            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
375            if (sid != null && !sid.equals(mSessionManager.getSessionId())) {
376                Log.d(TAG, "handleEnqueue fails because of bad sid="+sid);
377                return false;
378            }
379
380            Uri uri = intent.getData();
381            if (uri == null) {
382                Log.d(TAG, "handleEnqueue fails because of bad uri="+uri);
383                return false;
384            }
385
386            boolean enqueue = intent.getAction().equals(MediaControlIntent.ACTION_ENQUEUE);
387            String mime = intent.getType();
388            long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
389            Bundle metadata = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_METADATA);
390            Bundle headers = intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_HTTP_HEADERS);
391            PendingIntent receiver = (PendingIntent)intent.getParcelableExtra(
392                    MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER);
393
394            Log.d(TAG, mRouteId + ": Received " + (enqueue?"enqueue":"play") + " request"
395                    + ", uri=" + uri
396                    + ", mime=" + mime
397                    + ", sid=" + sid
398                    + ", pos=" + pos
399                    + ", metadata=" + metadata
400                    + ", headers=" + headers
401                    + ", receiver=" + receiver);
402            PlaylistItem item = mSessionManager.add(uri, mime, receiver);
403            if (callback != null) {
404                if (item != null) {
405                    Bundle result = new Bundle();
406                    result.putString(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
407                    result.putString(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
408                    result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
409                            item.getStatus().asBundle());
410                    callback.onResult(result);
411                } else {
412                    callback.onError("Failed to open " + uri.toString(), null);
413                }
414            }
415            mEnqueueCount +=1;
416            return true;
417        }
418
419        private boolean handleRemove(Intent intent, ControlRequestCallback callback) {
420            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
421            if (sid == null || !sid.equals(mSessionManager.getSessionId())) {
422                return false;
423            }
424
425            String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
426            PlaylistItem item = mSessionManager.remove(iid);
427            if (callback != null) {
428                if (item != null) {
429                    Bundle result = new Bundle();
430                    result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
431                            item.getStatus().asBundle());
432                    callback.onResult(result);
433                } else {
434                    callback.onError("Failed to remove" +
435                            ", sid=" + sid + ", iid=" + iid, null);
436                }
437            }
438            return (item != null);
439        }
440
441        private boolean handleSeek(Intent intent, ControlRequestCallback callback) {
442            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
443            if (sid == null || !sid.equals(mSessionManager.getSessionId())) {
444                return false;
445            }
446
447            String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
448            long pos = intent.getLongExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, 0);
449            Log.d(TAG, mRouteId + ": Received seek request, pos=" + pos);
450            PlaylistItem item = mSessionManager.seek(iid, pos);
451            if (callback != null) {
452                if (item != null) {
453                    Bundle result = new Bundle();
454                    result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
455                            item.getStatus().asBundle());
456                    callback.onResult(result);
457                } else {
458                    callback.onError("Failed to seek" +
459                            ", sid=" + sid + ", iid=" + iid + ", pos=" + pos, null);
460                }
461            }
462            return (item != null);
463        }
464
465        private boolean handleGetStatus(Intent intent, ControlRequestCallback callback) {
466            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
467            String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
468            Log.d(TAG, mRouteId + ": Received getStatus request, sid=" + sid + ", iid=" + iid);
469            PlaylistItem item = mSessionManager.getStatus(iid);
470            if (callback != null) {
471                if (item != null) {
472                    Bundle result = new Bundle();
473                    result.putBundle(MediaControlIntent.EXTRA_ITEM_STATUS,
474                            item.getStatus().asBundle());
475                    callback.onResult(result);
476                } else {
477                    callback.onError("Failed to get status" +
478                            ", sid=" + sid + ", iid=" + iid, null);
479                }
480            }
481            return (item != null);
482        }
483
484        private boolean handlePause(Intent intent, ControlRequestCallback callback) {
485            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
486            boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
487            mSessionManager.pause();
488            if (callback != null) {
489                if (success) {
490                    callback.onResult(new Bundle());
491                    handleSessionStatusChange(sid);
492                } else {
493                    callback.onError("Failed to pause, sid=" + sid, null);
494                }
495            }
496            return success;
497        }
498
499        private boolean handleResume(Intent intent, ControlRequestCallback callback) {
500            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
501            boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
502            mSessionManager.resume();
503            if (callback != null) {
504                if (success) {
505                    callback.onResult(new Bundle());
506                    handleSessionStatusChange(sid);
507                } else {
508                    callback.onError("Failed to resume, sid=" + sid, null);
509                }
510            }
511            return success;
512        }
513
514        private boolean handleStop(Intent intent, ControlRequestCallback callback) {
515            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
516            boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId());
517            mSessionManager.stop();
518            if (callback != null) {
519                if (success) {
520                    callback.onResult(new Bundle());
521                    handleSessionStatusChange(sid);
522                } else {
523                    callback.onError("Failed to stop, sid=" + sid, null);
524                }
525            }
526            return success;
527        }
528
529        private boolean handleStartSession(Intent intent, ControlRequestCallback callback) {
530            String sid = mSessionManager.startSession();
531            Log.d(TAG, "StartSession returns sessionId "+sid);
532            if (callback != null) {
533                if (sid != null) {
534                    Bundle result = new Bundle();
535                    result.putString(MediaControlIntent.EXTRA_SESSION_ID, sid);
536                    result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
537                            mSessionManager.getSessionStatus(sid).asBundle());
538                    callback.onResult(result);
539                    mSessionReceiver = (PendingIntent)intent.getParcelableExtra(
540                            MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER);
541                    handleSessionStatusChange(sid);
542                } else {
543                    callback.onError("Failed to start session.", null);
544                }
545            }
546            return (sid != null);
547        }
548
549        private boolean handleGetSessionStatus(Intent intent, ControlRequestCallback callback) {
550            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
551
552            MediaSessionStatus sessionStatus = mSessionManager.getSessionStatus(sid);
553            if (callback != null) {
554                if (sessionStatus != null) {
555                    Bundle result = new Bundle();
556                    result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS,
557                            mSessionManager.getSessionStatus(sid).asBundle());
558                    callback.onResult(result);
559                } else {
560                    callback.onError("Failed to get session status, sid=" + sid, null);
561                }
562            }
563            return (sessionStatus != null);
564        }
565
566        private boolean handleEndSession(Intent intent, ControlRequestCallback callback) {
567            String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
568            boolean success = (sid != null) && sid.equals(mSessionManager.getSessionId())
569                    && mSessionManager.endSession();
570            if (callback != null) {
571                if (success) {
572                    Bundle result = new Bundle();
573                    MediaSessionStatus sessionStatus = new MediaSessionStatus.Builder(
574                            MediaSessionStatus.SESSION_STATE_ENDED).build();
575                    result.putBundle(MediaControlIntent.EXTRA_SESSION_STATUS, sessionStatus.asBundle());
576                    callback.onResult(result);
577                    handleSessionStatusChange(sid);
578                    mSessionReceiver = null;
579                } else {
580                    callback.onError("Failed to end session, sid=" + sid, null);
581                }
582            }
583            return success;
584        }
585
586        private void handleStatusChange(PlaylistItem item) {
587            if (item == null) {
588                item = mSessionManager.getCurrentItem();
589            }
590            if (item != null) {
591                PendingIntent receiver = item.getUpdateReceiver();
592                if (receiver != null) {
593                    Intent intent = new Intent();
594                    intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
595                    intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
596                    intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS,
597                            item.getStatus().asBundle());
598                    try {
599                        receiver.send(getContext(), 0, intent);
600                        Log.d(TAG, mRouteId + ": Sending status update from provider");
601                    } catch (PendingIntent.CanceledException e) {
602                        Log.d(TAG, mRouteId + ": Failed to send status update!");
603                    }
604                }
605            }
606        }
607
608        private void handleSessionStatusChange(String sid) {
609            if (mSessionReceiver != null) {
610                Intent intent = new Intent();
611                intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid);
612                intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS,
613                        mSessionManager.getSessionStatus(sid).asBundle());
614                try {
615                    mSessionReceiver.send(getContext(), 0, intent);
616                    Log.d(TAG, mRouteId + ": Sending session status update from provider");
617                } catch (PendingIntent.CanceledException e) {
618                    Log.d(TAG, mRouteId + ": Failed to send session status update!");
619                }
620            }
621        }
622    }
623}