blob: 376c3045499e82622c32e004547e37fcd5341f39 [file] [log] [blame]
/*
* Copyright 2018 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 com.android.car.media;
import android.annotation.NonNull;
import android.content.Context;
import android.media.session.MediaController;
import android.os.Bundle;
import android.transition.Transition;
import android.transition.TransitionInflater;
import android.transition.TransitionListenerAdapter;
import android.transition.TransitionManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.car.widget.ListItem;
import androidx.car.widget.ListItemAdapter;
import androidx.car.widget.ListItemProvider;
import androidx.car.widget.PagedListView;
import androidx.car.widget.TextListItem;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.android.car.media.common.MediaItemMetadata;
import com.android.car.media.common.MediaSource;
import com.android.car.media.common.PlaybackControls;
import com.android.car.media.common.PlaybackModel;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* A {@link Fragment} that implements both the playback and the content forward browsing experience.
* It observes a {@link PlaybackModel} and updates its information depending on the currently
* playing media source through the {@link android.media.session.MediaSession} API.
*/
public class PlaybackFragment extends Fragment {
private static final String TAG = "PlaybackFragment";
private PlaybackModel mModel;
private PlaybackControls mPlaybackControls;
private QueueItemsAdapter mQueueAdapter;
private PagedListView mQueue;
private Callbacks mCallbacks;
private MetadataController mMetadataController;
private ConstraintLayout mRootView;
private boolean mNeedsStateUpdate;
private boolean mUpdatesPaused;
private boolean mQueueIsVisible;
private List<MediaItemMetadata> mQueueItems = new ArrayList<>();
private PlaybackModel.PlaybackObserver mPlaybackObserver =
new PlaybackModel.PlaybackObserver() {
@Override
public void onPlaybackStateChanged() {
updateState();
}
@Override
public void onSourceChanged() {
updateAccentColor();
updateState();
}
@Override
public void onMetadataChanged() {
}
};
private ListItemProvider mQueueItemsProvider = new ListItemProvider() {
@Override
public ListItem get(int position) {
if (position < 0 || position >= mQueueItems.size()) {
return null;
}
MediaItemMetadata item = mQueueItems.get(position);
TextListItem textListItem = new TextListItem(getContext());
textListItem.setTitle(item.getTitle() != null ? item.getTitle().toString() : null);
textListItem.setBody(item.getSubtitle() != null ? item.getSubtitle().toString() : null);
textListItem.setOnClickListener(v -> onQueueItemClicked(item));
return textListItem;
}
@Override
public int size() {
return mQueueItems.size();
}
};
private class QueueItemsAdapter extends ListItemAdapter {
QueueItemsAdapter(Context context, ListItemProvider itemProvider) {
super(context, itemProvider, BackgroundStyle.SOLID);
setHasStableIds(true);
}
void refresh() {
// TODO: Perform a diff between current and new content and trigger the proper
// RecyclerView updates.
this.notifyDataSetChanged();
}
@Override
public long getItemId(int position) {
return mQueueItems.get(position).getQueueId();
}
}
/**
* Callbacks this fragment can trigger
*/
public interface Callbacks {
/**
* Returns the playback model to use.
*/
PlaybackModel getPlaybackModel();
/**
* Indicates that the "show queue" button has been clicked
*/
void onQueueButtonClicked();
}
private PlaybackControls.Listener mPlaybackControlsListener = new PlaybackControls.Listener() {
@Override
public void onToggleQueue() {
if (mCallbacks != null) {
mCallbacks.onQueueButtonClicked();
}
}
};
@Override
public View onCreateView(@NonNull LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_playback, container, false);
mRootView = view.findViewById(R.id.playback_container);
mQueue = view.findViewById(R.id.queue_list);
initPlaybackControls(view.findViewById(R.id.playback_controls));
initQueue(mQueue);
initMetadataController(view);
return view;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mCallbacks = (Callbacks) context;
}
@Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
private void initPlaybackControls(PlaybackControls playbackControls) {
mPlaybackControls = playbackControls;
mPlaybackControls.setListener(mPlaybackControlsListener);
mPlaybackControls.setAnimationViewGroup(mRootView);
}
private void initQueue(PagedListView queueList) {
RecyclerView recyclerView = queueList.getRecyclerView();
recyclerView.setVerticalFadingEdgeEnabled(true);
recyclerView.setFadingEdgeLength(getResources()
.getDimensionPixelSize(R.dimen.car_padding_4));
mQueueAdapter = new QueueItemsAdapter(getContext(), mQueueItemsProvider);
queueList.setAdapter(mQueueAdapter);
}
private void initMetadataController(View view) {
ImageView albumArt = view.findViewById(R.id.album_art);
TextView title = view.findViewById(R.id.title);
TextView subtitle = view.findViewById(R.id.subtitle);
SeekBar seekbar = view.findViewById(R.id.seek_bar);
TextView time = view.findViewById(R.id.time);
mMetadataController = new MetadataController(title, subtitle, time, seekbar, albumArt);
}
@Override
public void onStart() {
super.onStart();
mModel = mCallbacks.getPlaybackModel();
mMetadataController.setModel(mModel);
mPlaybackControls.setModel(mModel);
mModel.registerObserver(mPlaybackObserver);
}
@Override
public void onStop() {
super.onStop();
mModel.unregisterObserver(mPlaybackObserver);
mMetadataController.setModel(null);
mPlaybackControls.setModel(null);
mModel = null;
}
/**
* Hides or shows the playback queue
*/
public void toggleQueueVisibility() {
mQueueIsVisible = !mQueueIsVisible;
mPlaybackControls.setQueueVisible(mQueueIsVisible);
Transition transition = TransitionInflater.from(getContext()).inflateTransition(
mQueueIsVisible ? R.transition.queue_in : R.transition.queue_out);
transition.addListener(new TransitionListenerAdapter() {
@Override
public void onTransitionStart(Transition transition) {
super.onTransitionStart(transition);
mUpdatesPaused = true;
mMetadataController.pauseUpdates();
}
@Override
public void onTransitionEnd(Transition transition) {
mUpdatesPaused = false;
if (mNeedsStateUpdate) {
updateState();
}
mMetadataController.resumeUpdates();
mQueue.getRecyclerView().scrollToPosition(0);
}
});
TransitionManager.beginDelayedTransition(mRootView, transition);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(mRootView.getContext(),
mQueueIsVisible ? R.layout.fragment_playback_with_queue : R.layout.fragment_playback);
constraintSet.applyTo(mRootView);
}
private void updateState() {
if (mUpdatesPaused) {
mNeedsStateUpdate = true;
return;
}
mNeedsStateUpdate = false;
mQueueItems = mModel.getQueue().stream()
.filter(item -> item.getTitle() != null)
.collect(Collectors.toList());
mQueueAdapter.refresh();
}
private void updateAccentColor() {
int defaultColor = getResources().getColor(android.R.color.background_dark, null);
MediaSource mediaSource = mModel.getMediaSource();
int color = mediaSource == null ? defaultColor : mediaSource.getAccentColor(defaultColor);
// TODO: Update queue color
}
private void onQueueItemClicked(MediaItemMetadata item) {
mModel.onSkipToQueueItem(item.getQueueId());
}
/**
* Collapses the playback controls.
*/
public void closeOverflowMenu() {
mPlaybackControls.close();
}
}