Update media metadata and linear progress bar in playback view
Bug: 128525472
Test: manual
Run test with "atest
car-media-common/tests/robotests/src/com/android/car/media/common/MediaItemMetadataTest.java"
Change-Id: I8efa9358476d91e154a69997c1e6c43b17043b94
diff --git a/car-apps-common/res/values-night/colors.xml b/car-apps-common/res/values-night/colors.xml
index 4afc3f4..2b24406 100644
--- a/car-apps-common/res/values-night/colors.xml
+++ b/car-apps-common/res/values-night/colors.xml
@@ -18,4 +18,6 @@
<resources>
<color name="car_tab_unselected_color">@color/car_tab_unselected_color_dark</color>
<color name="minimized_control_bar_background_color">#E00E1013</color>
+ <color name="primary_text_color">#E0FFFFFF</color>
+ <color name="secondary_text_color">#B7FFFFFF</color>
</resources>
diff --git a/car-apps-common/res/values/colors.xml b/car-apps-common/res/values/colors.xml
index 019191c..6e6854f 100644
--- a/car-apps-common/res/values/colors.xml
+++ b/car-apps-common/res/values/colors.xml
@@ -43,4 +43,7 @@
<color name="car_tab_unselected_color">@color/car_tab_unselected_color_light</color>
<color name="car_tab_unselected_color_dark">#99FFFFFF</color>
<color name="car_tab_unselected_color_light">#B7FFFFFF</color>
+
+ <color name="primary_text_color">#FFFFFFFF</color>
+ <color name="secondary_text_color">#90FFFFFF</color>
</resources>
diff --git a/car-apps-common/res/values/dimens.xml b/car-apps-common/res/values/dimens.xml
index cd80605..e181c48 100644
--- a/car-apps-common/res/values/dimens.xml
+++ b/car-apps-common/res/values/dimens.xml
@@ -53,4 +53,21 @@
<dimen name="car_tab_padding_x">@*android:dimen/car_padding_3</dimen>
<dimen name="car_tab_icon_size">@*android:dimen/car_primary_icon_size</dimen>
+ <!-- Padding -->
+ <dimen name="padding_0">4dp</dimen>
+ <dimen name="padding_1">10dp</dimen>
+ <dimen name="padding_2">12dp</dimen>
+ <dimen name="padding_3">16dp</dimen>
+ <dimen name="padding_4">20dp</dimen>
+ <dimen name="padding_5">40dp</dimen>
+ <dimen name="padding_6">64dp</dimen>
+
+ <!-- Letter spacing -->
+ <item name="letter_spacing_display1" format="float" type="dimen">0.0</item>
+ <item name="letter_spacing_display2" format="float" type="dimen">0.0</item>
+ <item name="letter_spacing_display3" format="float" type="dimen">0.0</item>
+ <item name="letter_spacing_body1" format="float" type="dimen">0.0</item>
+ <item name="letter_spacing_body2" format="float" type="dimen">0.0</item>
+ <item name="letter_spacing_body3" format="float" type="dimen">0.0</item>
+
</resources>
diff --git a/car-apps-common/res/values/styles.xml b/car-apps-common/res/values/styles.xml
index 39188d6..7582c50 100644
--- a/car-apps-common/res/values/styles.xml
+++ b/car-apps-common/res/values/styles.xml
@@ -41,4 +41,41 @@
<item name="android:textColor">@color/car_tab_item_selector</item>
</style>
+
+ <!-- Styles for text. Sub1-3 are not included here as their use should be exceptional -->
+ <style name="TextAppearance">
+ <item name="android:fontFamily">roboto-regular</item>
+ <item name="android:textColor">@color/primary_text_color</item>
+ </style>
+
+ <style name="TextAppearance.Display1" parent="TextAppearance">
+ <item name="android:textSize">56sp</item>
+ <item name="android:letterSpacing">@dimen/letter_spacing_display1</item>
+ </style>
+
+ <style name="TextAppearance.Display2" parent="TextAppearance">
+ <item name="android:textSize">44sp</item>
+ <item name="android:letterSpacing">@dimen/letter_spacing_display2</item>
+ </style>
+
+ <style name="TextAppearance.Display3" parent="TextAppearance">
+ <item name="android:textSize">36sp</item>
+ <item name="android:letterSpacing">@dimen/letter_spacing_display3</item>
+ </style>
+
+ <style name="TextAppearance.Body1" parent="TextAppearance">
+ <item name="android:textSize">32sp</item>
+ <item name="android:letterSpacing">@dimen/letter_spacing_body1</item>
+ </style>
+
+ <style name="TextAppearance.Body2" parent="TextAppearance">
+ <item name="android:textSize">28sp</item>
+ <item name="android:letterSpacing">@dimen/letter_spacing_body2</item>
+ </style>
+
+ <style name="TextAppearance.Body3" parent="TextAppearance">
+ <item name="android:textSize">24sp</item>
+ <item name="android:letterSpacing">@dimen/letter_spacing_body3</item>
+ </style>
+
</resources>
diff --git a/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java b/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
index 9f0a7ef..786e3a6 100644
--- a/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
+++ b/car-media-common/src/com/android/car/media/common/MediaItemMetadata.java
@@ -33,6 +33,8 @@
import android.view.View;
import android.widget.ImageView;
+import androidx.annotation.VisibleForTesting;
+
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.request.RequestOptions;
@@ -54,24 +56,28 @@
private final Long mQueueId;
private final boolean mIsBrowsable;
private final boolean mIsPlayable;
+ private final String mAlbumTitle;
+ private final String mArtist;
public MediaItemMetadata(@NonNull MediaDescriptionCompat description) {
- this(description, null, false, false);
+ this(description, null, false, false, null, null);
}
/** Creates an instance based on a {@link MediaMetadataCompat} */
public MediaItemMetadata(@NonNull MediaMetadataCompat metadata) {
- this(metadata.getDescription(), null, false, false);
+ this(metadata.getDescription(), null, false, false,
+ metadata.getString(MediaMetadataCompat.METADATA_KEY_ALBUM),
+ metadata.getString(MediaMetadataCompat.METADATA_KEY_ARTIST));
}
/** Creates an instance based on a {@link MediaSessionCompat.QueueItem} */
public MediaItemMetadata(@NonNull MediaSessionCompat.QueueItem queueItem) {
- this(queueItem.getDescription(), queueItem.getQueueId(), false, true);
+ this(queueItem.getDescription(), queueItem.getQueueId(), false, true, null, null);
}
/** Creates an instance based on a {@link MediaBrowserCompat.MediaItem} */
public MediaItemMetadata(@NonNull MediaBrowserCompat.MediaItem item) {
- this(item.getDescription(), null, item.isBrowsable(), item.isPlayable());
+ this(item.getDescription(), null, item.isBrowsable(), item.isPlayable(), null, null);
}
/** Creates an instance based on a {@link Parcel} */
@@ -81,6 +87,8 @@
mQueueId = in.readByte() == 0x00 ? null : in.readLong();
mIsBrowsable = in.readByte() != 0x00;
mIsPlayable = in.readByte() != 0x00;
+ mAlbumTitle = in.readString();
+ mArtist = in.readString();
}
/**
@@ -94,14 +102,19 @@
mQueueId = item.mQueueId;
mIsBrowsable = item.mIsBrowsable;
mIsPlayable = item.mIsPlayable;
+ mAlbumTitle = item.mAlbumTitle;
+ mArtist = item.mArtist;
}
- private MediaItemMetadata(MediaDescriptionCompat description, Long queueId, boolean isBrowsable,
- boolean isPlayable) {
+ @VisibleForTesting
+ public MediaItemMetadata(MediaDescriptionCompat description, Long queueId, boolean isBrowsable,
+ boolean isPlayable, String albumTitle, String artist) {
mMediaDescription = description;
mQueueId = queueId;
mIsPlayable = isPlayable;
mIsBrowsable = isBrowsable;
+ mAlbumTitle = albumTitle;
+ mArtist = artist;
}
/** @return media item id */
@@ -122,10 +135,16 @@
return mMediaDescription.getSubtitle();
}
- /** @return media item description */
+ /** @return the album title for the media */
@Nullable
- public CharSequence getDescription() {
- return mMediaDescription.getSubtitle();
+ public String getAlbumTitle() {
+ return mAlbumTitle;
+ }
+
+ /** @return the artist of the media */
+ @Nullable
+ public CharSequence getArtist() {
+ return mArtist;
}
/**
@@ -330,6 +349,8 @@
&& Objects.equals(getId(), that.getId())
&& Objects.equals(getTitle(), that.getTitle())
&& Objects.equals(getSubtitle(), that.getSubtitle())
+ && Objects.equals(getAlbumTitle(), that.getAlbumTitle())
+ && Objects.equals(getArtist(), that.getArtist())
&& Objects.equals(getAlbumArtUri(), that.getAlbumArtUri())
&& Objects.equals(mQueueId, that.mQueueId);
}
@@ -355,6 +376,8 @@
}
dest.writeByte((byte) (mIsBrowsable ? 0x01 : 0x00));
dest.writeByte((byte) (mIsPlayable ? 0x01 : 0x00));
+ dest.writeString(mAlbumTitle);
+ dest.writeString(mArtist);
}
@SuppressWarnings("unused")
@@ -378,9 +401,13 @@
+ ", Queue Id: "
+ (mQueueId != null ? mQueueId : "-")
+ ", title: "
- + (mMediaDescription != null ? mMediaDescription.getTitle() : "-")
+ + mMediaDescription != null ? mMediaDescription.getTitle().toString() : "-"
+ ", subtitle: "
- + (mMediaDescription != null ? mMediaDescription.getSubtitle() : "-")
+ + mMediaDescription != null ? mMediaDescription.getSubtitle().toString() : "-"
+ + ", album title: "
+ + mAlbumTitle != null ? mAlbumTitle : "-"
+ + ", artist: "
+ + mArtist != null ? mArtist : "-"
+ ", album art URI: "
+ (mMediaDescription != null ? mMediaDescription.getIconUri() : "-")
+ "]";
diff --git a/car-media-common/src/com/android/car/media/common/MetadataController.java b/car-media-common/src/com/android/car/media/common/MetadataController.java
index fe426b7..e39bcde 100644
--- a/car-media-common/src/com/android/car/media/common/MetadataController.java
+++ b/car-media-common/src/com/android/car/media/common/MetadataController.java
@@ -32,6 +32,7 @@
import android.graphics.Bitmap;
import android.graphics.drawable.ColorDrawable;
import android.media.session.PlaybackState;
+import android.text.TextUtils;
import android.view.View;
import android.widget.ImageView;
import android.widget.SeekBar;
@@ -81,14 +82,15 @@
* @param viewModel The ViewModel to provide metadata for display
* @param pauseUpdates Views will not update while this LiveData emits {@code true}
* @param title Displays the track's title. Must not be {@code null}.
- * @param subtitle Displays the track's artist. Must not be {@code null}.
+ * @param albumTitle Displays the track's album title. May be {@code null}.
+ * @param artist Displays the track's artist. Must not be {@code null}.
* @param time Displays the track's progress as text. May be {@code null}.
* @param seekBar Displays the track's progress visually. May be {@code null}.
* @param albumArt Displays the track's album art. May be {@code null}.
*/
public MetadataController(@NonNull LifecycleOwner lifecycleOwner,
@NonNull PlaybackViewModel viewModel, @Nullable LiveData<Boolean> pauseUpdates,
- @NonNull TextView title, @NonNull TextView subtitle,
+ @NonNull TextView title, @Nullable TextView albumTitle, @NonNull TextView artist,
@Nullable TextView time, @Nullable SeekBar seekBar,
@Nullable ImageView albumArt, int albumArtSizePx) {
viewModel.getPlaybackController().observe(lifecycleOwner,
@@ -97,7 +99,19 @@
Model model = new Model(viewModel, pauseUpdates);
model.getTitle().observe(lifecycleOwner, title::setText);
- model.getSubtitle().observe(lifecycleOwner, subtitle::setText);
+
+ if (albumTitle != null) {
+ model.getAlbumTitle().observe(lifecycleOwner, albumName -> {
+ if (!TextUtils.isEmpty(albumName)) {
+ albumTitle.setText(albumName);
+ albumTitle.setVisibility(View.VISIBLE);
+ } else {
+ albumTitle.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ model.getArtist().observe(lifecycleOwner, artist::setText);
if (albumArt != null) {
model.setAlbumArtSize(albumArtSizePx);
@@ -114,7 +128,7 @@
if (time != null) {
model.hasTime().observe(lifecycleOwner,
visible -> time.setVisibility(visible ? View.VISIBLE : View.INVISIBLE));
- model.getTimeText().observe(lifecycleOwner, time::setText);
+ model.getTimeText().observe(lifecycleOwner, timeText -> time.setText(" - " + timeText));
}
if (seekBar != null) {
@@ -150,7 +164,8 @@
static class Model {
private final LiveData<CharSequence> mTitle;
- private final LiveData<CharSequence> mSubtitle;
+ private final LiveData<CharSequence> mAlbumTitle;
+ private final LiveData<CharSequence> mArtist;
private final LiveData<Bitmap> mAlbumArt;
private final LiveData<Long> mProgress;
private final LiveData<Long> mMaxProgress;
@@ -167,8 +182,10 @@
}
mTitle = freezable(pauseUpdates,
mapNonNull(playbackViewModel.getMetadata(), MediaItemMetadata::getTitle));
- mSubtitle = freezable(pauseUpdates,
- mapNonNull(playbackViewModel.getMetadata(), MediaItemMetadata::getSubtitle));
+ mAlbumTitle = freezable(pauseUpdates,
+ mapNonNull(playbackViewModel.getMetadata(), MediaItemMetadata::getAlbumTitle));
+ mArtist = freezable(pauseUpdates,
+ mapNonNull(playbackViewModel.getMetadata(), MediaItemMetadata::getArtist));
mAlbumArt = freezable(pauseUpdates,
switchMap(mAlbumArtSize,
size -> AlbumArtLiveData.getAlbumArt(
@@ -205,8 +222,12 @@
return mTitle;
}
- LiveData<CharSequence> getSubtitle() {
- return mSubtitle;
+ LiveData<CharSequence> getAlbumTitle() {
+ return mAlbumTitle;
+ }
+
+ LiveData<CharSequence> getArtist() {
+ return mArtist;
}
LiveData<Bitmap> getAlbumArt() {
diff --git a/car-media-common/src/com/android/car/media/common/MinimizedPlaybackControlBar.java b/car-media-common/src/com/android/car/media/common/MinimizedPlaybackControlBar.java
index 4a69b13..2c4aa7f 100644
--- a/car-media-common/src/com/android/car/media/common/MinimizedPlaybackControlBar.java
+++ b/car-media-common/src/com/android/car/media/common/MinimizedPlaybackControlBar.java
@@ -59,7 +59,7 @@
public void setModel(@NonNull PlaybackViewModel model, @NonNull LifecycleOwner owner) {
mMediaButtonController.setModel(model, owner);
mMetadataController = new MetadataController(owner, model,
- falseLiveData(), mTitle, mSubtitle, null, null, mContentTile,
+ falseLiveData(), mTitle, null, mSubtitle, null, null, mContentTile,
getContext().getResources().getDimensionPixelSize(
R.dimen.minimized_control_bar_content_tile_size));
}
diff --git a/car-media-common/tests/robotests/src/com/android/car/media/common/MediaItemMetadataTest.java b/car-media-common/tests/robotests/src/com/android/car/media/common/MediaItemMetadataTest.java
new file mode 100644
index 0000000..1373d4c
--- /dev/null
+++ b/car-media-common/tests/robotests/src/com/android/car/media/common/MediaItemMetadataTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2019 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Intent;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class MediaItemMetadataTest {
+ private static final String EXTRA_METADATA = "metadata";
+
+ @Test
+ public void testWriteReadParcel() {
+ String[] albums = {"album", "", null};
+ String[] artists = {"artist", "", null};
+ for (int i = 0; i < 3; i++) {
+ MediaItemMetadata metadata = new MediaItemMetadata(null, 0L, false, false, albums[i],
+ artists[i]);
+
+ Intent intent = new Intent().putExtra(EXTRA_METADATA, metadata);
+ MediaItemMetadata newMetadata = (MediaItemMetadata) intent.getExtras().getParcelable(
+ EXTRA_METADATA);
+
+ assertThat(metadata).isEqualTo(newMetadata);
+ }
+ }
+}