1 /* 2 * Copyright (C) 2017 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.drawer; 17 18 import android.content.ComponentName; 19 import android.content.Context; 20 import android.media.browse.MediaBrowser; 21 import android.media.session.MediaController; 22 import android.media.session.MediaSession; 23 import android.os.Bundle; 24 import android.support.annotation.Nullable; 25 import android.support.v4.widget.DrawerLayout; 26 import android.util.Log; 27 import android.view.View; 28 29 import com.android.car.media.MediaManager; 30 import com.android.car.media.MediaPlaybackModel; 31 import com.android.car.media.R; 32 33 import androidx.car.drawer.CarDrawerAdapter; 34 import androidx.car.drawer.CarDrawerController; 35 36 /** 37 * Manages drawer navigation and item selection. 38 * <p> 39 * Maintains separate MediaPlaybackModel for media browsing and control. Sets up root Drawer 40 * adapter with root of media-browse tree (using MediaBrowserItemsFetcher). Supports switching the 41 * rootAdapter to show the queue-items (using MediaQueueItemsFetcher). 42 */ 43 public class MediaDrawerController implements MediaDrawerAdapter.MediaFetchCallback, 44 MediaItemOnClickListener { 45 private static final String TAG = "MediaDrawerController"; 46 47 private static final String EXTRA_ICON_SIZE = 48 "com.google.android.gms.car.media.BrowserIconSize"; 49 50 private final Context mContext; 51 private final CarDrawerController mDrawerController; 52 private final MediaPlaybackModel mMediaPlaybackModel; 53 private MediaDrawerAdapter mRootAdapter; 54 55 public MediaDrawerController(Context context, CarDrawerController drawerController) { 56 mContext = context; 57 mDrawerController = drawerController; 58 59 Bundle extras = new Bundle(); 60 extras.putInt(EXTRA_ICON_SIZE, 61 mContext.getResources().getDimensionPixelSize(R.dimen.car_primary_icon_size)); 62 63 mMediaPlaybackModel = new MediaPlaybackModel(mContext, extras); 64 mMediaPlaybackModel.addListener(mModelListener); 65 66 mRootAdapter = new MediaDrawerAdapter(mContext, mDrawerController); 67 // Start with a empty title since we depend on the mMediaManagerListener callback to 68 // know which app is being used and set the actual title there. 69 mRootAdapter.setTitle(""); 70 mRootAdapter.setFetchCallback(this); 71 72 // Kick off MediaBrowser/MediaController connection. 73 mMediaPlaybackModel.start(); 74 } 75 76 @Override 77 public void onQueueItemClicked(MediaSession.QueueItem queueItem) { 78 MediaController.TransportControls controls = mMediaPlaybackModel.getTransportControls(); 79 80 if (controls != null) { 81 controls.skipToQueueItem(queueItem.getQueueId()); 82 } 83 84 mDrawerController.closeDrawer(); 85 } 86 87 @Override 88 public void onMediaItemClicked(MediaBrowser.MediaItem item) { 89 if (item.isBrowsable()) { 90 MediaItemsFetcher fetcher; 91 if (MediaBrowserItemsFetcher.PLAY_QUEUE_MEDIA_ID.equals(item.getMediaId())) { 92 fetcher = createMediaQueueItemsFetcher(); 93 } else { 94 fetcher = createMediaBrowserItemFetcher(item.getMediaId(), 95 false /* showQueueItem */); 96 } 97 setupAdapterAndSwitch(fetcher, item.getDescription().getTitle()); 98 } else if (item.isPlayable()) { 99 MediaController.TransportControls controls = mMediaPlaybackModel.getTransportControls(); 100 if (controls != null) { 101 controls.playFromMediaId(item.getMediaId(), item.getDescription().getExtras()); 102 } 103 mDrawerController.closeDrawer(); 104 } else { 105 Log.w(TAG, "Unknown item type; don't know how to handle!"); 106 } 107 } 108 109 @Override 110 public void onFetchStart() { 111 // Initially there will be no items and we don't want to show empty-list indicator 112 // briefly until items are fetched. 113 mDrawerController.showLoadingProgressBar(true); 114 } 115 116 @Override 117 public void onFetchEnd() { 118 mDrawerController.showLoadingProgressBar(false); 119 } 120 121 /** 122 * Creates a new sub-level in the drawer and switches to that as the currently displayed view. 123 * 124 * @param fetcher The {@link MediaItemsFetcher} that is responsible for fetching the items to be 125 * displayed in the new view. 126 * @param title The title text of the new view in the drawer. 127 */ 128 private void setupAdapterAndSwitch(MediaItemsFetcher fetcher, CharSequence title) { 129 MediaDrawerAdapter subAdapter = new MediaDrawerAdapter(mContext, mDrawerController); 130 subAdapter.setFetcher(fetcher); 131 subAdapter.setTitle(title); 132 subAdapter.setFetchCallback(this); 133 mDrawerController.pushAdapter(subAdapter); 134 } 135 136 /** 137 * Opens the drawer and displays the current playing queue of items. When the drawer is closed, 138 * the view is switched back to the drawer root. 139 */ 140 public void showPlayQueue() { 141 mRootAdapter.setFetcherAndInvoke(createMediaQueueItemsFetcher()); 142 mRootAdapter.setTitle(mMediaPlaybackModel.getQueueTitle()); 143 mDrawerController.openDrawer(); 144 mRootAdapter.scrollToCurrent(); 145 mDrawerController.addDrawerListener(mQueueDrawerListener); 146 } 147 148 public void cleanup() { 149 mDrawerController.removeDrawerListener(mQueueDrawerListener); 150 mRootAdapter.cleanup(); 151 mMediaPlaybackModel.removeListener(mModelListener); 152 mMediaPlaybackModel.stop(); 153 } 154 155 /** 156 * @return Adapter to display root items of MediaBrowse tree. {@link #showPlayQueue()} can 157 * be used to display items from the queue. 158 */ 159 public CarDrawerAdapter getRootAdapter() { 160 return mRootAdapter; 161 } 162 163 /** 164 * Creates a {@link MediaBrowserItemsFetcher} that whose root is the given {@code mediaId}. 165 */ 166 private MediaBrowserItemsFetcher createMediaBrowserItemFetcher(String mediaId, 167 boolean showQueueItem) { 168 return new MediaBrowserItemsFetcher(mContext, mMediaPlaybackModel, this /* listener */, 169 mediaId, showQueueItem); 170 } 171 172 /** 173 * Creates a {@link MediaQueueItemsFetcher} that is responsible for fetching items in the user's 174 * current play queue. 175 */ 176 private MediaQueueItemsFetcher createMediaQueueItemsFetcher() { 177 return new MediaQueueItemsFetcher(mContext, mMediaPlaybackModel, this /* listener */); 178 } 179 180 /** 181 * Creates a {@link MediaItemsFetcher} that will display the top-most level of the drawer. 182 */ 183 private MediaItemsFetcher createRootMediaItemsFetcher() { 184 return createMediaBrowserItemFetcher(mMediaPlaybackModel.getMediaBrowser().getRoot(), 185 true /* showQueueItem */); 186 } 187 188 /** 189 * A {@link android.support.v4.widget.DrawerLayout.DrawerListener} specifically to be used when 190 * the play queue has been shown in the drawer. When the drawer is closed following this 191 * display, this listener will reset the drawer to display the root view. 192 */ 193 private final DrawerLayout.DrawerListener mQueueDrawerListener = 194 new DrawerLayout.DrawerListener() { 195 @Override 196 public void onDrawerClosed(View drawerView) { 197 mRootAdapter.setFetcherAndInvoke(createRootMediaItemsFetcher()); 198 mRootAdapter.setTitle( 199 MediaManager.getInstance(mContext).getMediaClientName()); 200 mDrawerController.removeDrawerListener(this); 201 } 202 203 @Override 204 public void onDrawerSlide(View drawerView, float slideOffset) {} 205 @Override 206 public void onDrawerOpened(View drawerView) {} 207 @Override 208 public void onDrawerStateChanged(int newState) {} 209 }; 210 211 private final MediaPlaybackModel.Listener mModelListener = 212 new MediaPlaybackModel.AbstractListener() { 213 @Override 214 public void onMediaAppChanged(@Nullable ComponentName currentName, 215 @Nullable ComponentName newName) { 216 // Only store MediaManager instance to a local variable when it is short lived. 217 MediaManager mediaManager = MediaManager.getInstance(mContext); 218 mRootAdapter.cleanup(); 219 mRootAdapter.setTitle(mediaManager.getMediaClientName()); 220 } 221 222 @Override 223 public void onMediaConnected() { 224 mRootAdapter.setFetcherAndInvoke(createRootMediaItemsFetcher()); 225 } 226 }; 227 } 228