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);
+        }
+    }
+}