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