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