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 */
16
17package com.example.android.supportv4.media;
18
19import android.content.ComponentName;
20import android.os.Bundle;
21import android.os.RemoteException;
22import android.support.v4.media.MediaBrowserCompat;
23import android.support.v4.media.session.MediaControllerCompat;
24import android.support.v4.media.session.MediaSessionCompat;
25import android.support.v4.media.session.PlaybackStateCompat;
26import android.util.Log;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
30import android.widget.AdapterView;
31import android.widget.ImageButton;
32import android.widget.ListView;
33
34import androidx.core.content.ContextCompat;
35import androidx.fragment.app.Fragment;
36
37import com.example.android.supportv4.R;
38
39import java.util.List;
40
41/**
42 * A class that shows the Media Queue to the user.
43 */
44public class QueueFragment extends Fragment {
45
46    private static final String TAG = "QueueFragment";
47
48    private ImageButton mSkipNext;
49    private ImageButton mSkipPrevious;
50    private ImageButton mPlayPause;
51
52    private MediaBrowserCompat mMediaBrowser;
53    private MediaControllerCompat.TransportControls mTransportControls;
54    private MediaControllerCompat mMediaController;
55    private PlaybackStateCompat mPlaybackState;
56
57    private QueueAdapter mQueueAdapter;
58
59    private MediaBrowserCompat.ConnectionCallback mConnectionCallback =
60            new MediaBrowserCompat.ConnectionCallback() {
61        @Override
62        public void onConnected() {
63            Log.d(TAG, "onConnected: session token " + mMediaBrowser.getSessionToken());
64
65            if (mMediaBrowser.getSessionToken() == null) {
66                throw new IllegalArgumentException("No Session token");
67            }
68
69            try {
70                mMediaController = new MediaControllerCompat(getActivity(),
71                        mMediaBrowser.getSessionToken());
72            } catch (RemoteException e) {
73                Log.e(TAG, "Failed to create MediaControllerCompat.", e);
74            }
75            mTransportControls = mMediaController.getTransportControls();
76            mMediaController.registerCallback(mSessionCallback);
77
78            ((MediaBrowserSupport) getActivity()).setMediaController(mMediaController);
79            mPlaybackState = mMediaController.getPlaybackState();
80
81            List<MediaSessionCompat.QueueItem> queue = mMediaController.getQueue();
82            if (queue != null) {
83                mQueueAdapter.clear();
84                mQueueAdapter.notifyDataSetInvalidated();
85                mQueueAdapter.addAll(queue);
86                mQueueAdapter.notifyDataSetChanged();
87            }
88            onPlaybackStateChanged(mPlaybackState);
89        }
90
91        @Override
92        public void onConnectionFailed() {
93            Log.d(TAG, "onConnectionFailed");
94        }
95
96        @Override
97        public void onConnectionSuspended() {
98            Log.d(TAG, "onConnectionSuspended");
99            mMediaController.unregisterCallback(mSessionCallback);
100            mTransportControls = null;
101            mMediaController = null;
102            ((MediaBrowserSupport) getActivity()).setMediaController((MediaControllerCompat) null);
103        }
104    };
105
106    // Receive callbacks from the MediaController. Here we update our state such as which queue
107    // is being shown, the current title and description and the PlaybackState.
108    private MediaControllerCompat.Callback mSessionCallback = new MediaControllerCompat.Callback() {
109
110        @Override
111        public void onSessionDestroyed() {
112            Log.d(TAG, "Session destroyed. Need to fetch a new Media Session");
113        }
114
115        @Override
116        public void onPlaybackStateChanged(PlaybackStateCompat state) {
117            if (state == null) {
118                return;
119            }
120            Log.d(TAG, "Received playback state change to state " + state.getState());
121            mPlaybackState = state;
122            QueueFragment.this.onPlaybackStateChanged(state);
123        }
124
125        @Override
126        public void onQueueChanged(List<MediaSessionCompat.QueueItem> queue) {
127            Log.d(TAG, "onQueueChanged " + queue);
128            if (queue != null) {
129                mQueueAdapter.clear();
130                mQueueAdapter.notifyDataSetInvalidated();
131                mQueueAdapter.addAll(queue);
132                mQueueAdapter.notifyDataSetChanged();
133            }
134        }
135    };
136
137    public static QueueFragment newInstance() {
138        return new QueueFragment();
139    }
140
141    @Override
142    public View onCreateView(LayoutInflater inflater, ViewGroup container,
143                             Bundle savedInstanceState) {
144        View rootView = inflater.inflate(R.layout.fragment_list, container, false);
145
146        mSkipPrevious = (ImageButton) rootView.findViewById(R.id.skip_previous);
147        mSkipPrevious.setEnabled(false);
148        mSkipPrevious.setOnClickListener(mButtonListener);
149
150        mSkipNext = (ImageButton) rootView.findViewById(R.id.skip_next);
151        mSkipNext.setEnabled(false);
152        mSkipNext.setOnClickListener(mButtonListener);
153
154        mPlayPause = (ImageButton) rootView.findViewById(R.id.play_pause);
155        mPlayPause.setEnabled(true);
156        mPlayPause.setOnClickListener(mButtonListener);
157
158        mQueueAdapter = new QueueAdapter(getActivity());
159
160        ListView mListView = (ListView) rootView.findViewById(R.id.list_view);
161        mListView.setAdapter(mQueueAdapter);
162        mListView.setFocusable(true);
163        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
164            @Override
165            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
166                MediaSessionCompat.QueueItem item = mQueueAdapter.getItem(position);
167                mTransportControls.skipToQueueItem(item.getQueueId());
168            }
169        });
170
171        mMediaBrowser = new MediaBrowserCompat(getActivity(),
172                new ComponentName(getActivity(), MediaBrowserServiceSupport.class),
173                mConnectionCallback, null);
174
175        return rootView;
176    }
177
178    @Override
179    public void onResume() {
180        super.onResume();
181        if (mMediaBrowser != null) {
182            mMediaBrowser.connect();
183        }
184    }
185
186    @Override
187    public void onPause() {
188        super.onPause();
189        if (mMediaController != null) {
190            mMediaController.unregisterCallback(mSessionCallback);
191        }
192        if (mMediaBrowser != null) {
193            mMediaBrowser.disconnect();
194        }
195    }
196
197
198    private void onPlaybackStateChanged(PlaybackStateCompat state) {
199        Log.d(TAG, "onPlaybackStateChanged " + state);
200        if (state == null) {
201            return;
202        }
203        mQueueAdapter.setActiveQueueItemId(state.getActiveQueueItemId());
204        mQueueAdapter.notifyDataSetChanged();
205        boolean enablePlay = false;
206        StringBuilder statusBuilder = new StringBuilder();
207        switch (state.getState()) {
208            case PlaybackStateCompat.STATE_PLAYING:
209                statusBuilder.append("playing");
210                enablePlay = false;
211                break;
212            case PlaybackStateCompat.STATE_PAUSED:
213                statusBuilder.append("paused");
214                enablePlay = true;
215                break;
216            case PlaybackStateCompat.STATE_STOPPED:
217                statusBuilder.append("ended");
218                enablePlay = true;
219                break;
220            case PlaybackStateCompat.STATE_ERROR:
221                statusBuilder.append("error: ").append(state.getErrorMessage());
222                break;
223            case PlaybackStateCompat.STATE_BUFFERING:
224                statusBuilder.append("buffering");
225                break;
226            case PlaybackStateCompat.STATE_NONE:
227                statusBuilder.append("none");
228                enablePlay = false;
229                break;
230            case PlaybackStateCompat.STATE_CONNECTING:
231                statusBuilder.append("connecting");
232                break;
233            default:
234                statusBuilder.append(mPlaybackState);
235        }
236        statusBuilder.append(" -- At position: ").append(state.getPosition());
237        Log.d(TAG, statusBuilder.toString());
238
239        if (enablePlay) {
240            mPlayPause.setImageDrawable(
241                    ContextCompat.getDrawable(getActivity(), R.drawable.ic_play_arrow_white_24dp));
242        } else {
243            mPlayPause.setImageDrawable(
244                    ContextCompat.getDrawable(getActivity(), R.drawable.ic_pause_white_24dp));
245        }
246
247        mSkipPrevious.setEnabled((state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
248                != 0);
249        mSkipNext.setEnabled((state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0);
250
251        Log.d(TAG, "Queue From MediaController *** Title "
252                + mMediaController.getQueueTitle() + "\n: Queue: " + mMediaController.getQueue()
253                + "\n Metadata " + mMediaController.getMetadata());
254    }
255
256    private View.OnClickListener mButtonListener = new View.OnClickListener() {
257        @Override
258        public void onClick(View v) {
259            final int state = mPlaybackState == null ?
260                    PlaybackStateCompat.STATE_NONE : mPlaybackState.getState();
261            switch (v.getId()) {
262                case R.id.play_pause:
263                    Log.d(TAG, "Play button pressed, in state " + state);
264                    if (state == PlaybackStateCompat.STATE_PAUSED ||
265                            state == PlaybackStateCompat.STATE_STOPPED ||
266                            state == PlaybackStateCompat.STATE_NONE) {
267                        playMedia();
268                    } else if (state == PlaybackStateCompat.STATE_PLAYING) {
269                        pauseMedia();
270                    }
271                    break;
272                case R.id.skip_previous:
273                    Log.d(TAG, "Start button pressed, in state " + state);
274                    skipToPrevious();
275                    break;
276                case R.id.skip_next:
277                    skipToNext();
278                    break;
279            }
280        }
281    };
282
283    private void playMedia() {
284        if (mTransportControls != null) {
285            mTransportControls.play();
286        }
287    }
288
289    private void pauseMedia() {
290        if (mTransportControls != null) {
291            mTransportControls.pause();
292        }
293    }
294
295    private void skipToPrevious() {
296        if (mTransportControls != null) {
297            mTransportControls.skipToPrevious();
298        }
299    }
300
301    private void skipToNext() {
302        if (mTransportControls != null) {
303            mTransportControls.skipToNext();
304        }
305    }
306}
307