• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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 package com.android.car.media;
17 
18 import android.annotation.NonNull;
19 import android.car.Car;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.SharedPreferences;
24 import android.graphics.Bitmap;
25 import android.media.session.MediaController;
26 import android.os.Bundle;
27 import android.support.v4.app.Fragment;
28 import android.support.v4.app.FragmentManager;
29 import android.support.v4.widget.DrawerLayout;
30 import android.transition.Fade;
31 import android.util.Log;
32 import android.util.TypedValue;
33 import android.view.View;
34 import android.view.ViewGroup;
35 
36 import com.android.car.media.common.ActiveMediaSourceManager;
37 import com.android.car.media.common.CrossfadeImageView;
38 import com.android.car.media.common.MediaItemMetadata;
39 import com.android.car.media.common.MediaSource;
40 import com.android.car.media.common.MediaSourcesManager;
41 import com.android.car.media.common.PlaybackControls;
42 import com.android.car.media.common.PlaybackModel;
43 import com.android.car.media.drawer.MediaDrawerController;
44 import com.android.car.media.widgets.AppBarView;
45 import com.android.car.media.widgets.MetadataView;
46 import com.android.car.media.widgets.ViewUtils;
47 
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.Objects;
51 import java.util.stream.Collectors;
52 
53 import androidx.car.drawer.CarDrawerActivity;
54 import androidx.car.drawer.CarDrawerAdapter;
55 
56 /**
57  * This activity controls the UI of media. It also updates the connection status for the media app
58  * by broadcast. Drawer menu is controlled by {@link MediaDrawerController}.
59  */
60 public class MediaActivity extends CarDrawerActivity implements BrowseFragment.Callbacks,
61         AppSelectionFragment.Callbacks, PlaybackFragment.Callbacks {
62     private static final String TAG = "MediaActivity";
63 
64     /** Intent extra specifying the package with the MediaBrowser */
65     public static final String KEY_MEDIA_PACKAGE = "media_package";
66     /** Shared preferences files */
67     public static final String SHARED_PREF = "com.android.car.media";
68     /** Shared preference containing the last controlled source */
69     public static final String LAST_MEDIA_SOURCE_SHARED_PREF_KEY = "last_media_source";
70 
71     /** Configuration (controlled from resources) */
72     private boolean mContentForwardBrowseEnabled;
73     private float mBackgroundBlurRadius;
74     private float mBackgroundBlurScale;
75     private int mFadeDuration;
76 
77     /** Models */
78     private MediaDrawerController mDrawerController;
79     private ActiveMediaSourceManager mActiveMediaSourceManager;
80     private MediaSource mMediaSource;
81     private PlaybackModel mPlaybackModel;
82     private MediaSourcesManager mMediaSourcesManager;
83     private SharedPreferences mSharedPreferences;
84 
85     /** Layout views */
86     private AppBarView mAppBarView;
87     private CrossfadeImageView mAlbumBackground;
88     private PlaybackFragment mPlaybackFragment;
89     private AppSelectionFragment mAppSelectionFragment;
90     private PlaybackControls mPlaybackControls;
91     private MetadataView mMetadataView;
92     private ViewGroup mBrowseControlsContainer;
93     private EmptyFragment mEmptyFragment;
94     private ViewGroup mBrowseContainer;
95     private ViewGroup mPlaybackContainer;
96 
97     /** Current state */
98     private Fragment mCurrentFragment;
99     private Mode mMode = Mode.BROWSING;
100     private boolean mIsAppSelectorOpen;
101     private MediaItemMetadata mCurrentMetadata;
102 
103     private MediaSource.Observer mMediaSourceObserver = new MediaSource.Observer() {
104         @Override
105         protected void onBrowseConnected(boolean success) {
106             MediaActivity.this.onBrowseConnected(success);
107         }
108 
109         @Override
110         protected void onBrowseDisconnected() {
111             MediaActivity.this.onBrowseConnected(false);
112         }
113     };
114     private PlaybackModel.PlaybackObserver mPlaybackObserver =
115             new PlaybackModel.PlaybackObserver() {
116         @Override
117         protected void onSourceChanged() {
118             updateMetadata();
119         }
120 
121         @Override
122         public void onMetadataChanged() {
123             mCurrentMetadata = null;
124             updateMetadata();
125         }
126     };
127     private ActiveMediaSourceManager.Observer mActiveSourceObserver = () -> {
128         // If the active media source changes and it is the one currently being browsed, then
129         // we should capture the controller.
130         MediaController controller = mActiveMediaSourceManager.getMediaController();
131         if (mPlaybackModel.getMediaController() == null
132                 && mMediaSource != null
133                 && controller != null
134                 && Objects.equals(controller.getPackageName(), mMediaSource.getPackageName())) {
135             mPlaybackModel.setMediaController(controller);
136         }
137     };
138     private MediaSource.ItemsSubscription mItemsSubscription =
139             new MediaSource.ItemsSubscription() {
140         @Override
141         public void onChildrenLoaded(MediaSource mediaSource, String parentId,
142                 List<MediaItemMetadata> items) {
143             if (mediaSource == mMediaSource) {
144                 updateTabs(items);
145             } else {
146                 Log.w(TAG, "Received items for a wrong source: " +
147                         mediaSource.getPackageName());
148             }
149         }
150     };
151     private AppBarView.AppBarListener mAppBarListener = new AppBarView.AppBarListener() {
152         @Override
153         public void onTabSelected(MediaItemMetadata item) {
154             updateBrowseFragment(BrowseState.LOADED, item);
155             switchToMode(Mode.BROWSING);
156         }
157 
158         @Override
159         public void onBack() {
160             if (mCurrentFragment != null && mCurrentFragment instanceof BrowseFragment) {
161                 BrowseFragment fragment = (BrowseFragment) mCurrentFragment;
162                 fragment.navigateBack();
163             }
164         }
165 
166         @Override
167         public void onCollapse() {
168             switchToMode(Mode.BROWSING);
169         }
170 
171         @Override
172         public void onAppSelection() {
173             Log.d(TAG, "onAppSelection clicked");
174             if (mIsAppSelectorOpen) {
175                 closeAppSelector();
176             } else {
177                 openAppSelector();
178             }
179         }
180     };
181     private MediaSourcesManager.Observer mMediaSourcesManagerObserver = () -> {
182         mAppBarView.setAppSelection(!mMediaSourcesManager.getMediaSources().isEmpty());
183         mAppSelectionFragment.refresh();
184     };
185     private DrawerLayout.DrawerListener mDrawerListener = new DrawerLayout.DrawerListener() {
186         @Override
187         public void onDrawerSlide(@android.support.annotation.NonNull View view, float v) {
188         }
189 
190         @Override
191         public void onDrawerOpened(@android.support.annotation.NonNull View view) {
192             closeAppSelector();
193         }
194 
195         @Override
196         public void onDrawerClosed(@android.support.annotation.NonNull View view) {
197         }
198 
199         @Override
200         public void onDrawerStateChanged(int i) {
201         }
202     };
203 
204     /**
205      * Possible modes of the application UI
206      */
207     private enum Mode {
208         /** The user is browsing a media source */
209         BROWSING,
210         /** The user is interacting with the full screen playback UI */
211         PLAYBACK
212     }
213 
214     /**
215      * Possible states of the application UI
216      */
217     public enum BrowseState {
218         /** There is no content to show */
219         EMPTY,
220         /** We are still in the process of obtaining data */
221         LOADING,
222         /** Data has been loaded */
223         LOADED,
224         /** The content can't be shown due an error */
225         ERROR
226     }
227 
228     @Override
onCreate(Bundle savedInstanceState)229     protected void onCreate(Bundle savedInstanceState) {
230         super.onCreate(savedInstanceState);
231 
232         setMainContent(R.layout.media_activity);
233         setToolbarElevation(0f);
234 
235         mContentForwardBrowseEnabled = getResources()
236                 .getBoolean(R.bool.forward_content_browse_enabled);
237         mDrawerController = new MediaDrawerController(this, getDrawerController());
238         getDrawerController().setRootAdapter(getRootAdapter());
239         getDrawerController().addDrawerListener(mDrawerListener);
240         if (mContentForwardBrowseEnabled) {
241             getSupportActionBar().hide();
242         }
243         mAppBarView = findViewById(R.id.app_bar);
244         mAppBarView.setListener(mAppBarListener);
245         mAppBarView.setContentForwardEnabled(mContentForwardBrowseEnabled);
246         mPlaybackFragment = new PlaybackFragment();
247         mAppSelectionFragment = new AppSelectionFragment();
248         int fadeDuration = getResources().getInteger(R.integer.app_selector_fade_duration);
249         mAppSelectionFragment.setEnterTransition(new Fade().setDuration(fadeDuration));
250         mAppSelectionFragment.setExitTransition(new Fade().setDuration(fadeDuration));
251         mActiveMediaSourceManager = new ActiveMediaSourceManager(this);
252         mPlaybackModel = new PlaybackModel(this);
253         mMediaSourcesManager = new MediaSourcesManager(this);
254         mAlbumBackground = findViewById(R.id.media_background);
255         mPlaybackControls = findViewById(R.id.browse_controls);
256         mPlaybackControls.setModel(mPlaybackModel);
257         mMetadataView = findViewById(R.id.browse_metadata);
258         mMetadataView.setModel(mPlaybackModel);
259         mBrowseControlsContainer = findViewById(R.id.browse_controls_container);
260         mBrowseControlsContainer.setOnClickListener(view -> switchToMode(Mode.PLAYBACK));
261         TypedValue outValue = new TypedValue();
262         getResources().getValue(R.dimen.playback_background_blur_radius, outValue, true);
263         mBackgroundBlurRadius = outValue.getFloat();
264         getResources().getValue(R.dimen.playback_background_blur_scale, outValue, true);
265         mBackgroundBlurScale = outValue.getFloat();
266         mSharedPreferences = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
267         mFadeDuration = getResources().getInteger(
268                 R.integer.new_album_art_fade_in_duration);
269         mEmptyFragment = new EmptyFragment();
270         mBrowseContainer = findViewById(R.id.fragment_container);
271         mPlaybackContainer = findViewById(R.id.playback_container);
272         getSupportFragmentManager().beginTransaction()
273                 .replace(R.id.playback_container, mPlaybackFragment)
274                 .commit();
275     }
276 
277     @Override
onResume()278     public void onResume() {
279         super.onResume();
280         mPlaybackModel.registerObserver(mPlaybackObserver);
281         mActiveMediaSourceManager.registerObserver(mActiveSourceObserver);
282         mMediaSourcesManager.registerObserver(mMediaSourcesManagerObserver);
283         handleIntent();
284     }
285 
286     @Override
onPause()287     public void onPause() {
288         super.onPause();
289         mPlaybackModel.unregisterObserver(mPlaybackObserver);
290         mActiveMediaSourceManager.unregisterObserver(mActiveSourceObserver);
291         mMediaSourcesManager.unregisterObserver(mMediaSourcesManagerObserver);
292     }
293 
294     @Override
onDestroy()295     public void onDestroy() {
296         super.onDestroy();
297         mDrawerController.cleanup();
298         mPlaybackControls.setModel(null);
299         mMetadataView.setModel(null);
300     }
301 
302     @Override
getRootAdapter()303     protected CarDrawerAdapter getRootAdapter() {
304         return mDrawerController == null ? null : mDrawerController.getRootAdapter();
305     }
306 
307     @Override
onNewIntent(Intent intent)308     protected void onNewIntent(Intent intent) {
309         super.onNewIntent(intent);
310         if (Log.isLoggable(TAG, Log.VERBOSE)) {
311             Log.v(TAG, "onNewIntent(); intent: " + (intent == null ? "<< NULL >>" : intent));
312         }
313 
314         setIntent(intent);
315         handleIntent();
316     }
317 
318     @Override
onBackPressed()319     public void onBackPressed() {
320         mPlaybackFragment.closeOverflowMenu();
321         super.onBackPressed();
322     }
323 
onBrowseConnected(boolean success)324     private void onBrowseConnected(boolean success) {
325         if (!success) {
326             updateTabs(null);
327             mMediaSource.unsubscribeChildren(null, mItemsSubscription);
328             mMediaSource.unsubscribe(mMediaSourceObserver);
329             updateBrowseFragment(BrowseState.ERROR, null);
330             return;
331         }
332         mMediaSource.subscribeChildren(null, mItemsSubscription);
333         if (mPlaybackModel.getMediaController() == null) {
334             mPlaybackModel.setMediaController(mMediaSource.getMediaController());
335         }
336     }
337 
handleIntent()338     private void handleIntent() {
339         Intent intent = getIntent();
340         String action = intent != null ? intent.getAction() : null;
341 
342         getDrawerController().closeDrawer();
343 
344         if (Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE.equals(action)) {
345             // The user either wants to browse a particular media source or switch to the
346             // playback UI.
347             String packageName = intent.getStringExtra(KEY_MEDIA_PACKAGE);
348             if (packageName != null) {
349                 // We were told to navigate to a particular package: we open browse for it.
350                 closeAppSelector();
351                 changeMediaSource(new MediaSource(this, packageName), null);
352                 switchToMode(Mode.BROWSING);
353                 return;
354             }
355 
356             // If we didn't receive a package name and we are playing something: show the playback
357             // UI for the playing media source.
358             MediaController controller = mActiveMediaSourceManager.getMediaController();
359             if (controller != null) {
360                 closeAppSelector();
361                 changeMediaSource(new MediaSource(this, controller.getPackageName()), controller);
362                 switchToMode(Mode.PLAYBACK);
363                 return;
364             }
365         }
366 
367         // In any other case, if we were already browsing something: just close drawers/overlays
368         // and display what we have.
369         if (mMediaSource != null) {
370             closeAppSelector();
371             return;
372         }
373 
374         // If we don't have a current media source, we try with the last one we remember.
375         MediaSource lastMediaSource = getLastMediaSource();
376         if (lastMediaSource != null) {
377             closeAppSelector();
378             changeMediaSource(lastMediaSource, null);
379             switchToMode(Mode.BROWSING);
380         } else {
381             // If we don't have anything from before: open the app selector.
382             openAppSelector();
383         }
384     }
385 
386     /**
387      * Sets the media source being browsed.
388      *
389      * @param mediaSource the media source we are going to try to browse
390      * @param controller a controller we can use to control the playback state of the given
391      *                   source. If not provided, we will try to obtain it from the session manager.
392      *                   Otherwise, we will obtain a controller once the media browser is connected.
393      */
changeMediaSource(MediaSource mediaSource, MediaController controller)394     private void changeMediaSource(MediaSource mediaSource, MediaController controller) {
395         if (Objects.equals(mediaSource, mMediaSource)) {
396             // No change, nothing to do.
397             return;
398         }
399         if (mMediaSource != null) {
400             mMediaSource.unsubscribeChildren(null, mItemsSubscription);
401             mMediaSource.unsubscribe(mMediaSourceObserver);
402             updateTabs(new ArrayList<>());
403         }
404         mMediaSource = mediaSource;
405         mPlaybackModel.setMediaController(controller != null ? controller
406                 : mActiveMediaSourceManager.getControllerForPackage(mediaSource.getPackageName()));
407         setLastMediaSource(mMediaSource);
408         if (mMediaSource != null) {
409             if (Log.isLoggable(TAG, Log.INFO)) {
410                 Log.i(TAG, "Browsing: " + mediaSource.getName());
411             }
412             // Prepare the media source for playback
413             mPlaybackModel.onPrepare();
414             // Make the drawer display browse information of the selected source
415             ComponentName component = mMediaSource.getBrowseServiceComponentName();
416             MediaManager.getInstance(this).setMediaClientComponent(component);
417             // If content forward browsing is disabled, then no need to subscribe to this media
418             // source, we will use the drawer instead.
419             if (mContentForwardBrowseEnabled) {
420                 Log.i(TAG, "Content forward is enabled: subscribing to " +
421                         mMediaSource.getPackageName());
422                 updateBrowseFragment(BrowseState.LOADING, null);
423                 mMediaSource.subscribe(mMediaSourceObserver);
424             }
425             mAppBarView.setAppIcon(mMediaSource.getRoundPackageIcon());
426             mAppBarView.setTitle(mMediaSource.getName());
427         } else {
428             mAppBarView.setAppIcon(null);
429             mAppBarView.setTitle(null);
430         }
431     }
432 
isCurrentMediaSourcePlaying()433     private boolean isCurrentMediaSourcePlaying() {
434         return mMediaSource != null
435                 && mActiveMediaSourceManager.isPlaying(mMediaSource.getPackageName());
436     }
437 
438     /**
439      * Updates the tabs displayed on the app bar, based on the top level items on the browse tree.
440      * If there is at least one browsable item, we show the browse content of that node.
441      * If there are only playable items, then we show those items.
442      * If there are not items at all, we show the empty message.
443      * If we receive null, we show the error message.
444      *
445      * @param items top level items, or null if there was an error trying load those items.
446      */
updateTabs(List<MediaItemMetadata> items)447     private void updateTabs(List<MediaItemMetadata> items) {
448         if (items == null || items.isEmpty()) {
449             mAppBarView.setItems(null);
450             updateBrowseFragment(items == null ? BrowseState.ERROR : BrowseState.EMPTY, null);
451             return;
452         }
453 
454         items = customizeTabs(mMediaSource, items);
455         List<MediaItemMetadata> browsableTopLevel = items.stream()
456                 .filter(item -> item.isBrowsable())
457                 .collect(Collectors.toList());
458 
459         if (!browsableTopLevel.isEmpty()) {
460             // If we have at least a few browsable items, we show the tabs
461             mAppBarView.setItems(browsableTopLevel);
462             updateBrowseFragment(BrowseState.LOADED, browsableTopLevel.get(0));
463         } else {
464             // Otherwise, we show the top of the tree with no fabs
465             mAppBarView.setItems(null);
466             updateBrowseFragment(BrowseState.LOADED, null);
467         }
468     }
469 
470     /**
471      * Extension point used to customize media items displayed on the tabs.
472      *
473      * @param mediaSource media source these items belong to.
474      * @param items items to override.
475      * @return an updated list of items.
476      * @deprecated This method will be removed on b/79089344
477      */
478     @Deprecated
customizeTabs(MediaSource mediaSource, List<MediaItemMetadata> items)479     protected List<MediaItemMetadata> customizeTabs(MediaSource mediaSource,
480             List<MediaItemMetadata> items) {
481         return items;
482     }
483 
switchToMode(Mode mode)484     private void switchToMode(Mode mode) {
485         // If content forward is not enable, then we always show the playback UI (browse will be
486         // done in the drawer)
487         mMode = mContentForwardBrowseEnabled ? mode : Mode.PLAYBACK;
488         updateMetadata();
489         switch (mMode) {
490             case PLAYBACK:
491                 ViewUtils.showViewAnimated(mPlaybackContainer, mFadeDuration);
492                 ViewUtils.hideViewAnimated(mBrowseContainer, mFadeDuration);
493                 mAppBarView.setState(AppBarView.State.PLAYING);
494                 break;
495             case BROWSING:
496                 ViewUtils.hideViewAnimated(mPlaybackContainer, mFadeDuration);
497                 ViewUtils.showViewAnimated(mBrowseContainer, mFadeDuration);
498                 mAppBarView.setState(AppBarView.State.BROWSING);
499                 break;
500         }
501     }
502 
503     /**
504      * Updates the browse area with either a loading state, the root node content, or the
505      * content of a particular media item.
506      *
507      * @param state state in the process of loading browse information.
508      * @param topItem if state == IDLE, this will contain the item to display,
509      *                or null to display the root node.
510      */
updateBrowseFragment(BrowseState state, MediaItemMetadata topItem)511     private void updateBrowseFragment(BrowseState state, MediaItemMetadata topItem) {
512         switch(state) {
513             case LOADED:
514                 if (topItem != null) {
515                     mCurrentFragment = BrowseFragment.newInstance(mMediaSource, topItem);
516                     mAppBarView.setActiveItem(topItem);
517                 } else {
518                     mCurrentFragment = BrowseFragment.newInstance(mMediaSource, null);
519                     mAppBarView.setActiveItem(null);
520                 }
521                 break;
522             case EMPTY:
523             case LOADING:
524             case ERROR:
525                 mCurrentFragment = mEmptyFragment;
526                 mEmptyFragment.setState(state, mMediaSource);
527                 mAppBarView.setActiveItem(null);
528                 break;
529         }
530         getSupportFragmentManager().beginTransaction()
531                 .replace(R.id.fragment_container, mCurrentFragment)
532                 .commitAllowingStateLoss();
533     }
534 
updateMetadata()535     private void updateMetadata() {
536         if (isCurrentMediaSourcePlaying()) {
537             if (mMode == Mode.PLAYBACK) {
538                 ViewUtils.hideViewAnimated(mBrowseControlsContainer, mFadeDuration);
539             } else {
540                 ViewUtils.showViewAnimated(mBrowseControlsContainer, mFadeDuration);
541             }
542             MediaItemMetadata metadata = mPlaybackModel.getMetadata();
543             if (Objects.equals(mCurrentMetadata, metadata)) {
544                 return;
545             }
546             mCurrentMetadata = metadata;
547             mUpdateAlbumArtRunnable.run();
548         } else {
549             mAlbumBackground.setImageBitmap(null, true);
550             ViewUtils.hideViewAnimated(mBrowseControlsContainer, mFadeDuration);
551         }
552     }
553 
554     /**
555      * We might receive new album art before we are ready to display it. If that situation happens
556      * we will retrieve and render the album art when the views are already laid out.
557      */
558     private Runnable mUpdateAlbumArtRunnable = new Runnable() {
559         @Override
560         public void run() {
561             MediaItemMetadata metadata = mPlaybackModel.getMetadata();
562             if (metadata != null) {
563                 if (mAlbumBackground.getWidth() == 0 || mAlbumBackground.getHeight() == 0) {
564                     // We need to wait for the view to be measured before we can render this
565                     // album art.
566                     mAlbumBackground.setImageBitmap(null, false);
567                     mAlbumBackground.post(this);
568                 } else {
569                     mAlbumBackground.removeCallbacks(this);
570                     metadata.getAlbumArt(MediaActivity.this,
571                             mAlbumBackground.getWidth(),
572                             mAlbumBackground.getHeight(),
573                             false)
574                             .thenAccept(bitmap -> setBackgroundImage(bitmap));
575                 }
576             } else {
577                 mAlbumBackground.removeCallbacks(this);
578                 mAlbumBackground.setImageBitmap(null, true);
579             }
580         }
581     };
582 
setBackgroundImage(Bitmap bitmap)583     private void setBackgroundImage(Bitmap bitmap) {
584         // TODO(b/77551865): Implement image blurring once the following issue is solved:
585         // b/77551557
586         // bitmap = ImageUtils.blur(getContext(), bitmap, mBackgroundBlurScale,
587         //        mBackgroundBlurRadius);
588         mAlbumBackground.setImageBitmap(bitmap, true);
589     }
590 
591     @Override
getMediaSource(String packageName)592     public MediaSource getMediaSource(String packageName) {
593         if (mMediaSource != null && mMediaSource.getPackageName().equals(packageName)) {
594             return mMediaSource;
595         }
596         return new MediaSource(this, packageName);
597     }
598 
599     @Override
onBackStackChanged()600     public void onBackStackChanged() {
601         // TODO: Update ActionBar
602     }
603 
604     @Override
onPlayableItemClicked(MediaSource mediaSource, MediaItemMetadata item)605     public void onPlayableItemClicked(MediaSource mediaSource, MediaItemMetadata item) {
606         mPlaybackModel.onStop();
607         if (!Objects.equals(mediaSource, mPlaybackModel.getMediaSource())) {
608             Log.w(TAG, "Trying to play an item from a different source "
609                 + "(expected: " + mPlaybackModel.getMediaSource() + ", received"
610                 + mediaSource + ")");
611             changeMediaSource(mediaSource, mediaSource.getMediaController());
612         }
613         mPlaybackModel.onPlayItem(item.getId());
614         setIntent(null);
615     }
616 
openAppSelector()617     private void openAppSelector() {
618         mIsAppSelectorOpen = true;
619         FragmentManager manager = getSupportFragmentManager();
620         mAppBarView.setState(AppBarView.State.APP_SELECTION);
621         manager.beginTransaction()
622                 .replace(R.id.app_selection_container, mAppSelectionFragment)
623                 .commit();
624     }
625 
closeAppSelector()626     private void closeAppSelector() {
627         mIsAppSelectorOpen = false;
628         FragmentManager manager = getSupportFragmentManager();
629         mAppBarView.setState(mMode == Mode.PLAYBACK ? AppBarView.State.PLAYING
630                 : AppBarView.State.BROWSING);
631         manager.beginTransaction()
632                 .remove(mAppSelectionFragment)
633                 .commit();
634     }
635 
636     @Override
getMediaSources()637     public List<MediaSource> getMediaSources() {
638         return mMediaSourcesManager.getMediaSources()
639                 .stream()
640                 .filter(source -> source.getMediaBrowser() != null || source.isCustom())
641                 .collect(Collectors.toList());
642     }
643 
644     @Override
onMediaSourceSelected(MediaSource mediaSource)645     public void onMediaSourceSelected(MediaSource mediaSource) {
646         closeAppSelector();
647         if (mediaSource.getMediaBrowser() != null && !mediaSource.isCustom()) {
648             mCurrentMetadata = null;
649             changeMediaSource(mediaSource, null);
650             switchToMode(Mode.BROWSING);
651         } else {
652             String packageName = mediaSource.getPackageName();
653             Intent intent = getPackageManager().getLaunchIntentForPackage(packageName);
654             startActivity(intent);
655         }
656     }
657 
getLastMediaSource()658     private MediaSource getLastMediaSource() {
659         String packageName = mSharedPreferences.getString(LAST_MEDIA_SOURCE_SHARED_PREF_KEY, null);
660         if (packageName == null) {
661             return null;
662         }
663         // Verify that the stored package name corresponds to a currently installed media source.
664         for (MediaSource mediaSource : mMediaSourcesManager.getMediaSources()) {
665             if (mediaSource.getPackageName().equals(packageName)) {
666                 return mediaSource;
667             }
668         }
669         return null;
670     }
671 
setLastMediaSource(@onNull MediaSource mediaSource)672     private void setLastMediaSource(@NonNull MediaSource mediaSource) {
673         mSharedPreferences.edit()
674                 .putString(LAST_MEDIA_SOURCE_SHARED_PREF_KEY, mediaSource.getPackageName())
675                 .apply();
676     }
677 
678 
679     @Override
getPlaybackModel()680     public PlaybackModel getPlaybackModel() {
681         return mPlaybackModel;
682     }
683 
684     @Override
onQueueButtonClicked()685     public void onQueueButtonClicked() {
686         if (mContentForwardBrowseEnabled) {
687             mPlaybackFragment.toggleQueueVisibility();
688         } else {
689             mDrawerController.showPlayQueue();
690         }
691     }
692 }
693