SampleMediaRouterActivity.java revision 7b95dd38981772983f00c5ed91aca4b30b782fac
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.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.BroadcastReceiver;
26import android.content.res.Resources;
27import android.content.DialogInterface;
28import android.app.PendingIntent;
29import android.app.Presentation;
30import android.media.AudioManager;
31import android.media.AudioManager.OnAudioFocusChangeListener;
32import android.media.MediaMetadataRetriever;
33import android.media.RemoteControlClient;
34import android.net.Uri;
35import android.os.Build;
36import android.os.Handler;
37import android.os.Bundle;
38import android.os.SystemClock;
39import android.support.v4.app.FragmentManager;
40import android.support.v4.view.MenuItemCompat;
41import android.support.v7.app.ActionBarActivity;
42import android.support.v7.app.MediaRouteActionProvider;
43import android.support.v7.app.MediaRouteDiscoveryFragment;
44import android.support.v7.media.MediaControlIntent;
45import android.support.v7.media.MediaRouter;
46import android.support.v7.media.MediaRouter.Callback;
47import android.support.v7.media.MediaRouter.RouteInfo;
48import android.support.v7.media.MediaRouter.ProviderInfo;
49import android.support.v7.media.MediaRouteSelector;
50import android.support.v7.media.MediaItemStatus;
51import android.util.Log;
52import android.view.Gravity;
53import android.view.KeyEvent;
54import android.view.Menu;
55import android.view.MenuItem;
56import android.view.View;
57import android.view.View.OnClickListener;
58import android.view.ViewGroup;
59import android.view.Display;
60import android.view.SurfaceView;
61import android.view.SurfaceHolder;
62import android.view.WindowManager;
63import android.widget.AdapterView;
64import android.widget.AdapterView.OnItemClickListener;
65import android.widget.ArrayAdapter;
66import android.widget.ImageButton;
67import android.widget.ListView;
68import android.widget.TextView;
69import android.widget.Toast;
70import android.widget.FrameLayout;
71import android.widget.TabHost;
72import android.widget.TabHost.TabSpec;
73import android.widget.TabHost.OnTabChangeListener;
74import android.widget.SeekBar;
75import android.widget.SeekBar.OnSeekBarChangeListener;
76
77
78import java.io.File;
79
80/**
81 * <h3>Media Router Support Activity</h3>
82 *
83 * <p>
84 * This demonstrates how to use the {@link MediaRouter} API to build an
85 * application that allows the user to send content to various rendering
86 * targets.
87 * </p>
88 */
89public class SampleMediaRouterActivity extends ActionBarActivity {
90    private static final String TAG = "MediaRouterSupport";
91    private static final String DISCOVERY_FRAGMENT_TAG = "DiscoveryFragment";
92    private static final String ACTION_STATUS_CHANGE =
93            "com.example.android.supportv7.media.ACTION_STATUS_CHANGE";
94
95    private MediaRouter mMediaRouter;
96    private MediaRouteSelector mSelector;
97    private LibraryAdapter mLibraryItems;
98    private PlaylistAdapter mPlayListItems;
99    private TextView mInfoTextView;
100    private ListView mLibraryView;
101    private ListView mPlayListView;
102    private ImageButton mPauseResumeButton;
103    private ImageButton mStopButton;
104    private SeekBar mSeekBar;
105    private String mStatsInfo;
106    private boolean mPaused;
107    private boolean mNeedResume;
108    private boolean mSeeking;
109    private long mLastStatusTime;
110    private PlaylistAdapter mSavedPlaylist;
111
112    private final Handler mHandler = new Handler();
113    private final Runnable mUpdateSeekRunnable = new Runnable() {
114        @Override
115        public void run() {
116            updateProgress(getCheckedMediaQueueItem());
117            // update Ui every 1 second
118            mHandler.postDelayed(this, 1000);
119        }
120    };
121
122    private final MediaPlayerWrapper mMediaPlayer = new MediaPlayerWrapper(this);
123    private final MediaPlayerWrapper.Callback mMediaPlayerCB =
124            new MediaPlayerWrapper.Callback()  {
125        @Override
126        public void onError() {
127            mPlayer.onFinish(true);
128        }
129
130        @Override
131        public void onCompletion() {
132            mPlayer.onFinish(false);
133        }
134
135        @Override
136        public void onSizeChanged(int width, int height) {
137            mPlayer.updateSize(width, height);
138        }
139
140        @Override
141        public void onStatusChanged() {
142            if (!mSeeking) {
143                updateUi();
144            }
145        }
146    };
147
148    private final RemotePlayer mRemotePlayer = new RemotePlayer();
149    private final LocalPlayer mLocalPlayer = new LocalPlayer();
150    private Player mPlayer;
151    private MediaSessionManager.Callback mPlayerCB;
152
153    private final MediaRouter.Callback mMediaRouterCB = new MediaRouter.Callback() {
154        // Return a custom callback that will simply log all of the route events
155        // for demonstration purposes.
156        @Override
157        public void onRouteAdded(MediaRouter router, RouteInfo route) {
158            Log.d(TAG, "onRouteAdded: route=" + route);
159        }
160
161        @Override
162        public void onRouteChanged(MediaRouter router, RouteInfo route) {
163            Log.d(TAG, "onRouteChanged: route=" + route);
164            mPlayer.showStatistics();
165        }
166
167        @Override
168        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
169            Log.d(TAG, "onRouteRemoved: route=" + route);
170        }
171
172        @Override
173        public void onRouteSelected(MediaRouter router, RouteInfo route) {
174            Log.d(TAG, "onRouteSelected: route=" + route);
175
176            Player player = mPlayer;
177            MediaSessionManager.Callback playerCB = mPlayerCB;
178
179            if (route.supportsControlCategory(
180                    MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
181                Intent enqueueIntent = new Intent(MediaControlIntent.ACTION_ENQUEUE);
182                enqueueIntent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
183                enqueueIntent.setDataAndType(Uri.parse("http://"), "video/mp4");
184
185                Intent removeIntent = new Intent(MediaControlIntent.ACTION_REMOVE);
186                removeIntent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
187
188                // Remote Playback:
189                //   If route supports remote queuing, let it manage the queue;
190                //   otherwise, manage the queue locally and feed it one item at a time
191                if (route.supportsControlRequest(enqueueIntent)
192                 && route.supportsControlRequest(removeIntent)) {
193                    player = mRemotePlayer;
194                } else {
195                    player = mLocalPlayer;
196                }
197                playerCB = mRemotePlayer;
198                mRemotePlayer.reset();
199
200            } else {
201                // Local Playback:
202                //   Use local player and feed media player one item at a time
203                player = mLocalPlayer;
204                playerCB = mMediaPlayer;
205            }
206
207            if (player != mPlayer || playerCB != mPlayerCB) {
208                // save current playlist
209                PlaylistAdapter playlist = new PlaylistAdapter();
210                for (int i = 0; i < mPlayListItems.getCount(); i++) {
211                    MediaQueueItem item = mPlayListItems.getItem(i);
212                    if (item.getState() == MediaItemStatus.PLAYBACK_STATE_PLAYING
213                            || item.getState() == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
214                        long position = item.getContentPosition();
215                        long timeDelta = mPaused ? 0 :
216                                (SystemClock.elapsedRealtime() - mLastStatusTime);
217                        item.setContentPosition(position + timeDelta);
218                    }
219                    playlist.add(item);
220                }
221
222                // switch players
223                mPlayer.stop();
224                mPaused = false;
225                mLocalPlayer.setCallback(playerCB);
226                mPlayerCB = playerCB;
227                mPlayer = player;
228                mPlayer.showStatistics();
229                mLocalPlayer.updatePresentation();
230
231                // migrate playlist to new route
232                int count = playlist.getCount();
233                if (isRemoteQueue()) {
234                    // if queuing is managed remotely, only enqueue the first
235                    // item, as we need to have the returned session id to
236                    // enqueue the rest of the playlist items
237                    mSavedPlaylist = playlist;
238                    count = 1;
239                }
240                for (int i = 0; i < count; i++) {
241                    final MediaQueueItem item = playlist.getItem(i);
242                    mPlayer.enqueue(item.getUri(), item.getContentPosition());
243                }
244            }
245            updateUi();
246        }
247
248        @Override
249        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
250            Log.d(TAG, "onRouteUnselected: route=" + route);
251            mPlayer.showStatistics();
252        }
253
254        @Override
255        public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
256            Log.d(TAG, "onRouteVolumeChanged: route=" + route);
257        }
258
259        @Override
260        public void onRoutePresentationDisplayChanged(
261                MediaRouter router, RouteInfo route) {
262            Log.d(TAG, "onRoutePresentationDisplayChanged: route=" + route);
263        }
264
265        @Override
266        public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
267            Log.d(TAG, "onRouteProviderAdded: provider=" + provider);
268        }
269
270        @Override
271        public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
272            Log.d(TAG, "onRouteProviderRemoved: provider=" + provider);
273        }
274
275        @Override
276        public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
277            Log.d(TAG, "onRouteProviderChanged: provider=" + provider);
278        }
279    };
280
281    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
282        @Override
283        public void onReceive(Context context, Intent intent) {
284            Log.d(TAG, "Received status update: " + intent);
285            if (intent.getAction().equals(ACTION_STATUS_CHANGE)) {
286                String sid = intent.getStringExtra(MediaControlIntent.EXTRA_SESSION_ID);
287                String iid = intent.getStringExtra(MediaControlIntent.EXTRA_ITEM_ID);
288                MediaItemStatus status = MediaItemStatus.fromBundle(
289                    intent.getBundleExtra(MediaControlIntent.EXTRA_ITEM_STATUS));
290
291                if (status.getPlaybackState() ==
292                        MediaItemStatus.PLAYBACK_STATE_FINISHED) {
293                    mPlayer.onFinish(false);
294                } else if (status.getPlaybackState() ==
295                        MediaItemStatus.PLAYBACK_STATE_ERROR) {
296                    mPlayer.onFinish(true);
297                    showToast("Error while playing item" +
298                            ", sid " + sid + ", iid " + iid);
299                } else {
300                    if (!mSeeking) {
301                        updateUi();
302                    }
303                }
304            }
305        }
306    };
307
308    private RemoteControlClient mRemoteControlClient;
309    private ComponentName mEventReceiver;
310    private AudioManager mAudioManager;
311    private PendingIntent mMediaPendingIntent;
312    private final OnAudioFocusChangeListener mAfChangeListener =
313            new OnAudioFocusChangeListener() {
314        @Override
315        public void onAudioFocusChange(int focusChange) {
316            if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
317                Log.d(TAG, "onAudioFocusChange: LOSS_TRANSIENT");
318            } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
319                Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_GAIN");
320            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
321                Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS");
322            }
323        }
324    };
325
326    @Override
327    protected void onCreate(Bundle savedInstanceState) {
328        // Be sure to call the super class.
329        super.onCreate(savedInstanceState);
330
331        // Get the media router service.
332        mMediaRouter = MediaRouter.getInstance(this);
333
334        // Create a route selector for the type of routes that we care about.
335        mSelector = new MediaRouteSelector.Builder()
336                .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
337                .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
338                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
339                .addControlCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE)
340                .build();
341
342        // Add a fragment to take care of media route discovery.
343        // This fragment automatically adds or removes a callback whenever the activity
344        // is started or stopped.
345        FragmentManager fm = getSupportFragmentManager();
346        DiscoveryFragment fragment = (DiscoveryFragment)fm.findFragmentByTag(
347                DISCOVERY_FRAGMENT_TAG);
348        if (fragment == null) {
349            fragment = new DiscoveryFragment(mMediaRouterCB);
350            fragment.setRouteSelector(mSelector);
351            fm.beginTransaction()
352                    .add(fragment, DISCOVERY_FRAGMENT_TAG)
353                    .commit();
354        } else {
355            fragment.setCallback(mMediaRouterCB);
356            fragment.setRouteSelector(mSelector);
357        }
358
359        // Populate an array adapter with streaming media items.
360        String[] mediaNames = getResources().getStringArray(R.array.media_names);
361        String[] mediaUris = getResources().getStringArray(R.array.media_uris);
362        mLibraryItems = new LibraryAdapter();
363        for (int i = 0; i < mediaNames.length; i++) {
364            mLibraryItems.add(new MediaItem(
365                    "[streaming] "+mediaNames[i], Uri.parse(mediaUris[i])));
366        }
367
368        // Scan local /sdcard/ directory for media files.
369        String sdcard = "/sdcard/";
370        File file = new File(sdcard);
371        File list[] = file.listFiles();
372        for (int i = 0; i < list.length; i++) {
373            String filename = list[i].getName();
374            if (filename.matches(".*\\.(m4v|mp4)")) {
375                mLibraryItems.add(new MediaItem(
376                        "[local] "+filename, Uri.parse("file:///sdcard/" + filename)));
377            }
378        }
379
380        mPlayListItems = new PlaylistAdapter();
381
382        // Initialize the layout.
383        setContentView(R.layout.sample_media_router);
384
385        TabHost tabHost=(TabHost)findViewById(R.id.tabHost);
386        tabHost.setup();
387        String tabName = getResources().getString(R.string.library_tab_text);
388        TabSpec spec1=tabHost.newTabSpec(tabName);
389        spec1.setContent(R.id.tab1);
390        spec1.setIndicator(tabName);
391
392        tabName = getResources().getString(R.string.playlist_tab_text);
393        TabSpec spec2=tabHost.newTabSpec(tabName);
394        spec2.setIndicator(tabName);
395        spec2.setContent(R.id.tab2);
396
397        tabName = getResources().getString(R.string.statistics_tab_text);
398        TabSpec spec3=tabHost.newTabSpec(tabName);
399        spec3.setIndicator(tabName);
400        spec3.setContent(R.id.tab3);
401
402        tabHost.addTab(spec1);
403        tabHost.addTab(spec2);
404        tabHost.addTab(spec3);
405        tabHost.setOnTabChangedListener(new OnTabChangeListener() {
406            @Override
407            public void onTabChanged(String arg0) {
408                if (arg0.equals(getResources().getString(
409                        R.string.statistics_tab_text))) {
410                    mPlayer.showStatistics();
411                }
412                updateUi();
413            }
414        });
415
416        mLibraryView = (ListView) findViewById(R.id.media);
417        mLibraryView.setAdapter(mLibraryItems);
418        mLibraryView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
419        mLibraryView.setOnItemClickListener(new OnItemClickListener() {
420            @Override
421            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
422                updateButtons();
423            }
424        });
425
426        mPlayListView = (ListView) findViewById(R.id.playlist);
427        mPlayListView.setAdapter(mPlayListItems);
428        mPlayListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
429        mPlayListView.setOnItemClickListener(new OnItemClickListener() {
430            @Override
431            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
432                updateButtons();
433            }
434        });
435
436        mInfoTextView = (TextView) findViewById(R.id.info);
437
438        mPauseResumeButton = (ImageButton)findViewById(R.id.pause_resume_button);
439        mPauseResumeButton.setOnClickListener(new OnClickListener() {
440            @Override
441            public void onClick(View v) {
442                if (!mPaused) {
443                    mPlayer.pause();
444                } else {
445                    mPlayer.resume();
446                }
447            }
448        });
449
450        mStopButton = (ImageButton)findViewById(R.id.stop_button);
451        mStopButton.setOnClickListener(new OnClickListener() {
452            @Override
453            public void onClick(View v) {
454                mPlayer.stop();
455                clearContent();
456            }
457        });
458
459        mSeekBar = (SeekBar) findViewById(R.id.seekbar);
460        mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
461            @Override
462            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
463                MediaQueueItem item = getCheckedMediaQueueItem();
464                if (fromUser && item != null && item.getContentDuration() > 0) {
465                    long pos = progress * item.getContentDuration() / 100;
466                    mPlayer.seek(item.getSessionId(), item.getItemId(), pos);
467                    item.setContentPosition(pos);
468                    mLastStatusTime = SystemClock.elapsedRealtime();
469                }
470            }
471            @Override
472            public void onStartTrackingTouch(SeekBar seekBar) {
473                mSeeking = true;
474            }
475            @Override
476            public void onStopTrackingTouch(SeekBar seekBar) {
477                mSeeking = false;
478                updateUi();
479            }
480        });
481
482        // Schedule Ui update
483        mHandler.postDelayed(mUpdateSeekRunnable, 1000);
484
485        // Use local playback with media player by default
486        mLocalPlayer.onCreate();
487        mMediaPlayer.setCallback(mMediaPlayerCB);
488        mLocalPlayer.setCallback(mMediaPlayer);
489        mPlayerCB = mMediaPlayer;
490        mPlayer = mLocalPlayer;
491
492        // Register broadcast receiver to receive status update from MRP
493        IntentFilter filter = new IntentFilter();
494        filter.addAction(SampleMediaRouterActivity.ACTION_STATUS_CHANGE);
495        registerReceiver(mReceiver, filter);
496
497        // Build the PendingIntent for the remote control client
498        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
499        mEventReceiver = new ComponentName(getPackageName(),
500                SampleMediaButtonReceiver.class.getName());
501        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
502        mediaButtonIntent.setComponent(mEventReceiver);
503        mMediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
504
505        // Create and register the remote control client
506        registerRCC();
507    }
508
509    private void registerRCC() {
510        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
511            // Create the RCC and register with AudioManager and MediaRouter
512            mAudioManager.requestAudioFocus(mAfChangeListener,
513                    AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
514            mAudioManager.registerMediaButtonEventReceiver(mEventReceiver);
515            mRemoteControlClient = new RemoteControlClient(mMediaPendingIntent);
516            mAudioManager.registerRemoteControlClient(mRemoteControlClient);
517            mMediaRouter.addRemoteControlClient(mRemoteControlClient);
518            SampleMediaButtonReceiver.setActivity(SampleMediaRouterActivity.this);
519            mRemoteControlClient.setTransportControlFlags(
520                    RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE);
521            mRemoteControlClient.setPlaybackState(
522                    RemoteControlClient.PLAYSTATE_PLAYING);
523        }
524    }
525
526    private void unregisterRCC() {
527        // Unregister the RCC with AudioManager and MediaRouter
528        if (mRemoteControlClient != null) {
529            mRemoteControlClient.setTransportControlFlags(0);
530            mAudioManager.abandonAudioFocus(mAfChangeListener);
531            mAudioManager.unregisterMediaButtonEventReceiver(mEventReceiver);
532            mAudioManager.unregisterRemoteControlClient(mRemoteControlClient);
533            mMediaRouter.removeRemoteControlClient(mRemoteControlClient);
534            SampleMediaButtonReceiver.setActivity(null);
535            mRemoteControlClient = null;
536        }
537    }
538
539    public boolean handleMediaKey(KeyEvent event) {
540        if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
541            switch (event.getKeyCode()) {
542                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
543                {
544                    Log.d(TAG, "Received Play/Pause event from RemoteControlClient");
545                    if (!mPaused) {
546                        mPlayer.pause();
547                    } else {
548                        mPlayer.resume();
549                    }
550                    return true;
551                }
552                case KeyEvent.KEYCODE_MEDIA_PLAY:
553                {
554                    Log.d(TAG, "Received Play event from RemoteControlClient");
555                    if (mPaused) {
556                        mPlayer.resume();
557                    }
558                    return true;
559                }
560                case KeyEvent.KEYCODE_MEDIA_PAUSE:
561                {
562                    Log.d(TAG, "Received Pause event from RemoteControlClient");
563                    if (!mPaused) {
564                        mPlayer.pause();
565                    }
566                    return true;
567                }
568                case KeyEvent.KEYCODE_MEDIA_STOP:
569                {
570                    Log.d(TAG, "Received Stop event from RemoteControlClient");
571                    mPlayer.stop();
572                    clearContent();
573                    return true;
574                }
575                default:
576                    break;
577            }
578        }
579        return false;
580    }
581
582    @Override
583    public boolean onKeyDown(int keyCode, KeyEvent event) {
584        return handleMediaKey(event) || super.onKeyDown(keyCode, event);
585    }
586
587    @Override
588    public boolean onKeyUp(int keyCode, KeyEvent event) {
589        return handleMediaKey(event) || super.onKeyUp(keyCode, event);
590    }
591
592    @Override
593    public void onStart() {
594        // Be sure to call the super class.
595        super.onStart();
596        mPlayer.showStatistics();
597    }
598
599    @Override
600    public void onPause() {
601        // pause media player for local playback case only
602        if (!isRemotePlayback() && !mPaused) {
603            mNeedResume = true;
604            mPlayer.pause();
605        }
606        super.onPause();
607    }
608
609    @Override
610    public void onResume() {
611        // resume media player for local playback case only
612        if (!isRemotePlayback() && mNeedResume) {
613            mPlayer.resume();
614            mNeedResume = false;
615        }
616        super.onResume();
617    }
618
619    @Override
620    public void onDestroy() {
621        // Unregister the remote control client
622        unregisterRCC();
623
624        // Unregister broadcast receiver
625        unregisterReceiver(mReceiver);
626        mPlayer.stop();
627        mMediaPlayer.release();
628
629        super.onDestroy();
630    }
631
632    @Override
633    public boolean onCreateOptionsMenu(Menu menu) {
634        // Be sure to call the super class.
635        super.onCreateOptionsMenu(menu);
636
637        // Inflate the menu and configure the media router action provider.
638        getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
639
640        MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
641        MediaRouteActionProvider mediaRouteActionProvider =
642                (MediaRouteActionProvider)MenuItemCompat.getActionProvider(mediaRouteMenuItem);
643        mediaRouteActionProvider.setRouteSelector(mSelector);
644
645        // Return true to show the menu.
646        return true;
647    }
648
649    private void updateRouteDescription() {
650        RouteInfo route = mMediaRouter.getSelectedRoute();
651        mInfoTextView.setText("Currently selected route:"
652                + "\nName: " + route.getName()
653                + "\nProvider: " + route.getProvider().getPackageName()
654                + "\nDescription: " + route.getDescription()
655                + "\nStatistics: " + mStatsInfo);
656        updateButtons();
657        mLocalPlayer.updatePresentation();
658    }
659
660    private void clearContent() {
661        //TO-DO: clear surface view
662    }
663
664    private void updateButtons() {
665        MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
666        // show pause or resume icon depending on current state
667        mPauseResumeButton.setImageResource(mPaused ?
668                R.drawable.ic_media_play : R.drawable.ic_media_pause);
669        // only enable seek bar when duration is known
670        MediaQueueItem item = getCheckedMediaQueueItem();
671        mSeekBar.setEnabled(item != null && item.getContentDuration() > 0);
672        if (mRemoteControlClient != null) {
673            mRemoteControlClient.setPlaybackState(mPaused ?
674                    RemoteControlClient.PLAYSTATE_PAUSED : RemoteControlClient.PLAYSTATE_PLAYING);
675        }
676    }
677
678    private void updateProgress(MediaQueueItem queueItem) {
679        // Estimate content position from last status time and elapsed time.
680        // (Note this might be slightly out of sync with remote side, however
681        // it avoids frequent polling the MRP.)
682        int progress = 0;
683        if (queueItem != null) {
684            int state = queueItem.getState();
685            long duration = queueItem.getContentDuration();
686            if (duration <= 0) {
687                if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING
688                        || state == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
689                    updateUi();
690                }
691            } else {
692                long position = queueItem.getContentPosition();
693                long timeDelta = mPaused ? 0 :
694                        (SystemClock.elapsedRealtime() - mLastStatusTime);
695                progress = (int)(100.0 * (position + timeDelta) / duration);
696            }
697        }
698        mSeekBar.setProgress(progress);
699    }
700
701    private void updateUi() {
702        updatePlaylist();
703        updateButtons();
704    }
705
706    private void updatePlaylist() {
707        Log.d(TAG, "updatePlaylist");
708        final PlaylistAdapter playlist = new PlaylistAdapter();
709        // make a copy of current playlist
710        for (int i = 0; i < mPlayListItems.getCount(); i++) {
711            playlist.add(mPlayListItems.getItem(i));
712        }
713        // clear mPlayListItems first, items will be added back when we get
714        // status back from provider.
715        mPlayListItems.clear();
716        mPlayListView.invalidate();
717
718        for (int i = 0; i < playlist.getCount(); i++) {
719            final MediaQueueItem item = playlist.getItem(i);
720            final boolean update = (i == playlist.getCount() - 1);
721            mPlayer.getStatus(item, update);
722        }
723    }
724
725    private MediaItem getCheckedMediaItem() {
726        int index = mLibraryView.getCheckedItemPosition();
727        if (index >= 0 && index < mLibraryItems.getCount()) {
728            return mLibraryItems.getItem(index);
729        }
730        return null;
731    }
732
733    private MediaQueueItem getCheckedMediaQueueItem() {
734        int count = mPlayListView.getCount();
735        int index = mPlayListView.getCheckedItemPosition();
736        if (count > 0) {
737            if (index < 0 || index >= count) {
738                index = 0;
739                mPlayListView.setItemChecked(0, true);
740            }
741            return mPlayListItems.getItem(index);
742        }
743        return null;
744    }
745
746    private void enqueuePlaylist() {
747        if (mSavedPlaylist != null) {
748            final PlaylistAdapter playlist = mSavedPlaylist;
749            mSavedPlaylist = null;
750            // migrate playlist (except for the 1st item) to new route
751            for (int i = 1; i < playlist.getCount(); i++) {
752                final MediaQueueItem item = playlist.getItem(i);
753                mPlayer.enqueue(item.getUri(), item.getContentPosition());
754            }
755        }
756    }
757
758    private boolean isRemoteQueue() {
759        return mPlayer == mRemotePlayer;
760    }
761
762    private boolean isRemotePlayback() {
763        return mPlayerCB == mRemotePlayer;
764    }
765
766    private void showToast(String msg) {
767        Toast toast = Toast.makeText(SampleMediaRouterActivity.this,
768                "[app] " + msg, Toast.LENGTH_LONG);
769        toast.setGravity(Gravity.TOP, 0, 100);
770        toast.show();
771    }
772
773    private interface Player {
774        void enqueue(final Uri uri, long pos);
775        void remove(final MediaQueueItem item);
776        void seek(String sid, String iid, long pos);
777        void getStatus(final MediaQueueItem item, final boolean update);
778        void pause();
779        void resume();
780        void stop();
781        void showStatistics();
782        void onFinish(boolean error);
783        void updateSize(int width, int height);
784    }
785
786    private class LocalPlayer implements Player, SurfaceHolder.Callback {
787        private final MediaSessionManager mSessionManager = new MediaSessionManager();
788        private String mSessionId;
789        // The presentation to show on the secondary display.
790        private DemoPresentation mPresentation;
791        private SurfaceView mSurfaceView;
792        private FrameLayout mLayout;
793        private int mVideoWidth;
794        private int mVideoHeight;
795
796        public void onCreate() {
797            mLayout = (FrameLayout)findViewById(R.id.player);
798            mSurfaceView = (SurfaceView)findViewById(R.id.surface_view);
799            SurfaceHolder holder = mSurfaceView.getHolder();
800            holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
801            holder.addCallback(this);
802        }
803
804        public void setCallback(MediaSessionManager.Callback cb) {
805            mSessionManager.setCallback(cb);
806        }
807
808        @Override
809        public void enqueue(final Uri uri, long pos) {
810            Log.d(TAG, "LocalPlayer: enqueue, uri=" + uri + ", pos=" + pos);
811            MediaQueueItem playlistItem = mSessionManager.enqueue(mSessionId, uri, null);
812            mSessionId = playlistItem.getSessionId();
813            // Set remote control client title
814            if (mPlayListItems.getCount() == 0 && mRemoteControlClient != null) {
815                RemoteControlClient.MetadataEditor ed = mRemoteControlClient.editMetadata(true);
816                ed.putString(MediaMetadataRetriever.METADATA_KEY_TITLE,
817                        playlistItem.toString());
818                ed.apply();
819            }
820            mPlayListItems.add(playlistItem);
821            if (pos > 0) {
822                // Seek to initial position if needed
823                mPlayer.seek(mSessionId, playlistItem.getItemId(), pos);
824            }
825            updateUi();
826        }
827
828        @Override
829        public void remove(final MediaQueueItem item) {
830            Log.d(TAG, "LocalPlayer: remove, item=" + item);
831            mSessionManager.remove(item.getSessionId(), item.getItemId());
832            updateUi();
833        }
834
835        @Override
836        public void seek(String sid, String iid, long pos) {
837            Log.d(TAG, "LocalPlayer: seek, sid=" + sid + ", iid=" + iid);
838            mSessionManager.seek(sid, iid, pos);
839        }
840
841        @Override
842        public void getStatus(final MediaQueueItem item, final boolean update) {
843            Log.d(TAG, "LocalPlayer: getStatus, item=" + item + ", update=" + update);
844            MediaQueueItem playlistItem =
845                    mSessionManager.getStatus(item.getSessionId(), item.getItemId());
846            if (playlistItem != null) {
847                mLastStatusTime = playlistItem.getStatus().getTimestamp();
848                mPlayListItems.add(item);
849                mPlayListView.invalidate();
850            }
851            if (update) {
852                clearContent();
853                updateButtons();
854            }
855        }
856
857        @Override
858        public void pause() {
859            Log.d(TAG, "LocalPlayer: pause");
860            mSessionManager.pause(mSessionId);
861            mPaused = true;
862            updateUi();
863        }
864
865        @Override
866        public void resume() {
867            Log.d(TAG, "LocalPlayer: resume");
868            mSessionManager.resume(mSessionId);
869            mPaused = false;
870            updateUi();
871        }
872
873        @Override
874        public void stop() {
875            Log.d(TAG, "LocalPlayer: stop");
876            mSessionManager.stop(mSessionId);
877            mSessionId = null;
878            mPaused = false;
879            // For demo purpose, invalidate remote session when local session
880            // is stopped (note this is not necessary, remote player could reuse
881            // the same session)
882            mRemotePlayer.reset();
883            updateUi();
884        }
885
886        @Override
887        public void showStatistics() {
888            Log.d(TAG, "LocalPlayer: showStatistics");
889            mStatsInfo = null;
890            if (isRemotePlayback()) {
891                mRemotePlayer.showStatistics();
892            }
893            updateRouteDescription();
894        }
895
896        @Override
897        public void onFinish(boolean error) {
898            MediaQueueItem item = mSessionManager.finish(error);
899            updateUi();
900            if (error && item != null) {
901                showToast("Failed to play item " + item.getUri());
902            }
903        }
904
905        // SurfaceHolder.Callback
906        @Override
907        public void surfaceChanged(SurfaceHolder holder, int format,
908                int width, int height) {
909            Log.d(TAG, "surfaceChanged "+width+"x"+height);
910            mMediaPlayer.setSurface(holder);
911        }
912
913        @Override
914        public void surfaceCreated(SurfaceHolder holder) {
915            Log.d(TAG, "surfaceCreated");
916            mMediaPlayer.setSurface(holder);
917            updateSize(mVideoWidth, mVideoHeight);
918        }
919
920        @Override
921        public void surfaceDestroyed(SurfaceHolder holder) {
922            Log.d(TAG, "surfaceDestroyed");
923        }
924
925        @Override
926        public void updateSize(int width, int height) {
927            if (width > 0 && height > 0) {
928                if (mPresentation == null) {
929                    int surfaceWidth = mLayout.getWidth();
930                    int surfaceHeight = mLayout.getHeight();
931
932                    // Calculate the new size of mSurfaceView, so that video is centered
933                    // inside the framelayout with proper letterboxing/pillarboxing
934                    ViewGroup.LayoutParams lp = mSurfaceView.getLayoutParams();
935                    if (surfaceWidth * height < surfaceHeight * width) {
936                        // Black bars on top&bottom, mSurfaceView has full layout width,
937                        // while height is derived from video's aspect ratio
938                        lp.width = surfaceWidth;
939                        lp.height = surfaceWidth * height / width;
940                    } else {
941                        // Black bars on left&right, mSurfaceView has full layout height,
942                        // while width is derived from video's aspect ratio
943                        lp.width = surfaceHeight * width / height;
944                        lp.height = surfaceHeight;
945                    }
946                    Log.d(TAG, "video rect is "+lp.width+"x"+lp.height);
947                    mSurfaceView.setLayoutParams(lp);
948                } else {
949                    mPresentation.updateSize(width, height);
950                }
951                mVideoWidth = width;
952                mVideoHeight = height;
953            } else {
954                mVideoWidth = mVideoHeight = 0;
955            }
956        }
957
958        private void updatePresentation() {
959            // Get the current route and its presentation display.
960            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
961            Display presentationDisplay = route != null ? route.getPresentationDisplay() : null;
962
963            // Dismiss the current presentation if the display has changed.
964            if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) {
965                Log.i(TAG, "Dismissing presentation because the current route no longer "
966                        + "has a presentation display.");
967                mPresentation.dismiss();
968                mPresentation = null;
969            }
970
971            // Show a new presentation if needed.
972            if (mPresentation == null && presentationDisplay != null) {
973                Log.i(TAG, "Showing presentation on display: " + presentationDisplay);
974                mPresentation = new DemoPresentation(
975                        SampleMediaRouterActivity.this, presentationDisplay);
976                mPresentation.setOnDismissListener(mOnDismissListener);
977                try {
978                    mPresentation.show();
979                } catch (WindowManager.InvalidDisplayException ex) {
980                    Log.w(TAG, "Couldn't show presentation!  Display was removed in "
981                            + "the meantime.", ex);
982                    mPresentation = null;
983                }
984            }
985
986            if (mPresentation != null || route.supportsControlCategory(
987                    MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)) {
988                mMediaPlayer.setSurface((SurfaceHolder)null);
989                mMediaPlayer.reset();
990                mSurfaceView.setVisibility(View.GONE);
991                mLayout.setVisibility(View.GONE);
992            } else {
993                mLayout.setVisibility(View.VISIBLE);
994                mSurfaceView.setVisibility(View.VISIBLE);
995            }
996        }
997
998        // Listens for when presentations are dismissed.
999        private final DialogInterface.OnDismissListener mOnDismissListener =
1000                new DialogInterface.OnDismissListener() {
1001            @Override
1002            public void onDismiss(DialogInterface dialog) {
1003                if (dialog == mPresentation) {
1004                    Log.i(TAG, "Presentation was dismissed.");
1005                    mPresentation = null;
1006                    updatePresentation();
1007                }
1008            }
1009        };
1010
1011        private final class DemoPresentation extends Presentation {
1012            private SurfaceView mPresentationSurfaceView;
1013
1014            public DemoPresentation(Context context, Display display) {
1015                super(context, display);
1016            }
1017
1018            @Override
1019            protected void onCreate(Bundle savedInstanceState) {
1020                // Be sure to call the super class.
1021                super.onCreate(savedInstanceState);
1022
1023                // Get the resources for the context of the presentation.
1024                // Notice that we are getting the resources from the context
1025                // of the presentation.
1026                Resources r = getContext().getResources();
1027
1028                // Inflate the layout.
1029                setContentView(R.layout.sample_media_router_presentation);
1030
1031                // Set up the surface view.
1032                mPresentationSurfaceView = (SurfaceView)findViewById(R.id.surface_view);
1033                SurfaceHolder holder = mPresentationSurfaceView.getHolder();
1034                holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
1035                holder.addCallback(LocalPlayer.this);
1036            }
1037
1038            public void updateSize(int width, int height) {
1039                int surfaceHeight = getWindow().getDecorView().getHeight();
1040                int surfaceWidth = getWindow().getDecorView().getWidth();
1041                ViewGroup.LayoutParams lp = mPresentationSurfaceView.getLayoutParams();
1042                if (surfaceWidth * height < surfaceHeight * width) {
1043                    lp.width = surfaceWidth;
1044                    lp.height = surfaceWidth * height / width;
1045                } else {
1046                    lp.width = surfaceHeight * width / height;
1047                    lp.height = surfaceHeight;
1048                }
1049                Log.d(TAG, "video rect is " + lp.width + "x" + lp.height);
1050                mPresentationSurfaceView.setLayoutParams(lp);
1051            }
1052        }
1053    }
1054
1055    private class RemotePlayer implements Player, MediaSessionManager.Callback {
1056        private MediaQueueItem mQueueItem;
1057        private MediaQueueItem mPlaylistItem;
1058        private String mSessionId;
1059        private String mItemId;
1060        private long mPosition;
1061
1062        public void reset() {
1063            mQueueItem = null;
1064            mPlaylistItem = null;
1065            mSessionId = null;
1066            mItemId = null;
1067            mPosition = 0;
1068        }
1069
1070        // MediaSessionManager.Callback
1071        @Override
1072        public void onStart() {
1073            resume();
1074        }
1075
1076        @Override
1077        public void onPause() {
1078            pause();
1079        }
1080
1081        @Override
1082        public void onStop() {
1083            stop();
1084        }
1085
1086        @Override
1087        public void onSeek(long pos) {
1088            // If we're currently performing a Play/Enqueue, do not seek
1089            // until we get the result back (or we may not have valid session
1090            // and item ids); otherwise do the seek now
1091            if (mSessionId != null) {
1092                seek(mSessionId, mItemId, pos);
1093            }
1094            // Set current position to seek-to position, actual position will
1095            // be updated when next getStatus is completed.
1096            mPosition = pos;
1097        }
1098
1099        @Override
1100        public void onGetStatus(MediaQueueItem item) {
1101            if (mQueueItem != null) {
1102                mPlaylistItem = item;
1103                getStatus(mQueueItem, false);
1104            }
1105        }
1106
1107        @Override
1108        public void onNewItem(Uri uri) {
1109            mPosition = 0;
1110            play(uri, false, 0);
1111        }
1112
1113        // Player API
1114        @Override
1115        public void enqueue(final Uri uri, long pos) {
1116            play(uri, true, pos);
1117        }
1118
1119        @Override
1120        public void remove(final MediaQueueItem item) {
1121            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
1122            Intent intent = makeRemoveIntent(item);
1123            if (route.supportsControlRequest(intent)) {
1124                MediaRouter.ControlRequestCallback callback =
1125                        new MediaRouter.ControlRequestCallback() {
1126                    @Override
1127                    public void onResult(Bundle data) {
1128                        MediaItemStatus status = MediaItemStatus.fromBundle(
1129                                data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS));
1130                        Log.d(TAG, "Remove request succeeded: status=" + status.toString());
1131                        updateUi();
1132                    }
1133
1134                    @Override
1135                    public void onError(String error, Bundle data) {
1136                        Log.d(TAG, "Remove request failed: error=" + error + ", data=" + data);
1137                    }
1138                };
1139
1140                Log.d(TAG, "Sending remove request: intent=" + intent);
1141                route.sendControlRequest(intent, callback);
1142            } else {
1143                Log.d(TAG, "Remove request not supported!");
1144            }
1145        }
1146
1147        @Override
1148        public void seek(String sid, String iid, long pos) {
1149            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
1150            Intent intent = makeSeekIntent(sid, iid, pos);
1151            if (route.supportsControlRequest(intent)) {
1152                MediaRouter.ControlRequestCallback callback =
1153                        new MediaRouter.ControlRequestCallback() {
1154                    @Override
1155                    public void onResult(Bundle data) {
1156                        MediaItemStatus status = MediaItemStatus.fromBundle(
1157                                data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS));
1158                        Log.d(TAG, "Seek request succeeded: status=" + status.toString());
1159                    }
1160
1161                    @Override
1162                    public void onError(String error, Bundle data) {
1163                        Log.d(TAG, "Seek request failed: error=" + error + ", data=" + data);
1164                    }
1165                };
1166
1167                Log.d(TAG, "Sending seek request: intent=" + intent);
1168                route.sendControlRequest(intent, callback);
1169            } else {
1170                Log.d(TAG, "Seek request not supported!");
1171            }
1172        }
1173
1174        @Override
1175        public void getStatus(final MediaQueueItem item, final boolean update) {
1176            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
1177            Intent intent = makeGetStatusIntent(item);
1178            if (route.supportsControlRequest(intent)) {
1179                MediaRouter.ControlRequestCallback callback =
1180                        new MediaRouter.ControlRequestCallback() {
1181                    @Override
1182                    public void onResult(Bundle data) {
1183                        if (data != null) {
1184                            String sid = data.getString(MediaControlIntent.EXTRA_SESSION_ID);
1185                            String iid = data.getString(MediaControlIntent.EXTRA_ITEM_ID);
1186                            MediaItemStatus status = MediaItemStatus.fromBundle(
1187                                    data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS));
1188                            Log.d(TAG, "GetStatus request succeeded: status=" + status.toString());
1189                            //showToast("GetStatus request succeeded " + item.mName);
1190                            if (isRemoteQueue()) {
1191                                int state = status.getPlaybackState();
1192                                if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING
1193                                        || state == MediaItemStatus.PLAYBACK_STATE_PAUSED
1194                                        || state == MediaItemStatus.PLAYBACK_STATE_PENDING) {
1195                                    item.setState(state);
1196                                    item.setContentPosition(status.getContentPosition());
1197                                    item.setContentDuration(status.getContentDuration());
1198                                    mLastStatusTime = status.getTimestamp();
1199                                    mPlayListItems.add(item);
1200                                    mPlayListView.invalidate();
1201                                    // update buttons as the queue count might have changed
1202                                    if (update) {
1203                                        clearContent();
1204                                        updateButtons();
1205                                    }
1206                                }
1207                            } else {
1208                                if (mPlaylistItem != null) {
1209                                    mPlaylistItem.setContentPosition(status.getContentPosition());
1210                                    mPlaylistItem.setContentDuration(status.getContentDuration());
1211                                    mPlaylistItem = null;
1212                                    updateButtons();
1213                                }
1214                            }
1215                        }
1216                    }
1217
1218                    @Override
1219                    public void onError(String error, Bundle data) {
1220                        Log.d(TAG, "GetStatus request failed: error=" + error + ", data=" + data);
1221                        //showToast("Unable to get status ");
1222                        if (isRemoteQueue()) {
1223                            if (update) {
1224                                clearContent();
1225                                updateButtons();
1226                            }
1227                        }
1228                    }
1229                };
1230
1231                Log.d(TAG, "Sending GetStatus request: intent=" + intent);
1232                route.sendControlRequest(intent, callback);
1233            } else {
1234                Log.d(TAG, "GetStatus request not supported!");
1235            }
1236        }
1237
1238        @Override
1239        public void pause() {
1240            Intent intent = makePauseIntent();
1241            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
1242            if (route.supportsControlRequest(intent)) {
1243                MediaRouter.ControlRequestCallback callback =
1244                        new MediaRouter.ControlRequestCallback() {
1245                    @Override
1246                    public void onResult(Bundle data) {
1247                        Log.d(TAG, "Pause request succeeded");
1248                        if (isRemoteQueue()) {
1249                            mPaused = true;
1250                            updateUi();
1251                        }
1252                    }
1253
1254                    @Override
1255                    public void onError(String error, Bundle data) {
1256                        Log.d(TAG, "Pause request failed: error=" + error);
1257                    }
1258                };
1259
1260                Log.d(TAG, "Sending pause request");
1261                route.sendControlRequest(intent, callback);
1262            } else {
1263                Log.d(TAG, "Pause request not supported!");
1264            }
1265        }
1266
1267        @Override
1268        public void resume() {
1269            Intent intent = makeResumeIntent();
1270            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
1271            if (route.supportsControlRequest(intent)) {
1272                MediaRouter.ControlRequestCallback callback =
1273                        new MediaRouter.ControlRequestCallback() {
1274                    @Override
1275                    public void onResult(Bundle data) {
1276                        Log.d(TAG, "Resume request succeeded");
1277                        if (isRemoteQueue()) {
1278                            mPaused = false;
1279                            updateUi();
1280                        }
1281                    }
1282
1283                    @Override
1284                    public void onError(String error, Bundle data) {
1285                        Log.d(TAG, "Resume request failed: error=" + error);
1286                    }
1287                };
1288
1289                Log.d(TAG, "Sending resume request");
1290                route.sendControlRequest(intent, callback);
1291            } else {
1292                Log.d(TAG, "Resume request not supported!");
1293            }
1294        }
1295
1296        @Override
1297        public void stop() {
1298            Intent intent = makeStopIntent();
1299            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
1300            if (route.supportsControlRequest(intent)) {
1301                MediaRouter.ControlRequestCallback callback =
1302                        new MediaRouter.ControlRequestCallback() {
1303                    @Override
1304                    public void onResult(Bundle data) {
1305                        Log.d(TAG, "Stop request succeeded");
1306                        if (isRemoteQueue()) {
1307                            // Reset mSessionId, so that next Play/Enqueue
1308                            // starts a new session
1309                            mQueueItem = null;
1310                            mSessionId = null;
1311                            mPaused = false;
1312                            updateUi();
1313                        }
1314                    }
1315
1316                    @Override
1317                    public void onError(String error, Bundle data) {
1318                        Log.d(TAG, "Stop request failed: error=" + error);
1319                    }
1320                };
1321
1322                Log.d(TAG, "Sending stop request");
1323                route.sendControlRequest(intent, callback);
1324            } else {
1325                Log.d(TAG, "Stop request not supported!");
1326            }
1327        }
1328
1329        @Override
1330        public void showStatistics() {
1331            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
1332            Intent intent = makeStatisticsIntent();
1333            if (route.supportsControlRequest(intent)) {
1334                MediaRouter.ControlRequestCallback callback =
1335                        new MediaRouter.ControlRequestCallback() {
1336                    @Override
1337                    public void onResult(Bundle data) {
1338                        Log.d(TAG, "Statistics request succeeded: data=" + data);
1339                        if (data != null) {
1340                            int playbackCount = data.getInt(
1341                                    SampleMediaRouteProvider.DATA_PLAYBACK_COUNT, -1);
1342                            mStatsInfo = "Total playback count: " + playbackCount;
1343                        } else {
1344                            showToast("Statistics query did not return any data");
1345                        }
1346                        updateRouteDescription();
1347                    }
1348
1349                    @Override
1350                    public void onError(String error, Bundle data) {
1351                        Log.d(TAG, "Statistics request failed: error=" + error + ", data=" + data);
1352                        showToast("Unable to query statistics, error: " + error);
1353                        updateRouteDescription();
1354                    }
1355                };
1356
1357                Log.d(TAG, "Sent statistics request: intent=" + intent);
1358                route.sendControlRequest(intent, callback);
1359            } else {
1360                Log.d(TAG, "Statistics request not supported!");
1361            }
1362
1363        }
1364
1365        @Override
1366        public void onFinish(boolean error) {
1367            updateUi();
1368        }
1369
1370        @Override
1371        public void updateSize(int width, int height) {
1372            // nothing to do
1373        }
1374
1375        private void play(final Uri uri, boolean enqueue, final long pos) {
1376            // save the initial seek position
1377            mPosition = pos;
1378            MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
1379            Intent intent = makePlayIntent(uri, enqueue);
1380            final String request = enqueue ? "Enqueue" : "Play";
1381            if (route.supportsControlRequest(intent)) {
1382                MediaRouter.ControlRequestCallback callback =
1383                        new MediaRouter.ControlRequestCallback() {
1384                    @Override
1385                    public void onResult(Bundle data) {
1386                        if (data != null) {
1387                            String sid = data.getString(MediaControlIntent.EXTRA_SESSION_ID);
1388                            String iid = data.getString(MediaControlIntent.EXTRA_ITEM_ID);
1389                            MediaItemStatus status = MediaItemStatus.fromBundle(
1390                                    data.getBundle(MediaControlIntent.EXTRA_ITEM_STATUS));
1391                            Log.d(TAG, request + " request succeeded: data=" + data +
1392                                    ", sid=" + sid + ", iid=" + iid);
1393
1394                            // perform delayed initial seek
1395                            if (mSessionId == null && mPosition > 0) {
1396                                seek(sid, iid, mPosition);
1397                            }
1398
1399                            mSessionId = sid;
1400                            mItemId = iid;
1401                            mQueueItem = new MediaQueueItem(sid, iid, null, null);
1402
1403                            if (isRemoteQueue()) {
1404                                MediaQueueItem playlistItem =
1405                                        new MediaQueueItem(sid, iid, uri, null);
1406                                playlistItem.setState(status.getPlaybackState());
1407                                mPlayListItems.add(playlistItem);
1408                                updateUi();
1409                                enqueuePlaylist();
1410                            }
1411                        }
1412                    }
1413
1414                    @Override
1415                    public void onError(String error, Bundle data) {
1416                        Log.d(TAG, request + " request failed: error=" + error + ", data=" + data);
1417                        showToast("Unable to " + request + uri + ", error: " + error);
1418                    }
1419                };
1420
1421                Log.d(TAG, "Sending " + request + " request: intent=" + intent);
1422                route.sendControlRequest(intent, callback);
1423            } else {
1424                Log.d(TAG, request + " request not supported!");
1425            }
1426        }
1427
1428        private Intent makePlayIntent(Uri uri, boolean enqueue) {
1429            Intent intent = new Intent(
1430                    enqueue ? MediaControlIntent.ACTION_ENQUEUE
1431                            : MediaControlIntent.ACTION_PLAY);
1432            intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
1433            intent.setDataAndType(uri, "video/mp4");
1434
1435            // Provide a valid session id, or none (which starts a new session)
1436            if (mSessionId != null) {
1437                intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
1438            }
1439
1440            // PendingIntent for receiving status update from MRP
1441            Intent statusIntent = new Intent(SampleMediaRouterActivity.ACTION_STATUS_CHANGE);
1442            intent.putExtra(MediaControlIntent.EXTRA_ITEM_STATUS_UPDATE_RECEIVER,
1443                    PendingIntent.getBroadcast(SampleMediaRouterActivity.this,
1444                            0, statusIntent, 0));
1445
1446            return intent;
1447        }
1448
1449        private Intent makeRemoveIntent(MediaQueueItem item) {
1450            Intent intent = new Intent(MediaControlIntent.ACTION_REMOVE);
1451            intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
1452            intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
1453            intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
1454            return intent;
1455        }
1456
1457        private Intent makeSeekIntent(String sid, String iid, long pos) {
1458            Intent intent = new Intent(MediaControlIntent.ACTION_SEEK);
1459            intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
1460            intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, sid);
1461            intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, iid);
1462            intent.putExtra(MediaControlIntent.EXTRA_ITEM_CONTENT_POSITION, pos);
1463            return intent;
1464        }
1465
1466        private Intent makePauseIntent() {
1467            Intent intent = new Intent(MediaControlIntent.ACTION_PAUSE);
1468            intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
1469            if (mSessionId != null) {
1470                intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
1471            }
1472            return intent;
1473        }
1474
1475        private Intent makeResumeIntent() {
1476            Intent intent = new Intent(MediaControlIntent.ACTION_RESUME);
1477            intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
1478            if (mSessionId != null) {
1479                intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
1480            }
1481            return intent;
1482        }
1483
1484        private Intent makeStopIntent() {
1485            Intent intent = new Intent(MediaControlIntent.ACTION_STOP);
1486            intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
1487            if (mSessionId != null) {
1488                intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, mSessionId);
1489            }
1490            return intent;
1491        }
1492
1493        private Intent makeGetStatusIntent(MediaQueueItem item) {
1494            Intent intent = new Intent(MediaControlIntent.ACTION_GET_STATUS);
1495            intent.addCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK);
1496            intent.putExtra(MediaControlIntent.EXTRA_SESSION_ID, item.getSessionId());
1497            intent.putExtra(MediaControlIntent.EXTRA_ITEM_ID, item.getItemId());
1498            return intent;
1499         }
1500
1501        private Intent makeStatisticsIntent() {
1502            Intent intent = new Intent(SampleMediaRouteProvider.ACTION_GET_STATISTICS);
1503            intent.addCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE);
1504            return intent;
1505        }
1506    }
1507
1508    public static final class DiscoveryFragment extends MediaRouteDiscoveryFragment {
1509        private static final String TAG = "DiscoveryFragment";
1510        private Callback mCallback;
1511
1512        public DiscoveryFragment() {
1513            mCallback = null;
1514        }
1515
1516        public DiscoveryFragment(Callback cb) {
1517            mCallback = cb;
1518        }
1519
1520        public void setCallback(Callback cb) {
1521            mCallback = cb;
1522        }
1523
1524        @Override
1525        public Callback onCreateCallback() {
1526            return mCallback;
1527        }
1528
1529        @Override
1530        public int onPrepareCallbackFlags() {
1531            // Add the CALLBACK_FLAG_UNFILTERED_EVENTS flag to ensure that we will
1532            // observe and log all route events including those that are for routes
1533            // that do not match our selector.  This is only for demonstration purposes
1534            // and should not be needed by most applications.
1535            return super.onPrepareCallbackFlags()
1536                    | MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS;
1537        }
1538    }
1539
1540    private static final class MediaItem {
1541        public final String mName;
1542        public final Uri mUri;
1543
1544        public MediaItem(String name, Uri uri) {
1545            mName = name;
1546            mUri = uri;
1547        }
1548
1549        @Override
1550        public String toString() {
1551            return mName;
1552        }
1553    }
1554
1555    private final class LibraryAdapter extends ArrayAdapter<MediaItem> {
1556        public LibraryAdapter() {
1557            super(SampleMediaRouterActivity.this, R.layout.media_item);
1558        }
1559
1560        @Override
1561        public View getView(int position, View convertView, ViewGroup parent) {
1562            final View v;
1563            if (convertView == null) {
1564                v = getLayoutInflater().inflate(R.layout.media_item, null);
1565            } else {
1566                v = convertView;
1567            }
1568
1569            final MediaItem item = getItem(position);
1570
1571            TextView tv = (TextView)v.findViewById(R.id.item_text);
1572            tv.setText(item.mName);
1573
1574            ImageButton b = (ImageButton)v.findViewById(R.id.item_action);
1575            b.setImageResource(R.drawable.ic_menu_add);
1576            b.setTag(item);
1577            b.setOnClickListener(new OnClickListener() {
1578                @Override
1579                public void onClick(View v) {
1580                    if (item != null) {
1581                        mPlayer.enqueue(item.mUri, 0);
1582                    }
1583                }
1584            });
1585
1586            return v;
1587        }
1588    }
1589
1590    private final class PlaylistAdapter extends ArrayAdapter<MediaQueueItem> {
1591        public PlaylistAdapter() {
1592            super(SampleMediaRouterActivity.this, R.layout.media_item);
1593        }
1594
1595        @Override
1596        public View getView(int position, View convertView, ViewGroup parent) {
1597            final View v;
1598            if (convertView == null) {
1599                v = getLayoutInflater().inflate(R.layout.media_item, null);
1600            } else {
1601                v = convertView;
1602            }
1603
1604            final MediaQueueItem item = getItem(position);
1605
1606            TextView tv = (TextView)v.findViewById(R.id.item_text);
1607            tv.setText(item.toString());
1608
1609            ImageButton b = (ImageButton)v.findViewById(R.id.item_action);
1610            b.setImageResource(R.drawable.ic_menu_delete);
1611            b.setTag(item);
1612            b.setOnClickListener(new OnClickListener() {
1613                @Override
1614                public void onClick(View v) {
1615                    if (item != null) {
1616                        mPlayer.remove(item);
1617                    }
1618                }
1619            });
1620
1621            return v;
1622        }
1623    }
1624
1625    /**
1626     * Trivial subclass of this activity used to provide another copy of the
1627     * same activity using a light theme instead of the dark theme.
1628     */
1629    public static class Light extends SampleMediaRouterActivity {
1630    }
1631
1632    /**
1633     * Trivial subclass of this activity used to provide another copy of the
1634     * same activity using a light theme with dark action bar instead of the dark theme.
1635     */
1636    public static class LightWithDarkActionBar extends SampleMediaRouterActivity {
1637    }
1638}
1639