Merge "Support4Demo: Add sample usage of MediaBrowser pagination API"
diff --git a/samples/Support4Demos/res/layout/fragment_list.xml b/samples/Support4Demos/res/layout/fragment_list.xml
index c169fec..904ec1a 100644
--- a/samples/Support4Demos/res/layout/fragment_list.xml
+++ b/samples/Support4Demos/res/layout/fragment_list.xml
@@ -54,7 +54,6 @@
<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
- android:layout_height="match_parent">
- </ListView>
+ android:layout_height="match_parent"/>
-</LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java b/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
index 2ee7622..765dc88 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/BrowseFragment.java
@@ -26,6 +26,7 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
@@ -44,7 +45,7 @@
* <p/>
* It uses a {@link MediaBrowserCompat} to connect to the {@link MediaBrowserServiceSupport}.
* Once connected, the fragment subscribes to get all the children. All
- * {@link MediaBrowserCompat.MediaItem}'s that can be browsed are shown in a ListView.
+ * {@link MediaBrowserCompat.MediaItem} objects that can be browsed are shown in a ListView.
*/
public class BrowseFragment extends Fragment {
@@ -52,27 +53,68 @@
public static final String ARG_MEDIA_ID = "media_id";
+ // The number of media items per page.
+ private static final int PAGE_SIZE = 6;
+
public static interface FragmentDataHelper {
void onMediaItemSelected(MediaBrowserCompat.MediaItem item);
}
// The mediaId to be used for subscribing for children using the MediaBrowser.
private String mMediaId;
+ private final List<MediaBrowserCompat.MediaItem> mMediaItems = new ArrayList<>();
+ private boolean mCanLoadNewPage;
private MediaBrowserCompat mMediaBrowser;
private BrowseAdapter mBrowserAdapter;
private MediaBrowserCompat.SubscriptionCallback mSubscriptionCallback =
new MediaBrowserCompat.SubscriptionCallback() {
+ @Override
+ public void onChildrenLoaded(String parentId, List<MediaBrowserCompat.MediaItem> children,
+ Bundle options) {
+ int page = options.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
+ int pageSize = options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
+ if (page < 0 || pageSize != PAGE_SIZE || children == null
+ || children.size() > PAGE_SIZE) {
+ return;
+ }
+
+ int itemIndex = page * PAGE_SIZE;
+ if (itemIndex >= mMediaItems.size()) {
+ if (children.size() == 0) {
+ return;
+ }
+ // An additional page is loaded.
+ mMediaItems.addAll(children);
+ } else {
+ // An existing page is replaced by the newly loaded page.
+ for (MediaBrowserCompat.MediaItem item : children) {
+ if (itemIndex < mMediaItems.size()) {
+ mMediaItems.set(itemIndex, item);
+ } else {
+ mMediaItems.add(item);
+ }
+ itemIndex++;
+ }
+
+ // If the newly loaded page contains less than {PAGE_SIZE} items,
+ // then this page should be the last page.
+ if (children.size() < PAGE_SIZE) {
+ while (mMediaItems.size() > itemIndex) {
+ mMediaItems.remove(mMediaItems.size() - 1);
+ }
+ }
+ }
+ mBrowserAdapter.notifyDataSetChanged();
+ mCanLoadNewPage = true;
+ }
@Override
public void onChildrenLoaded(String parentId, List<MediaBrowserCompat.MediaItem> children) {
- Log.d(TAG, "onChildrenLoaded: " + parentId);
- mBrowserAdapter.clear();
- mBrowserAdapter.notifyDataSetInvalidated();
- for (MediaBrowserCompat.MediaItem item : children) {
- mBrowserAdapter.add(item);
- }
+ Log.d(TAG, "onChildrenLoaded: parentId=" + parentId);
+ mMediaItems.clear();
+ mMediaItems.addAll(children);
mBrowserAdapter.notifyDataSetChanged();
}
@@ -89,10 +131,6 @@
public void onConnected() {
Log.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken());
- if (mMediaId == null) {
- mMediaId = mMediaBrowser.getRoot();
- }
- mMediaBrowser.subscribe(mMediaId, mSubscriptionCallback);
if (mMediaBrowser.getSessionToken() == null) {
throw new IllegalArgumentException("No Session token");
}
@@ -104,6 +142,14 @@
Log.e(TAG, "Failed to create MediaController.", e);
}
((MediaBrowserSupport) getActivity()).setMediaController(mediaController);
+
+ if (mMediaId == null) {
+ mMediaId = mMediaBrowser.getRoot();
+ }
+
+ if (mMediaItems.size() == 0) {
+ loadPage(0);
+ }
}
@Override
@@ -128,10 +174,10 @@
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
+ Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_list, container, false);
- mBrowserAdapter = new BrowseAdapter(getActivity());
+ mBrowserAdapter = new BrowseAdapter(getActivity(), mMediaItems);
View controls = rootView.findViewById(R.id.controls);
controls.setVisibility(View.GONE);
@@ -158,6 +204,22 @@
new ComponentName(getActivity(), MediaBrowserServiceSupport.class),
mConnectionCallback, null);
+ listView.setOnScrollListener(new AbsListView.OnScrollListener() {
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ if (mCanLoadNewPage && firstVisibleItem + visibleItemCount == totalItemCount) {
+ mCanLoadNewPage = false;
+ loadPage((mMediaItems.size() + PAGE_SIZE - 1) / PAGE_SIZE);
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // Do nothing
+ }
+ });
+
return rootView;
}
@@ -173,11 +235,18 @@
mMediaBrowser.disconnect();
}
- // An adapter for showing the list of browsed MediaItem's
+ private void loadPage(int page) {
+ Bundle options = new Bundle();
+ options.putInt(MediaBrowserCompat.EXTRA_PAGE, page);
+ options.putInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, PAGE_SIZE);
+ mMediaBrowser.subscribe(mMediaId, options, mSubscriptionCallback);
+ }
+
+ // An adapter for showing the list of browsed MediaItem objects
private static class BrowseAdapter extends ArrayAdapter<MediaBrowserCompat.MediaItem> {
- public BrowseAdapter(Context context) {
- super(context, R.layout.media_list_item, new ArrayList<MediaBrowserCompat.MediaItem>());
+ public BrowseAdapter(Context context, List<MediaBrowserCompat.MediaItem> mediaItems) {
+ super(context, R.layout.media_list_item, mediaItems);
}
static class ViewHolder {
@@ -211,8 +280,10 @@
holder.mImageView.setImageDrawable(getContext().getResources()
.getDrawable(R.drawable.ic_play_arrow_white_24dp));
holder.mImageView.setVisibility(View.VISIBLE);
+ } else {
+ holder.mImageView.setVisibility(View.GONE);
}
return convertView;
}
}
-}
+}
\ No newline at end of file
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/MediaBrowserServiceSupport.java b/samples/Support4Demos/src/com/example/android/supportv4/media/MediaBrowserServiceSupport.java
index 74ddd3f..a1d5bfa 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/MediaBrowserServiceSupport.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/MediaBrowserServiceSupport.java
@@ -25,6 +25,7 @@
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
+import android.support.v4.media.MediaBrowserCompat;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.MediaBrowserCompat.MediaItem;
@@ -241,6 +242,12 @@
@Override
public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) {
+ onLoadChildren(parentMediaId, result, null);
+ }
+
+ @Override
+ public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result,
+ final Bundle options) {
if (!mMusicProvider.isInitialized()) {
// Use result.detach to allow calling result.sendResult from another thread:
result.detach();
@@ -249,17 +256,16 @@
@Override
public void onMusicCatalogReady(boolean success) {
if (success) {
- loadChildrenImpl(parentMediaId, result);
+ loadChildrenImpl(parentMediaId, result, options);
} else {
updatePlaybackState(getString(R.string.error_no_metadata));
result.sendResult(Collections.<MediaItem>emptyList());
}
}
});
-
} else {
// If our music catalog is already loaded/cached, load them into result immediately
- loadChildrenImpl(parentMediaId, result);
+ loadChildrenImpl(parentMediaId, result, options);
}
}
@@ -268,26 +274,49 @@
* initialized.
*/
private void loadChildrenImpl(final String parentMediaId,
- final Result<List<MediaItem>> result) {
- Log.d(TAG, "OnLoadChildren: parentMediaId=" + parentMediaId);
+ final Result<List<MediaItem>> result, final Bundle options) {
+ Log.d(TAG, "OnLoadChildren: parentMediaId=" + parentMediaId + ", options=" + options);
+
+ int page = -1;
+ int pageSize = -1;
+
+ if (options != null && (options.containsKey(MediaBrowserCompat.EXTRA_PAGE)
+ || options.containsKey(MediaBrowserCompat.EXTRA_PAGE_SIZE))) {
+ page = options.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
+ pageSize = options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
+
+ if (page < 0 || pageSize < 1) {
+ result.sendResult(new ArrayList<>());
+ return;
+ }
+ }
+
+ int fromIndex = page == -1 ? 0 : page * pageSize;
+ int toIndex = 0;
List<MediaItem> mediaItems = new ArrayList<>();
if (MEDIA_ID_ROOT.equals(parentMediaId)) {
Log.d(TAG, "OnLoadChildren.ROOT");
- mediaItems.add(new MediaItem(
- new MediaDescriptionCompat.Builder()
- .setMediaId(MEDIA_ID_MUSICS_BY_GENRE)
- .setTitle(getString(R.string.browse_genres))
- .setIconUri(Uri.parse("android.resource://" +
- "com.example.android.supportv4.media/drawable/ic_by_genre"))
- .setSubtitle(getString(R.string.browse_genre_subtitle))
- .build(), MediaItem.FLAG_BROWSABLE
- ));
+ if (page <= 0) {
+ mediaItems.add(new MediaItem(
+ new MediaDescriptionCompat.Builder()
+ .setMediaId(MEDIA_ID_MUSICS_BY_GENRE)
+ .setTitle(getString(R.string.browse_genres))
+ .setIconUri(Uri.parse("android.resource://" +
+ "com.example.android.supportv4.media/drawable/ic_by_genre"))
+ .setSubtitle(getString(R.string.browse_genre_subtitle))
+ .build(), MediaItem.FLAG_BROWSABLE));
+ }
} else if (MEDIA_ID_MUSICS_BY_GENRE.equals(parentMediaId)) {
Log.d(TAG, "OnLoadChildren.GENRES");
- for (String genre : mMusicProvider.getGenres()) {
+
+ List<String> genres = mMusicProvider.getGenres();
+ toIndex = page == -1 ? genres.size() : Math.min(fromIndex + pageSize, genres.size());
+
+ for (int i = fromIndex; i < toIndex; i++) {
+ String genre = genres.get(i);
MediaItem item = new MediaItem(
new MediaDescriptionCompat.Builder()
.setMediaId(createBrowseCategoryMediaID(MEDIA_ID_MUSICS_BY_GENRE,
@@ -303,7 +332,13 @@
} else if (parentMediaId.startsWith(MEDIA_ID_MUSICS_BY_GENRE)) {
String genre = MediaIDHelper.getHierarchy(parentMediaId)[1];
Log.d(TAG, "OnLoadChildren.SONGS_BY_GENRE genre=" + genre);
- for (MediaMetadataCompat track : mMusicProvider.getMusicsByGenre(genre)) {
+
+ List<MediaMetadataCompat> tracks = mMusicProvider.getMusicsByGenre(genre);
+ toIndex = page == -1 ? tracks.size() : Math.min(fromIndex + pageSize, tracks.size());
+
+ for (int i = fromIndex; i < toIndex; i++) {
+ MediaMetadataCompat track = tracks.get(i);
+
// Since mediaMetadata fields are immutable, we need to create a copy, so we
// can set a hierarchy-aware mediaID. We will need to know the media hierarchy
// when we get a onPlayFromMusicID call, so we can create the proper queue based
diff --git a/samples/Support4Demos/src/com/example/android/supportv4/media/model/MusicProvider.java b/samples/Support4Demos/src/com/example/android/supportv4/media/model/MusicProvider.java
index 777ca8d..a6eff2c 100644
--- a/samples/Support4Demos/src/com/example/android/supportv4/media/model/MusicProvider.java
+++ b/samples/Support4Demos/src/com/example/android/supportv4/media/model/MusicProvider.java
@@ -64,6 +64,7 @@
// Categorized caches for music track data:
private ConcurrentMap<String, List<MediaMetadataCompat>> mMusicListByGenre;
+ private List<String> mMusicGenres;
private final ConcurrentMap<String, MutableMediaMetadata> mMusicListById;
private final Set<String> mFavoriteTracks;
@@ -82,25 +83,25 @@
mMusicListByGenre = new ConcurrentHashMap<>();
mMusicListById = new ConcurrentHashMap<>();
mFavoriteTracks = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>());
+ mMusicGenres = new ArrayList<>();
}
/**
- * Get an iterator over the list of genres
+ * Get the list of genres
*
* @return genres
*/
- public Iterable<String> getGenres() {
+ public List<String> getGenres() {
if (mCurrentState != State.INITIALIZED) {
return Collections.emptyList();
}
- return mMusicListByGenre.keySet();
+ return mMusicGenres;
}
/**
* Get music tracks of the given genre
- *
*/
- public Iterable<MediaMetadataCompat> getMusicsByGenre(String genre) {
+ public List<MediaMetadataCompat> getMusicsByGenre(String genre) {
if (mCurrentState != State.INITIALIZED || !mMusicListByGenre.containsKey(genre)) {
return Collections.emptyList();
}
@@ -199,7 +200,8 @@
}
private synchronized void buildListsByGenre() {
- ConcurrentMap<String, List<MediaMetadataCompat>> newMusicListByGenre = new ConcurrentHashMap<>();
+ ConcurrentMap<String, List<MediaMetadataCompat>> newMusicListByGenre
+ = new ConcurrentHashMap<>();
for (MutableMediaMetadata m : mMusicListById.values()) {
String genre = m.metadata.getString(MediaMetadataCompat.METADATA_KEY_GENRE);
@@ -211,6 +213,7 @@
list.add(m.metadata);
}
mMusicListByGenre = newMusicListByGenre;
+ mMusicGenres = new ArrayList<>(mMusicListByGenre.keySet());
}
private synchronized void retrieveMedia() {