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