• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.car.media;
2 
3 import android.annotation.NonNull;
4 import android.annotation.Nullable;
5 import android.media.session.PlaybackState;
6 import android.support.v4.media.session.PlaybackStateCompat;
7 import android.view.View;
8 import android.widget.ImageView;
9 import android.widget.SeekBar;
10 import android.widget.TextView;
11 
12 import com.android.car.media.common.MediaItemMetadata;
13 import com.android.car.media.common.PlaybackModel;
14 
15 import java.text.DateFormat;
16 import java.text.SimpleDateFormat;
17 import java.util.Date;
18 import java.util.Locale;
19 
20 /**
21  * Common controller for displaying current track's metadata.
22  */
23 public class MetadataController {
24 
25     private static final DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss", Locale.US);
26 
27     @NonNull
28     private final TextView mTitle;
29     @NonNull
30     private final TextView mSubtitle;
31     @Nullable
32     private final TextView mTime;
33     @NonNull
34     private final SeekBar mSeekBar;
35     @Nullable
36     private final ImageView mAlbumArt;
37 
38     @Nullable
39     private PlaybackModel mModel;
40 
41     private boolean mUpdatesPaused;
42     private boolean mNeedsMetadataUpdate;
43     private int mAlbumArtSize;
44 
45     private final PlaybackModel.PlaybackObserver mPlaybackObserver =
46             new PlaybackModel.PlaybackObserver() {
47                 @Override
48                 public void onPlaybackStateChanged() {
49                     updateState();
50                 }
51 
52                 @Override
53                 public void onSourceChanged() {
54                     updateState();
55                     updateMetadata();
56                 }
57 
58                 @Override
59                 public void onMetadataChanged() {
60                     updateMetadata();
61                 }
62             };
63 
64     /**
65      * Create a new MetadataController that operates on the provided Views
66      * @param title Displays the track's title. Must not be {@code null}.
67      * @param subtitle Displays the track's artist. Must not be {@code null}.
68      * @param time Displays the track's progress as text. May be {@code null}.
69      * @param seekBar Displays the track's progress visually. Must not be {@code null}.
70      * @param albumArt Displays the track's album art. May be {@code null}.
71      */
MetadataController(@onNull TextView title, @NonNull TextView subtitle, @Nullable TextView time, @NonNull SeekBar seekBar, @Nullable ImageView albumArt)72     public MetadataController(@NonNull TextView title, @NonNull TextView subtitle,
73             @Nullable TextView time, @NonNull SeekBar seekBar, @Nullable ImageView albumArt) {
74         mTitle = title;
75         mSubtitle = subtitle;
76         mTime = time;
77         mSeekBar = seekBar;
78         mSeekBar.setOnTouchListener((view, event) -> true);
79         mAlbumArt = albumArt;
80         mAlbumArtSize = title.getContext().getResources()
81                 .getDimensionPixelSize(R.dimen.playback_album_art_size_large);
82     }
83 
84     /**
85      * Registers the {@link PlaybackModel} this widget will use to follow playback state.
86      * Consumers of this class must unregister the {@link PlaybackModel} by calling this method with
87      * null.
88      *
89      * @param model {@link PlaybackModel} to subscribe, or null to unsubscribe.
90      */
setModel(@ullable PlaybackModel model)91     public void setModel(@Nullable PlaybackModel model) {
92         if (mModel != null) {
93             mModel.unregisterObserver(mPlaybackObserver);
94         }
95         mModel = model;
96         if (mModel != null) {
97             mModel.registerObserver(mPlaybackObserver);
98         }
99     }
100 
updateState()101     private void updateState() {
102         updateProgress();
103 
104         mSeekBar.removeCallbacks(mSeekBarRunnable);
105         if (mModel != null && mModel.isPlaying()) {
106             mSeekBar.post(mSeekBarRunnable);
107         }
108     }
109 
updateMetadata()110     private void updateMetadata() {
111         if(mUpdatesPaused) {
112             mNeedsMetadataUpdate = true;
113             return;
114         }
115 
116         mNeedsMetadataUpdate = false;
117         MediaItemMetadata metadata = mModel != null ? mModel.getMetadata() : null;
118         mTitle.setText(metadata != null ? metadata.getTitle() : null);
119         mSubtitle.setText(metadata != null ? metadata.getSubtitle() : null);
120         if (mAlbumArt != null && metadata != null && (metadata.getAlbumArtUri() != null
121                 || metadata.getAlbumArtBitmap() != null)) {
122             mAlbumArt.setVisibility(View.VISIBLE);
123             metadata.getAlbumArt(mAlbumArt.getContext(), mAlbumArtSize, mAlbumArtSize, true)
124                     .thenAccept(mAlbumArt::setImageBitmap);
125         } else if (mAlbumArt != null) {
126             mAlbumArt.setVisibility(View.GONE);
127         }
128     }
129 
130     private static final long SEEK_BAR_UPDATE_TIME_INTERVAL_MS = 1000;
131 
132     private final Runnable mSeekBarRunnable = new Runnable() {
133         @Override
134         public void run() {
135             if (mModel == null || !mModel.isPlaying()) {
136                 return;
137             }
138             updateProgress();
139             mSeekBar.postDelayed(this, SEEK_BAR_UPDATE_TIME_INTERVAL_MS);
140 
141         }
142     };
143 
updateProgress()144     private void updateProgress() {
145         if (mModel == null) {
146             mTime.setVisibility(View.INVISIBLE);
147             mSeekBar.setVisibility(View.INVISIBLE);
148             return;
149         }
150         long maxProgress = mModel.getMaxProgress();
151         long progress = mModel.getProgress();
152         int visibility = maxProgress > 0 && progress != PlaybackState.PLAYBACK_POSITION_UNKNOWN
153                 ? View.VISIBLE : View.INVISIBLE;
154         if (mTime != null) {
155             String time = String.format("%s / %s",
156                     TIME_FORMAT.format(new Date(progress)),
157                     TIME_FORMAT.format(new Date(maxProgress)));
158             mTime.setVisibility(visibility);
159             mTime.setText(time);
160         }
161         mSeekBar.setVisibility(visibility);
162         mSeekBar.setMax((int) maxProgress);
163         mSeekBar.setProgress((int) progress);
164     }
165 
166 
pauseUpdates()167     public void pauseUpdates() {
168         mUpdatesPaused = true;
169     }
170 
resumeUpdates()171     public void resumeUpdates() {
172         mUpdatesPaused = false;
173         if (mNeedsMetadataUpdate) {
174             updateMetadata();
175         }
176     }
177 }
178