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