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}