Java tutorial
/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dk.glutter.android.dansker.uamp.ui.tv; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v17.leanback.app.BrowseSupportFragment; import android.support.v17.leanback.widget.ArrayObjectAdapter; import android.support.v17.leanback.widget.HeaderItem; import android.support.v17.leanback.widget.ListRow; import android.support.v17.leanback.widget.ListRowPresenter; import android.support.v17.leanback.widget.OnItemViewClickedListener; import android.support.v17.leanback.widget.Presenter; import android.support.v17.leanback.widget.Row; import android.support.v17.leanback.widget.RowPresenter; import android.support.v4.media.MediaBrowserCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaControllerCompat; import android.support.v4.media.session.MediaSessionCompat; import android.text.TextUtils; import android.view.View; import dk.glutter.android.dansker.uamp.R; import dk.glutter.android.dansker.uamp.utils.LogHelper; import dk.glutter.android.dansker.uamp.utils.MediaIDHelper; import java.util.HashSet; import java.util.Iterator; import java.util.List; import static android.support.v4.media.MediaBrowserCompat.MediaItem; /** * Browse media categories and current playing queue. * <p/> * WARNING: This sample's UI is implemented for a specific MediaBrowser tree structure. * It expects a tree that is three levels deep under root: * - level 0: root * - level 1: categories of categories (like "by genre", "by artist", "playlists") * - level 2: song categories (like "by genre -> Rock", "by artist -> artistname" or * "playlists -> my favorite music") * - level 3: the actual music * <p/> * If you are reusing this TV code, make sure you adapt it to your MediaBrowser structure, in case * it is not the same. * <p/> * <p/> * It uses a {@link android.support.v4.media.MediaBrowserCompat} to connect to the {@link dk.glutter.android.dansker.uamp.MusicService}. * Once connected, the fragment subscribes to get the children of level 1 and then, for each * children, it adds a ListRow and subscribes for its children, which, when received, are * added to the ListRow. These items (like "Rock"), when clicked, will open a * TvVerticalGridActivity that lists all songs of the specified category on a grid-like UI. * <p/> * This fragment also shows the MediaSession queue ("now playing" list), in case there is * something playing. */ public class TvBrowseFragment extends BrowseSupportFragment { private static final String TAG = LogHelper.makeLogTag(TvBrowseFragment.class); private ArrayObjectAdapter mRowsAdapter; private ArrayObjectAdapter mListRowAdapter; private MediaFragmentListener mMediaFragmentListener; private MediaBrowserCompat mMediaBrowser; private HashSet<String> mSubscribedMediaIds; // Receive callbacks from the MediaController. Here we update our state such as which queue // is being shown, the current title and description and the PlaybackState. private final MediaControllerCompat.Callback mMediaControllerCallback = new MediaControllerCompat.Callback() { @Override public void onMetadataChanged(MediaMetadataCompat metadata) { if (metadata != null) { MediaControllerCompat mediaController = getActivity().getSupportMediaController(); long activeQueueId; if (mediaController.getPlaybackState() == null) { activeQueueId = MediaSessionCompat.QueueItem.UNKNOWN_ID; } else { activeQueueId = mediaController.getPlaybackState().getActiveQueueItemId(); } updateNowPlayingList(mediaController.getQueue(), activeQueueId); mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size()); } } @Override public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) { // queue has changed somehow MediaControllerCompat mediaController = getActivity().getSupportMediaController(); long activeQueueId; if (mediaController.getPlaybackState() == null) { activeQueueId = MediaSessionCompat.QueueItem.UNKNOWN_ID; } else { activeQueueId = mediaController.getPlaybackState().getActiveQueueItemId(); } updateNowPlayingList(queue, activeQueueId); mRowsAdapter.notifyArrayItemRangeChanged(0, mRowsAdapter.size()); } }; private void updateNowPlayingList(List<MediaSessionCompat.QueueItem> queue, long activeQueueId) { mListRowAdapter.clear(); if (activeQueueId != MediaSessionCompat.QueueItem.UNKNOWN_ID) { Iterator<MediaSessionCompat.QueueItem> iterator = queue.iterator(); while (iterator.hasNext()) { MediaSessionCompat.QueueItem queueItem = iterator.next(); if (activeQueueId != queueItem.getQueueId()) { iterator.remove(); } else { break; } } } mListRowAdapter.addAll(0, queue); } private final MediaBrowserCompat.SubscriptionCallback mSubscriptionCallback = new MediaBrowserCompat.SubscriptionCallback() { @Override public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaBrowserCompat.MediaItem> children) { mRowsAdapter.clear(); CardPresenter cardPresenter = new CardPresenter(); for (int i = 0; i < children.size(); i++) { MediaBrowserCompat.MediaItem item = children.get(i); String title = (String) item.getDescription().getTitle(); HeaderItem header = new HeaderItem(i, title); ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter); mRowsAdapter.add(new ListRow(header, listRowAdapter)); if (item.isPlayable()) { listRowAdapter.add(item); } else if (item.isBrowsable()) { subscribeToMediaId(item.getMediaId(), new RowSubscriptionCallback(listRowAdapter)); } else { LogHelper.e(TAG, "Item should be playable or browsable."); } } MediaControllerCompat mediaController = getActivity().getSupportMediaController(); if (mediaController.getQueue() != null && !mediaController.getQueue().isEmpty()) { // add Now Playing queue to Browse Home HeaderItem header = new HeaderItem(children.size(), getString(R.string.now_playing)); mListRowAdapter = new ArrayObjectAdapter(cardPresenter); mRowsAdapter.add(new ListRow(header, mListRowAdapter)); long activeQueueId; if (mediaController.getPlaybackState() == null) { activeQueueId = MediaSessionCompat.QueueItem.UNKNOWN_ID; } else { activeQueueId = mediaController.getPlaybackState().getActiveQueueItemId(); } updateNowPlayingList(mediaController.getQueue(), activeQueueId); } mRowsAdapter.notifyArrayItemRangeChanged(0, children.size()); } @Override public void onError(@NonNull String id) { LogHelper.e(TAG, "SubscriptionCallback subscription onError, id=" + id); } }; /** * This callback fills content for a single Row in the BrowseFragment. */ private class RowSubscriptionCallback extends MediaBrowserCompat.SubscriptionCallback { private final ArrayObjectAdapter mListRowAdapter; public RowSubscriptionCallback(ArrayObjectAdapter listRowAdapter) { mListRowAdapter = listRowAdapter; } @Override public void onChildrenLoaded(@NonNull String parentId, @NonNull List<MediaBrowserCompat.MediaItem> children) { mListRowAdapter.clear(); for (MediaBrowserCompat.MediaItem item : children) { mListRowAdapter.add(item); } mListRowAdapter.notifyArrayItemRangeChanged(0, children.size()); } @Override public void onError(@NonNull String id) { LogHelper.e(TAG, "RowSubscriptionCallback subscription onError, id=", id); } } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); LogHelper.d(TAG, "onActivityCreated"); mSubscribedMediaIds = new HashSet<>(); // set search icon color setSearchAffordanceColor(getResources().getColor(R.color.tv_search_button)); loadRows(); setupEventListeners(); } private void loadRows() { mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter()); setAdapter(mRowsAdapter); } private void setupEventListeners() { setOnItemViewClickedListener(new OnItemViewClickedListener() { @Override public void onItemClicked(Presenter.ViewHolder viewHolder, Object clickedItem, RowPresenter.ViewHolder viewHolder2, Row row) { if (clickedItem instanceof MediaBrowserCompat.MediaItem) { MediaItem item = (MediaItem) clickedItem; if (item.isPlayable()) { LogHelper.w(TAG, "Ignoring click on PLAYABLE MediaItem in" + "TvBrowseFragment. mediaId=", item.getMediaId()); return; } Intent intent = new Intent(getActivity(), TvVerticalGridActivity.class); intent.putExtra(TvBrowseActivity.SAVED_MEDIA_ID, item.getMediaId()); intent.putExtra(TvBrowseActivity.BROWSE_TITLE, item.getDescription().getTitle()); startActivity(intent); } else if (clickedItem instanceof MediaSessionCompat.QueueItem) { MediaSessionCompat.QueueItem item = (MediaSessionCompat.QueueItem) clickedItem; MediaControllerCompat mediaController = getActivity().getSupportMediaController(); // if clicked media item is not already playing, call skipToQueueItem to play it if (mediaController != null && mediaController.getMetadata() != null) { String currentPlaying = mediaController.getMetadata().getDescription().getMediaId(); String itemMusicId = MediaIDHelper .extractMusicIDFromMediaID(item.getDescription().getMediaId()); if (!TextUtils.equals(currentPlaying, itemMusicId)) { mediaController.getTransportControls().skipToQueueItem(item.getQueueId()); } } else { mediaController.getTransportControls().skipToQueueItem(item.getQueueId()); } Intent intent = new Intent(getActivity(), TvPlaybackActivity.class); startActivity(intent); } } }); setOnSearchClickedListener(new View.OnClickListener() { @Override public void onClick(View view) { LogHelper.d(TAG, "In-app search"); // TODO: implement in-app search Intent intent = new Intent(getActivity(), TvBrowseActivity.class); startActivity(intent); } }); } @Override public void onAttach(Activity activity) { super.onAttach(activity); try { mMediaFragmentListener = (MediaFragmentListener) activity; } catch (ClassCastException ex) { LogHelper.e(TAG, "TVBrowseFragment can only be attached to an activity that " + "implements MediaFragmentListener", ex); } } @Override public void onStop() { super.onStop(); if (mMediaBrowser != null && mMediaBrowser.isConnected()) { for (String mediaId : mSubscribedMediaIds) { mMediaBrowser.unsubscribe(mediaId); } mSubscribedMediaIds.clear(); } MediaControllerCompat mediaController = getActivity().getSupportMediaController(); if (mediaController != null) { mediaController.unregisterCallback(mMediaControllerCallback); } } @Override public void onDetach() { super.onDetach(); mMediaFragmentListener = null; } public void initializeWithMediaId(String mediaId) { LogHelper.d(TAG, "subscribeToData"); // fetch browsing information to fill the listview: mMediaBrowser = mMediaFragmentListener.getMediaBrowser(); if (mediaId == null) { mediaId = mMediaBrowser.getRoot(); } subscribeToMediaId(mediaId, mSubscriptionCallback); // Add MediaController callback so we can redraw the list when metadata changes: MediaControllerCompat mediaController = getActivity().getSupportMediaController(); if (mediaController != null) { mediaController.registerCallback(mMediaControllerCallback); } } private void subscribeToMediaId(String mediaId, MediaBrowserCompat.SubscriptionCallback callback) { if (mSubscribedMediaIds.contains(mediaId)) { mMediaBrowser.unsubscribe(mediaId); } else { mSubscribedMediaIds.add(mediaId); } mMediaBrowser.subscribe(mediaId, callback); } public interface MediaFragmentListener { MediaBrowserCompat getMediaBrowser(); } }