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