1/* 2 * Copyright (C) 2015 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 */ 16package com.example.android.supportv4.media; 17 18import android.content.ComponentName; 19import android.content.Context; 20import android.os.Bundle; 21import android.os.RemoteException; 22import android.support.v4.media.MediaBrowserCompat; 23import android.support.v4.media.session.MediaControllerCompat; 24import android.util.Log; 25import android.view.LayoutInflater; 26import android.view.View; 27import android.view.ViewGroup; 28import android.widget.AbsListView; 29import android.widget.AdapterView; 30import android.widget.ArrayAdapter; 31import android.widget.ImageView; 32import android.widget.ListView; 33import android.widget.TextView; 34import android.widget.Toast; 35 36import androidx.core.content.ContextCompat; 37import androidx.fragment.app.Fragment; 38 39import com.example.android.supportv4.R; 40 41import java.util.ArrayList; 42import java.util.HashSet; 43import java.util.List; 44import java.util.Set; 45 46/** 47 * A Fragment that lists all the various browsable queues available 48 * from a {@link android.service.media.MediaBrowserService}. 49 * <p/> 50 * It uses a {@link MediaBrowserCompat} to connect to the {@link MediaBrowserServiceSupport}. 51 * Once connected, the fragment subscribes to get all the children. All 52 * {@link MediaBrowserCompat.MediaItem} objects that can be browsed are shown in a ListView. 53 */ 54public class BrowseFragment extends Fragment { 55 56 private static final String TAG = "BrowseFragment"; 57 58 public static final String ARG_MEDIA_ID = "media_id"; 59 60 // The number of media items per page. 61 private static final int PAGE_SIZE = 6; 62 63 public static interface FragmentDataHelper { 64 void onMediaItemSelected(MediaBrowserCompat.MediaItem item); 65 } 66 67 // The mediaId to be used for subscribing for children using the MediaBrowser. 68 private String mMediaId; 69 private final List<MediaBrowserCompat.MediaItem> mMediaItems = new ArrayList<>(); 70 71 private boolean mCanLoadNewPage; 72 private final Set<Integer> mSubscribedPages = new HashSet<Integer>(); 73 private MediaBrowserCompat mMediaBrowser; 74 private BrowseAdapter mBrowserAdapter; 75 76 private MediaBrowserCompat.SubscriptionCallback mSubscriptionCallback = 77 new MediaBrowserCompat.SubscriptionCallback() { 78 @Override 79 public void onChildrenLoaded(String parentId, List<MediaBrowserCompat.MediaItem> children, 80 Bundle options) { 81 int page = options.getInt(MediaBrowserCompat.EXTRA_PAGE, -1); 82 int pageSize = options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1); 83 if (page < 0 || pageSize != PAGE_SIZE || children == null 84 || children.size() > PAGE_SIZE) { 85 return; 86 } 87 88 int itemIndex = page * PAGE_SIZE; 89 if (itemIndex >= mMediaItems.size()) { 90 if (children.size() == 0) { 91 return; 92 } 93 // An additional page is loaded. 94 mMediaItems.addAll(children); 95 } else { 96 // An existing page is replaced by the newly loaded page. 97 for (MediaBrowserCompat.MediaItem item : children) { 98 if (itemIndex < mMediaItems.size()) { 99 mMediaItems.set(itemIndex, item); 100 } else { 101 mMediaItems.add(item); 102 } 103 itemIndex++; 104 } 105 106 // If the newly loaded page contains less than {PAGE_SIZE} items, 107 // then this page should be the last page. 108 if (children.size() < PAGE_SIZE) { 109 while (mMediaItems.size() > itemIndex) { 110 mMediaItems.remove(mMediaItems.size() - 1); 111 } 112 } 113 } 114 mBrowserAdapter.notifyDataSetChanged(); 115 mCanLoadNewPage = true; 116 } 117 118 @Override 119 public void onChildrenLoaded(String parentId, List<MediaBrowserCompat.MediaItem> children) { 120 Log.d(TAG, "onChildrenLoaded: parentId=" + parentId); 121 mMediaItems.clear(); 122 mMediaItems.addAll(children); 123 mBrowserAdapter.notifyDataSetChanged(); 124 } 125 126 @Override 127 public void onError(String id) { 128 Toast.makeText(getActivity(), R.string.error_loading_media, 129 Toast.LENGTH_LONG).show(); 130 } 131 }; 132 133 private MediaBrowserCompat.ConnectionCallback mConnectionCallback = 134 new MediaBrowserCompat.ConnectionCallback() { 135 @Override 136 public void onConnected() { 137 Log.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken()); 138 139 if (mMediaBrowser.getSessionToken() == null) { 140 throw new IllegalArgumentException("No Session token"); 141 } 142 MediaControllerCompat mediaController = null; 143 try { 144 mediaController = new MediaControllerCompat(getActivity(), 145 mMediaBrowser.getSessionToken()); 146 } catch (RemoteException e) { 147 Log.e(TAG, "Failed to create MediaController.", e); 148 } 149 ((MediaBrowserSupport) getActivity()).setMediaController(mediaController); 150 151 if (mMediaId == null) { 152 mMediaId = mMediaBrowser.getRoot(); 153 } 154 155 if (mMediaItems.size() == 0) { 156 loadPage(0); 157 } 158 } 159 160 @Override 161 public void onConnectionFailed() { 162 Log.d(TAG, "onConnectionFailed"); 163 } 164 165 @Override 166 public void onConnectionSuspended() { 167 Log.d(TAG, "onConnectionSuspended"); 168 ((MediaBrowserSupport) getActivity()).setMediaController((MediaControllerCompat) null); 169 } 170 }; 171 172 public static BrowseFragment newInstance(String mediaId) { 173 Bundle args = new Bundle(); 174 args.putString(ARG_MEDIA_ID, mediaId); 175 BrowseFragment fragment = new BrowseFragment(); 176 fragment.setArguments(args); 177 return fragment; 178 } 179 180 @Override 181 public View onCreateView(LayoutInflater inflater, ViewGroup container, 182 Bundle savedInstanceState) { 183 View rootView = inflater.inflate(R.layout.fragment_list, container, false); 184 185 mBrowserAdapter = new BrowseAdapter(getActivity(), mMediaItems); 186 187 View controls = rootView.findViewById(R.id.controls); 188 controls.setVisibility(View.GONE); 189 190 ListView listView = (ListView) rootView.findViewById(R.id.list_view); 191 listView.setAdapter(mBrowserAdapter); 192 listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { 193 @Override 194 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 195 MediaBrowserCompat.MediaItem item = mBrowserAdapter.getItem(position); 196 try { 197 FragmentDataHelper listener = (FragmentDataHelper) getActivity(); 198 listener.onMediaItemSelected(item); 199 } catch (ClassCastException ex) { 200 Log.e(TAG, "Exception trying to cast to FragmentDataHelper", ex); 201 } 202 } 203 }); 204 205 Bundle args = getArguments(); 206 mMediaId = args.getString(ARG_MEDIA_ID, null); 207 208 mMediaBrowser = new MediaBrowserCompat(getActivity(), 209 new ComponentName(getActivity(), MediaBrowserServiceSupport.class), 210 mConnectionCallback, null); 211 212 listView.setOnScrollListener(new AbsListView.OnScrollListener() { 213 @Override 214 public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, 215 int totalItemCount) { 216 if (mCanLoadNewPage && firstVisibleItem + visibleItemCount == totalItemCount) { 217 mCanLoadNewPage = false; 218 loadPage((mMediaItems.size() + PAGE_SIZE - 1) / PAGE_SIZE); 219 } 220 } 221 222 @Override 223 public void onScrollStateChanged(AbsListView view, int scrollState) { 224 // Do nothing 225 } 226 }); 227 228 return rootView; 229 } 230 231 @Override 232 public void onStart() { 233 super.onStart(); 234 mMediaBrowser.connect(); 235 } 236 237 @Override 238 public void onStop() { 239 super.onStop(); 240 mMediaBrowser.disconnect(); 241 mSubscribedPages.clear(); 242 } 243 244 private void loadPage(int page) { 245 Integer pageInteger = Integer.valueOf(page); 246 if (mSubscribedPages.contains(pageInteger)) { 247 return; 248 } 249 mSubscribedPages.add(pageInteger); 250 Bundle options = new Bundle(); 251 options.putInt(MediaBrowserCompat.EXTRA_PAGE, page); 252 options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, PAGE_SIZE); 253 mMediaBrowser.subscribe(mMediaId, options, mSubscriptionCallback); 254 } 255 256 // An adapter for showing the list of browsed MediaItem objects 257 private static class BrowseAdapter extends ArrayAdapter<MediaBrowserCompat.MediaItem> { 258 259 public BrowseAdapter(Context context, List<MediaBrowserCompat.MediaItem> mediaItems) { 260 super(context, R.layout.media_list_item, mediaItems); 261 } 262 263 static class ViewHolder { 264 ImageView mImageView; 265 TextView mTitleView; 266 TextView mDescriptionView; 267 } 268 269 @Override 270 public View getView(int position, View convertView, ViewGroup parent) { 271 272 ViewHolder holder; 273 274 if (convertView == null) { 275 convertView = LayoutInflater.from(getContext()) 276 .inflate(R.layout.media_list_item, parent, false); 277 holder = new ViewHolder(); 278 holder.mImageView = (ImageView) convertView.findViewById(R.id.play_eq); 279 holder.mImageView.setVisibility(View.GONE); 280 holder.mTitleView = (TextView) convertView.findViewById(R.id.title); 281 holder.mDescriptionView = (TextView) convertView.findViewById(R.id.description); 282 convertView.setTag(holder); 283 } else { 284 holder = (ViewHolder) convertView.getTag(); 285 } 286 287 MediaBrowserCompat.MediaItem item = getItem(position); 288 holder.mTitleView.setText(item.getDescription().getTitle()); 289 holder.mDescriptionView.setText(item.getDescription().getDescription()); 290 if (item.isPlayable()) { 291 292 holder.mImageView.setImageDrawable(ContextCompat.getDrawable( 293 getContext(), R.drawable.ic_play_arrow_white_24dp)); 294 holder.mImageView.setVisibility(View.VISIBLE); 295 } else { 296 holder.mImageView.setVisibility(View.GONE); 297 } 298 return convertView; 299 } 300 } 301}