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