SampleMediaRouterActivity.java revision 9a2de97bec8126f8c8c05e9c995f378c5a73319a
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.DialogInterface;
24import android.content.Intent;
25import android.content.res.Resources;
26import android.app.PendingIntent;
27import android.media.AudioManager;
28import android.media.AudioManager.OnAudioFocusChangeListener;
29import android.media.MediaMetadataRetriever;
30import android.media.RemoteControlClient;
31import android.net.Uri;
32import android.os.Build;
33import android.os.Environment;
34import android.os.Handler;
35import android.os.Bundle;
36import android.os.SystemClock;
37import android.support.v4.app.FragmentManager;
38import android.support.v4.media.session.MediaSessionCompat;
39import android.support.v4.view.MenuItemCompat;
40import android.support.v7.app.ActionBarActivity;
41import android.support.v7.app.MediaRouteActionProvider;
42import android.support.v7.app.MediaRouteButton;
43import android.support.v7.app.MediaRouteControllerDialog;
44import android.support.v7.app.MediaRouteControllerDialogFragment;
45import android.support.v7.app.MediaRouteDiscoveryFragment;
46import android.support.v7.app.MediaRouteDialogFactory;
47import android.support.v7.media.MediaControlIntent;
48import android.support.v7.media.MediaRouter;
49import android.support.v7.media.MediaRouter.Callback;
50import android.support.v7.media.MediaRouter.RouteInfo;
51import android.support.v7.media.MediaRouter.ProviderInfo;
52import android.support.v7.media.MediaRouteSelector;
53import android.support.v7.media.MediaItemStatus;
54import android.util.Log;
55import android.view.KeyEvent;
56import android.view.Menu;
57import android.view.MenuItem;
58import android.view.View;
59import android.view.View.OnClickListener;
60import android.view.ViewGroup;
61import android.widget.AdapterView;
62import android.widget.AdapterView.OnItemClickListener;
63import android.widget.ArrayAdapter;
64import android.widget.ImageButton;
65import android.widget.ListView;
66import android.widget.TextView;
67import android.widget.TabHost;
68import android.widget.TabHost.TabSpec;
69import android.widget.TabHost.OnTabChangeListener;
70import android.widget.SeekBar;
71import android.widget.SeekBar.OnSeekBarChangeListener;
72
73import java.io.File;
74
75/**
76 * <h3>Media Router Support Activity</h3>
77 *
78 * <p>
79 * This demonstrates how to use the {@link MediaRouter} API to build an
80 * application that allows the user to send content to various rendering
81 * targets.
82 * </p>
83 */
84public class SampleMediaRouterActivity extends ActionBarActivity {
85    private static final String TAG = "SampleMediaRouterActivity";
86    private static final String DISCOVERY_FRAGMENT_TAG = "DiscoveryFragment";
87
88    private MediaRouter mMediaRouter;
89    private MediaRouteSelector mSelector;
90    private LibraryAdapter mLibraryItems;
91    private PlaylistAdapter mPlayListItems;
92    private TextView mInfoTextView;
93    private ListView mLibraryView;
94    private ListView mPlayListView;
95    private ImageButton mPauseResumeButton;
96    private ImageButton mStopButton;
97    private SeekBar mSeekBar;
98    private boolean mNeedResume;
99    private boolean mSeeking;
100
101    private final Handler mHandler = new Handler();
102    private final Runnable mUpdateSeekRunnable = new Runnable() {
103        @Override
104        public void run() {
105            updateProgress();
106            // update Ui every 1 second
107            mHandler.postDelayed(this, 1000);
108        }
109    };
110
111    private final SessionManager mSessionManager = new SessionManager("app");
112    private Player mPlayer;
113
114    private final MediaRouter.Callback mMediaRouterCB = new MediaRouter.Callback() {
115        // Return a custom callback that will simply log all of the route events
116        // for demonstration purposes.
117        @Override
118        public void onRouteAdded(MediaRouter router, RouteInfo route) {
119            Log.d(TAG, "onRouteAdded: route=" + route);
120        }
121
122        @Override
123        public void onRouteChanged(MediaRouter router, RouteInfo route) {
124            Log.d(TAG, "onRouteChanged: route=" + route);
125        }
126
127        @Override
128        public void onRouteRemoved(MediaRouter router, RouteInfo route) {
129            Log.d(TAG, "onRouteRemoved: route=" + route);
130        }
131
132        @Override
133        public void onRouteSelected(MediaRouter router, RouteInfo route) {
134            Log.d(TAG, "onRouteSelected: route=" + route);
135
136            mPlayer = Player.create(SampleMediaRouterActivity.this, route, mMediaSession);
137            mPlayer.updatePresentation();
138            mSessionManager.setPlayer(mPlayer);
139            mSessionManager.unsuspend();
140
141            updateUi();
142        }
143
144        @Override
145        public void onRouteUnselected(MediaRouter router, RouteInfo route) {
146            Log.d(TAG, "onRouteUnselected: route=" + route);
147            mMediaSession.setActive(false);
148
149            PlaylistItem item = getCheckedPlaylistItem();
150            if (item != null) {
151                long pos = item.getPosition() + (mSessionManager.isPaused() ?
152                        0 : (SystemClock.elapsedRealtime() - item.getTimestamp()));
153                mSessionManager.suspend(pos);
154            }
155            mPlayer.updatePresentation();
156            mPlayer.release();
157        }
158
159        @Override
160        public void onRouteVolumeChanged(MediaRouter router, RouteInfo route) {
161            Log.d(TAG, "onRouteVolumeChanged: route=" + route);
162        }
163
164        @Override
165        public void onRoutePresentationDisplayChanged(
166                MediaRouter router, RouteInfo route) {
167            Log.d(TAG, "onRoutePresentationDisplayChanged: route=" + route);
168            mPlayer.updatePresentation();
169        }
170
171        @Override
172        public void onProviderAdded(MediaRouter router, ProviderInfo provider) {
173            Log.d(TAG, "onRouteProviderAdded: provider=" + provider);
174        }
175
176        @Override
177        public void onProviderRemoved(MediaRouter router, ProviderInfo provider) {
178            Log.d(TAG, "onRouteProviderRemoved: provider=" + provider);
179        }
180
181        @Override
182        public void onProviderChanged(MediaRouter router, ProviderInfo provider) {
183            Log.d(TAG, "onRouteProviderChanged: provider=" + provider);
184        }
185    };
186
187    private MediaSessionCompat mMediaSession;
188    private ComponentName mEventReceiver;
189    private AudioManager mAudioManager;
190    private PendingIntent mMediaPendingIntent;
191    private final OnAudioFocusChangeListener mAfChangeListener =
192            new OnAudioFocusChangeListener() {
193        @Override
194        public void onAudioFocusChange(int focusChange) {
195            if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) {
196                Log.d(TAG, "onAudioFocusChange: LOSS_TRANSIENT");
197            } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) {
198                Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_GAIN");
199            } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
200                Log.d(TAG, "onAudioFocusChange: AUDIOFOCUS_LOSS");
201            }
202        }
203    };
204
205    @Override
206    protected void onCreate(Bundle savedInstanceState) {
207        // Be sure to call the super class.
208        super.onCreate(savedInstanceState);
209
210        // Get the media router service.
211        mMediaRouter = MediaRouter.getInstance(this);
212
213        // Create a route selector for the type of routes that we care about.
214        mSelector = new MediaRouteSelector.Builder()
215                .addControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
216                .addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
217                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
218                .addControlCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE)
219                .build();
220
221        // Add a fragment to take care of media route discovery.
222        // This fragment automatically adds or removes a callback whenever the activity
223        // is started or stopped.
224        FragmentManager fm = getSupportFragmentManager();
225        DiscoveryFragment fragment = (DiscoveryFragment)fm.findFragmentByTag(
226                DISCOVERY_FRAGMENT_TAG);
227        if (fragment == null) {
228            fragment = new DiscoveryFragment(mMediaRouterCB);
229            fragment.setRouteSelector(mSelector);
230            fm.beginTransaction()
231                    .add(fragment, DISCOVERY_FRAGMENT_TAG)
232                    .commit();
233        } else {
234            fragment.setCallback(mMediaRouterCB);
235            fragment.setRouteSelector(mSelector);
236        }
237
238        // Populate an array adapter with streaming media items.
239        String[] mediaNames = getResources().getStringArray(R.array.media_names);
240        String[] mediaUris = getResources().getStringArray(R.array.media_uris);
241        mLibraryItems = new LibraryAdapter();
242        for (int i = 0; i < mediaNames.length; i++) {
243            mLibraryItems.add(new MediaItem(
244                    "[streaming] "+mediaNames[i], Uri.parse(mediaUris[i]), "video/mp4"));
245        }
246
247        // Scan local external storage directory for media files.
248        File externalDir = Environment.getExternalStorageDirectory();
249        if (externalDir != null) {
250            File list[] = externalDir.listFiles();
251            if (list != null) {
252                for (int i = 0; i < list.length; i++) {
253                    String filename = list[i].getName();
254                    if (filename.matches(".*\\.(m4v|mp4)")) {
255                        mLibraryItems.add(new MediaItem("[local] " + filename,
256                                Uri.fromFile(list[i]), "video/mp4"));
257                    }
258                }
259            }
260        }
261
262        mPlayListItems = new PlaylistAdapter();
263
264        // Initialize the layout.
265        setContentView(R.layout.sample_media_router);
266
267        TabHost tabHost=(TabHost)findViewById(R.id.tabHost);
268        tabHost.setup();
269        String tabName = getResources().getString(R.string.library_tab_text);
270        TabSpec spec1=tabHost.newTabSpec(tabName);
271        spec1.setContent(R.id.tab1);
272        spec1.setIndicator(tabName);
273
274        tabName = getResources().getString(R.string.playlist_tab_text);
275        TabSpec spec2=tabHost.newTabSpec(tabName);
276        spec2.setIndicator(tabName);
277        spec2.setContent(R.id.tab2);
278
279        tabName = getResources().getString(R.string.info_tab_text);
280        TabSpec spec3=tabHost.newTabSpec(tabName);
281        spec3.setIndicator(tabName);
282        spec3.setContent(R.id.tab3);
283
284        tabHost.addTab(spec1);
285        tabHost.addTab(spec2);
286        tabHost.addTab(spec3);
287        tabHost.setOnTabChangedListener(new OnTabChangeListener() {
288            @Override
289            public void onTabChanged(String arg0) {
290                updateUi();
291            }
292        });
293
294        mLibraryView = (ListView) findViewById(R.id.media);
295        mLibraryView.setAdapter(mLibraryItems);
296        mLibraryView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
297        mLibraryView.setOnItemClickListener(new OnItemClickListener() {
298            @Override
299            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
300                updateButtons();
301            }
302        });
303
304        mPlayListView = (ListView) findViewById(R.id.playlist);
305        mPlayListView.setAdapter(mPlayListItems);
306        mPlayListView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
307        mPlayListView.setOnItemClickListener(new OnItemClickListener() {
308            @Override
309            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
310                updateButtons();
311            }
312        });
313
314        mInfoTextView = (TextView) findViewById(R.id.info);
315
316        mPauseResumeButton = (ImageButton)findViewById(R.id.pause_resume_button);
317        mPauseResumeButton.setOnClickListener(new OnClickListener() {
318            @Override
319            public void onClick(View v) {
320                if (mSessionManager.isPaused()) {
321                    mSessionManager.resume();
322                } else {
323                    mSessionManager.pause();
324                }
325            }
326        });
327
328        mStopButton = (ImageButton)findViewById(R.id.stop_button);
329        mStopButton.setOnClickListener(new OnClickListener() {
330            @Override
331            public void onClick(View v) {
332                mSessionManager.stop();
333            }
334        });
335
336        mSeekBar = (SeekBar) findViewById(R.id.seekbar);
337        mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
338            @Override
339            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
340                PlaylistItem item = getCheckedPlaylistItem();
341                if (fromUser && item != null && item.getDuration() > 0) {
342                    long pos = progress * item.getDuration() / 100;
343                    mSessionManager.seek(item.getItemId(), pos);
344                    item.setPosition(pos);
345                    item.setTimestamp(SystemClock.elapsedRealtime());
346                }
347            }
348            @Override
349            public void onStartTrackingTouch(SeekBar seekBar) {
350                mSeeking = true;
351            }
352            @Override
353            public void onStopTrackingTouch(SeekBar seekBar) {
354                mSeeking = false;
355                updateUi();
356            }
357        });
358
359        // Schedule Ui update
360        mHandler.postDelayed(mUpdateSeekRunnable, 1000);
361
362        // Build the PendingIntent for the remote control client
363        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
364        mEventReceiver = new ComponentName(getPackageName(),
365                SampleMediaButtonReceiver.class.getName());
366        Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
367        mediaButtonIntent.setComponent(mEventReceiver);
368        mMediaPendingIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, 0);
369
370        // Create and register the remote control client
371        createMediaSession();
372        mMediaRouter.setMediaSessionCompat(mMediaSession);
373
374        // Set up playback manager and player
375        mPlayer = Player.create(SampleMediaRouterActivity.this,
376                mMediaRouter.getSelectedRoute(), mMediaSession);
377
378        mSessionManager.setPlayer(mPlayer);
379        mSessionManager.setCallback(new SessionManager.Callback() {
380            @Override
381            public void onStatusChanged() {
382                updateUi();
383            }
384
385            @Override
386            public void onItemChanged(PlaylistItem item) {
387            }
388        });
389
390        updateUi();
391    }
392
393    private void createMediaSession() {
394        // Create the MediaSession
395        mMediaSession = new MediaSessionCompat(this, "SampleMediaRouter", mEventReceiver,
396                mMediaPendingIntent);
397        mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
398                | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
399        mMediaSession.setCallback(new MediaSessionCompat.Callback() {
400            @Override
401            public boolean onMediaButtonEvent(Intent mediaButtonEvent) {
402                if (mediaButtonEvent != null) {
403                    return handleMediaKey(
404                            (KeyEvent) mediaButtonEvent.getParcelableExtra(Intent.EXTRA_KEY_EVENT));
405                }
406                return super.onMediaButtonEvent(mediaButtonEvent);
407            }
408
409            @Override
410            public void onPlay() {
411                mSessionManager.resume();
412            }
413
414            @Override
415            public void onPause() {
416                mSessionManager.pause();
417            }
418        });
419
420        SampleMediaButtonReceiver.setActivity(SampleMediaRouterActivity.this);
421    }
422
423    public boolean handleMediaKey(KeyEvent event) {
424        if (event != null && event.getAction() == KeyEvent.ACTION_DOWN
425                && event.getRepeatCount() == 0) {
426            switch (event.getKeyCode()) {
427                case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
428                case KeyEvent.KEYCODE_HEADSETHOOK:
429                {
430                    Log.d(TAG, "Received Play/Pause event from RemoteControlClient");
431                    if (mSessionManager.isPaused()) {
432                        mSessionManager.resume();
433                    } else {
434                        mSessionManager.pause();
435                    }
436                    return true;
437                }
438                case KeyEvent.KEYCODE_MEDIA_PLAY:
439                {
440                    Log.d(TAG, "Received Play event from RemoteControlClient");
441                    if (mSessionManager.isPaused()) {
442                        mSessionManager.resume();
443                    }
444                    return true;
445                }
446                case KeyEvent.KEYCODE_MEDIA_PAUSE:
447                {
448                    Log.d(TAG, "Received Pause event from RemoteControlClient");
449                    if (!mSessionManager.isPaused()) {
450                        mSessionManager.pause();
451                    }
452                    return true;
453                }
454                case KeyEvent.KEYCODE_MEDIA_STOP:
455                {
456                    Log.d(TAG, "Received Stop event from RemoteControlClient");
457                    mSessionManager.stop();
458                    return true;
459                }
460                default:
461                    break;
462            }
463        }
464        return false;
465    }
466
467    @Override
468    public boolean onKeyDown(int keyCode, KeyEvent event) {
469        return handleMediaKey(event) || super.onKeyDown(keyCode, event);
470    }
471
472    @Override
473    public boolean onKeyUp(int keyCode, KeyEvent event) {
474        return handleMediaKey(event) || super.onKeyUp(keyCode, event);
475    }
476
477    @Override
478    public void onStart() {
479        // Be sure to call the super class.
480        super.onStart();
481    }
482
483    @Override
484    public void onPause() {
485        // pause media player for local playback case only
486        if (!mPlayer.isRemotePlayback() && !mSessionManager.isPaused()) {
487            mNeedResume = true;
488            mSessionManager.pause();
489        }
490        super.onPause();
491    }
492
493    @Override
494    public void onResume() {
495        // resume media player for local playback case only
496        if (!mPlayer.isRemotePlayback() && mNeedResume) {
497            mSessionManager.resume();
498            mNeedResume = false;
499        }
500        super.onResume();
501    }
502
503    @Override
504    public void onDestroy() {
505        mSessionManager.stop();
506        mPlayer.release();
507        mMediaSession.release();
508        super.onDestroy();
509    }
510
511    @Override
512    public boolean onCreateOptionsMenu(Menu menu) {
513        // Be sure to call the super class.
514        super.onCreateOptionsMenu(menu);
515
516        // Inflate the menu and configure the media router action provider.
517        getMenuInflater().inflate(R.menu.sample_media_router_menu, menu);
518
519        MenuItem mediaRouteMenuItem = menu.findItem(R.id.media_route_menu_item);
520        MediaRouteActionProvider mediaRouteActionProvider =
521                (MediaRouteActionProvider)MenuItemCompat.getActionProvider(mediaRouteMenuItem);
522        mediaRouteActionProvider.setRouteSelector(mSelector);
523        mediaRouteActionProvider.setDialogFactory(new MediaRouteDialogFactory() {
524            @Override
525            public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
526                return new ControllerDialogFragment(mPlayer);
527            }
528        });
529
530        // Return true to show the menu.
531        return true;
532    }
533
534    private void updateProgress() {
535        // Estimate content position from last status time and elapsed time.
536        // (Note this might be slightly out of sync with remote side, however
537        // it avoids frequent polling the MRP.)
538        int progress = 0;
539        PlaylistItem item = getCheckedPlaylistItem();
540        if (item != null) {
541            int state = item.getState();
542            long duration = item.getDuration();
543            if (duration <= 0) {
544                if (state == MediaItemStatus.PLAYBACK_STATE_PLAYING
545                        || state == MediaItemStatus.PLAYBACK_STATE_PAUSED) {
546                    mSessionManager.updateStatus();
547                }
548            } else {
549                long position = item.getPosition();
550                long timeDelta = mSessionManager.isPaused() ? 0 :
551                        (SystemClock.elapsedRealtime() - item.getTimestamp());
552                progress = (int)(100.0 * (position + timeDelta) / duration);
553            }
554        }
555        mSeekBar.setProgress(progress);
556    }
557
558    private void updateUi() {
559        updatePlaylist();
560        updateRouteDescription();
561        updateButtons();
562        if (mPlayer != null) {
563            mPlayer.updateMetadata();
564        }
565    }
566
567    private void updatePlaylist() {
568        mPlayListItems.clear();
569        for (PlaylistItem item : mSessionManager.getPlaylist()) {
570            mPlayListItems.add(item);
571        }
572        mPlayListView.invalidate();
573    }
574
575    private void updateRouteDescription() {
576        RouteInfo route = mMediaRouter.getSelectedRoute();
577        mInfoTextView.setText("Currently selected route:"
578                + "\nName: " + route.getName()
579                + "\nProvider: " + route.getProvider().getPackageName()
580                + "\nDescription: " + route.getDescription());
581    }
582
583    private void updateButtons() {
584        MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute();
585        // show pause or resume icon depending on current state
586        mPauseResumeButton.setImageResource(mSessionManager.isPaused() ?
587                R.drawable.ic_media_play : R.drawable.ic_media_pause);
588        // only enable seek bar when duration is known
589        PlaylistItem item = getCheckedPlaylistItem();
590        mSeekBar.setEnabled(item != null && item.getDuration() > 0);
591    }
592
593    private PlaylistItem getCheckedPlaylistItem() {
594        int count = mPlayListView.getCount();
595        int index = mPlayListView.getCheckedItemPosition();
596        if (count > 0) {
597            if (index < 0 || index >= count) {
598                index = 0;
599                mPlayListView.setItemChecked(0, true);
600            }
601            return mPlayListItems.getItem(index);
602        }
603        return null;
604    }
605
606    public static final class DiscoveryFragment extends MediaRouteDiscoveryFragment {
607        private static final String TAG = "DiscoveryFragment";
608        private Callback mCallback;
609
610        public DiscoveryFragment() {
611            mCallback = null;
612        }
613
614        public DiscoveryFragment(Callback cb) {
615            mCallback = cb;
616        }
617
618        public void setCallback(Callback cb) {
619            mCallback = cb;
620        }
621
622        @Override
623        public Callback onCreateCallback() {
624            return mCallback;
625        }
626
627        @Override
628        public int onPrepareCallbackFlags() {
629            // Add the CALLBACK_FLAG_UNFILTERED_EVENTS flag to ensure that we will
630            // observe and log all route events including those that are for routes
631            // that do not match our selector.  This is only for demonstration purposes
632            // and should not be needed by most applications.
633            return super.onPrepareCallbackFlags()
634                    | MediaRouter.CALLBACK_FLAG_UNFILTERED_EVENTS;
635        }
636    }
637
638    private static final class MediaItem {
639        public final String mName;
640        public final Uri mUri;
641        public final String mMime;
642
643        public MediaItem(String name, Uri uri, String mime) {
644            mName = name;
645            mUri = uri;
646            mMime = mime;
647        }
648
649        @Override
650        public String toString() {
651            return mName;
652        }
653    }
654
655    private final class LibraryAdapter extends ArrayAdapter<MediaItem> {
656        public LibraryAdapter() {
657            super(SampleMediaRouterActivity.this, R.layout.media_item);
658        }
659
660        @Override
661        public View getView(int position, View convertView, ViewGroup parent) {
662            final View v;
663            if (convertView == null) {
664                v = getLayoutInflater().inflate(R.layout.media_item, null);
665            } else {
666                v = convertView;
667            }
668
669            final MediaItem item = getItem(position);
670
671            TextView tv = (TextView)v.findViewById(R.id.item_text);
672            tv.setText(item.mName);
673
674            ImageButton b = (ImageButton)v.findViewById(R.id.item_action);
675            b.setImageResource(R.drawable.ic_menu_add);
676            b.setTag(item);
677            b.setOnClickListener(new OnClickListener() {
678                @Override
679                public void onClick(View v) {
680                    if (item != null) {
681                        mSessionManager.add(item.mUri, item.mMime);
682                    }
683                }
684            });
685
686            return v;
687        }
688    }
689
690    private final class PlaylistAdapter extends ArrayAdapter<PlaylistItem> {
691        public PlaylistAdapter() {
692            super(SampleMediaRouterActivity.this, R.layout.media_item);
693        }
694
695        @Override
696        public View getView(int position, View convertView, ViewGroup parent) {
697            final View v;
698            if (convertView == null) {
699                v = getLayoutInflater().inflate(R.layout.media_item, null);
700            } else {
701                v = convertView;
702            }
703
704            final PlaylistItem item = getItem(position);
705
706            TextView tv = (TextView)v.findViewById(R.id.item_text);
707            tv.setText(item.toString());
708
709            ImageButton b = (ImageButton)v.findViewById(R.id.item_action);
710            b.setImageResource(R.drawable.ic_menu_delete);
711            b.setTag(item);
712            b.setOnClickListener(new OnClickListener() {
713                @Override
714                public void onClick(View v) {
715                    if (item != null) {
716                        mSessionManager.remove(item.getItemId());
717                    }
718                }
719            });
720
721            return v;
722        }
723    }
724
725    /**
726     * Trivial subclass of this activity used to provide another copy of the
727     * same activity using a light theme instead of the dark theme.
728     */
729    public static class Light extends SampleMediaRouterActivity {
730    }
731
732    /**
733     * Trivial subclass of this activity used to provide another copy of the
734     * same activity using a light theme with dark action bar instead of the dark theme.
735     */
736    public static class LightWithDarkActionBar extends SampleMediaRouterActivity {
737    }
738
739    public static class ControllerDialogFragment extends MediaRouteControllerDialogFragment {
740        private MediaRouteControllerDialog mControllerDialog;
741        private Player mPlayer;
742
743        public ControllerDialogFragment() {
744            super();
745        }
746
747        public ControllerDialogFragment(Player player) {
748            mPlayer = player;
749        }
750
751        @Override
752        public MediaRouteControllerDialog onCreateControllerDialog(
753                Context context, Bundle savedInstanceState) {
754            mControllerDialog = super.onCreateControllerDialog(context,
755                    savedInstanceState);
756            if (mPlayer != null) {
757                mControllerDialog.setMediaSession(mPlayer.getMediaSession().getSessionToken());
758            }
759            mControllerDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
760                @Override
761                public void onDismiss(DialogInterface dialog) {
762                    mControllerDialog = null;
763                }
764            });
765            return mControllerDialog;
766        }
767
768        public void setPlayer(Player player) {
769            mPlayer = player;
770            if (mControllerDialog != null) {
771                mControllerDialog.setMediaSession(mPlayer.getMediaSession().getSessionToken());
772            }
773        }
774    }
775}
776