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