130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownpackage com.android.car.media;
230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport android.annotation.NonNull;
430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport android.annotation.Nullable;
5035185163a5eba95b7655439bff9e162494f25ffRoberto Perezimport android.media.session.PlaybackState;
6035185163a5eba95b7655439bff9e162494f25ffRoberto Perezimport android.support.v4.media.session.PlaybackStateCompat;
730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport android.view.View;
830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport android.widget.ImageView;
930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport android.widget.SeekBar;
1030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport android.widget.TextView;
1130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
1230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport com.android.car.media.common.MediaItemMetadata;
1330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport com.android.car.media.common.PlaybackModel;
1430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
1530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport java.text.DateFormat;
1630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport java.text.SimpleDateFormat;
1730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport java.util.Date;
1830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownimport java.util.Locale;
1930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
2030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown/**
2130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown * Common controller for displaying current track's metadata.
2230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown */
2330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brownpublic class MetadataController {
2430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
2530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private static final DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss", Locale.US);
2630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
2730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    @NonNull
2830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private final TextView mTitle;
2930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    @NonNull
3030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private final TextView mSubtitle;
3130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    @Nullable
3230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private final TextView mTime;
3330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    @NonNull
3430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private final SeekBar mSeekBar;
3530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    @Nullable
3630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private final ImageView mAlbumArt;
3730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
3830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    @Nullable
3930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private PlaybackModel mModel;
4030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
41839543fa68e92663908eb074b65c74e801168aedJoshua Brown    private boolean mUpdatesPaused;
42839543fa68e92663908eb074b65c74e801168aedJoshua Brown    private boolean mNeedsMetadataUpdate;
43a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez    private int mAlbumArtSize;
44839543fa68e92663908eb074b65c74e801168aedJoshua Brown
4530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private final PlaybackModel.PlaybackObserver mPlaybackObserver =
4630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            new PlaybackModel.PlaybackObserver() {
4730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                @Override
4830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                public void onPlaybackStateChanged() {
4930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                    updateState();
5030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                }
5130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
5230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                @Override
5330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                public void onSourceChanged() {
5430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                    updateState();
5530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                    updateMetadata();
5630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                }
5730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
5830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                @Override
5930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                public void onMetadataChanged() {
6030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                    updateMetadata();
6130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                }
6230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            };
6330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
6430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    /**
6530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * Create a new MetadataController that operates on the provided Views
6630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * @param title Displays the track's title. Must not be {@code null}.
6730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * @param subtitle Displays the track's artist. Must not be {@code null}.
6830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * @param time Displays the track's progress as text. May be {@code null}.
6930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * @param seekBar Displays the track's progress visually. Must not be {@code null}.
7030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * @param albumArt Displays the track's album art. May be {@code null}.
7130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     */
7230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    public MetadataController(@NonNull TextView title, @NonNull TextView subtitle,
7330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            @Nullable TextView time, @NonNull SeekBar seekBar, @Nullable ImageView albumArt) {
7430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        mTitle = title;
7530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        mSubtitle = subtitle;
7630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        mTime = time;
7730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        mSeekBar = seekBar;
78a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        mSeekBar.setOnTouchListener((view, event) -> true);
7930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        mAlbumArt = albumArt;
80a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez        mAlbumArtSize = title.getContext().getResources()
81a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                .getDimensionPixelSize(R.dimen.playback_album_art_size_large);
8230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    }
8330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
8430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    /**
8530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * Registers the {@link PlaybackModel} this widget will use to follow playback state.
8630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * Consumers of this class must unregister the {@link PlaybackModel} by calling this method with
8730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * null.
8830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     *
8930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     * @param model {@link PlaybackModel} to subscribe, or null to unsubscribe.
9030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown     */
9130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    public void setModel(@Nullable PlaybackModel model) {
9230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        if (mModel != null) {
9330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            mModel.unregisterObserver(mPlaybackObserver);
9430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        }
9530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        mModel = model;
9630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        if (mModel != null) {
9730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            mModel.registerObserver(mPlaybackObserver);
9830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        }
9930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    }
10030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
10130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private void updateState() {
10230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        updateProgress();
10330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
104839543fa68e92663908eb074b65c74e801168aedJoshua Brown        mSeekBar.removeCallbacks(mSeekBarRunnable);
10530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        if (mModel != null && mModel.isPlaying()) {
10630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            mSeekBar.post(mSeekBarRunnable);
10730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        }
10830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    }
10930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
11030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private void updateMetadata() {
111839543fa68e92663908eb074b65c74e801168aedJoshua Brown        if(mUpdatesPaused) {
112839543fa68e92663908eb074b65c74e801168aedJoshua Brown            mNeedsMetadataUpdate = true;
113839543fa68e92663908eb074b65c74e801168aedJoshua Brown            return;
114839543fa68e92663908eb074b65c74e801168aedJoshua Brown        }
115839543fa68e92663908eb074b65c74e801168aedJoshua Brown
116839543fa68e92663908eb074b65c74e801168aedJoshua Brown        mNeedsMetadataUpdate = false;
11730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        MediaItemMetadata metadata = mModel != null ? mModel.getMetadata() : null;
11830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        mTitle.setText(metadata != null ? metadata.getTitle() : null);
11930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        mSubtitle.setText(metadata != null ? metadata.getSubtitle() : null);
1201d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez        if (mAlbumArt != null && metadata != null && (metadata.getAlbumArtUri() != null
1211d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez                || metadata.getAlbumArtBitmap() != null)) {
1221d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez            mAlbumArt.setVisibility(View.VISIBLE);
123a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez            metadata.getAlbumArt(mAlbumArt.getContext(), mAlbumArtSize, mAlbumArtSize, true)
124a51d23d9109f44187200495fdb4523d47255b14aRoberto Perez                    .thenAccept(mAlbumArt::setImageBitmap);
1251d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez        } else if (mAlbumArt != null) {
1261d3e7e02676ed495b9f99701fa4647aa524d3f1bRoberto Perez            mAlbumArt.setVisibility(View.GONE);
12730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        }
12830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    }
12930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
13030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private static final long SEEK_BAR_UPDATE_TIME_INTERVAL_MS = 1000;
13130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
13230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private final Runnable mSeekBarRunnable = new Runnable() {
13330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        @Override
13430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        public void run() {
13530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            if (mModel == null || !mModel.isPlaying()) {
13630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                return;
13730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            }
13830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            updateProgress();
13930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            mSeekBar.postDelayed(this, SEEK_BAR_UPDATE_TIME_INTERVAL_MS);
14030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
14130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        }
14230ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    };
14330ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
14430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    private void updateProgress() {
14530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        if (mModel == null) {
14630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            mTime.setVisibility(View.INVISIBLE);
14730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            mSeekBar.setVisibility(View.INVISIBLE);
14830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            return;
14930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        }
15030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        long maxProgress = mModel.getMaxProgress();
151035185163a5eba95b7655439bff9e162494f25ffRoberto Perez        long progress = mModel.getProgress();
152035185163a5eba95b7655439bff9e162494f25ffRoberto Perez        int visibility = maxProgress > 0 && progress != PlaybackState.PLAYBACK_POSITION_UNKNOWN
153035185163a5eba95b7655439bff9e162494f25ffRoberto Perez                ? View.VISIBLE : View.INVISIBLE;
15430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        if (mTime != null) {
15530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            String time = String.format("%s / %s",
156035185163a5eba95b7655439bff9e162494f25ffRoberto Perez                    TIME_FORMAT.format(new Date(progress)),
15730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown                    TIME_FORMAT.format(new Date(maxProgress)));
15830ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            mTime.setVisibility(visibility);
15930ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown            mTime.setText(time);
16030ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        }
16130ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown        mSeekBar.setVisibility(visibility);
162035185163a5eba95b7655439bff9e162494f25ffRoberto Perez        mSeekBar.setMax((int) maxProgress);
163035185163a5eba95b7655439bff9e162494f25ffRoberto Perez        mSeekBar.setProgress((int) progress);
16430ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown    }
16530ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
16630ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown
167839543fa68e92663908eb074b65c74e801168aedJoshua Brown    public void pauseUpdates() {
168839543fa68e92663908eb074b65c74e801168aedJoshua Brown        mUpdatesPaused = true;
169839543fa68e92663908eb074b65c74e801168aedJoshua Brown    }
170839543fa68e92663908eb074b65c74e801168aedJoshua Brown
171839543fa68e92663908eb074b65c74e801168aedJoshua Brown    public void resumeUpdates() {
172839543fa68e92663908eb074b65c74e801168aedJoshua Brown        mUpdatesPaused = false;
173839543fa68e92663908eb074b65c74e801168aedJoshua Brown        if (mNeedsMetadataUpdate) {
174839543fa68e92663908eb074b65c74e801168aedJoshua Brown            updateMetadata();
175839543fa68e92663908eb074b65c74e801168aedJoshua Brown        }
176839543fa68e92663908eb074b65c74e801168aedJoshua Brown    }
17730ff1007ab6729566d053649d3e973ac9fd33627Joshua Brown}
178