summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoshua Brown <keyboardr@google.com>2018-04-27 13:20:02 -0700
committerJoshua Brown <keyboardr@google.com>2018-05-02 11:40:05 -0700
commit30ff1007ab6729566d053649d3e973ac9fd33627 (patch)
tree523c2a5effee661a20c9b1b6be3b0b6a8876b86d
parent3b78b872b97b59880ad46c96114bc349570fc333 (diff)
downloadMedia-30ff1007ab6729566d053649d3e973ac9fd33627.tar.gz
DO NOT MERGE Show/hide playback queue
Flattens layouts so that they can be easily transitioned with ConstraintSets. Extracts metadata logic into MetadataController. Bug: 78784683 Test: Manual Change-Id: I55f2de884e278fc38fe23a64a84a48d156959747
-rw-r--r--res/layout/fragment_metadata.xml57
-rw-r--r--res/layout/fragment_metadata_with_queue.xml45
-rw-r--r--res/layout/fragment_playback.xml69
-rw-r--r--res/layout/fragment_playback_with_queue.xml43
-rw-r--r--res/layout/media_activity.xml3
-rw-r--r--res/layout/metadata_compact.xml27
-rw-r--r--res/layout/metadata_normal.xml62
-rw-r--r--res/layout/playback_controls.xml26
-rw-r--r--res/transition/queue_in.xml19
-rw-r--r--res/transition/queue_out.xml19
-rw-r--r--res/values/attrs.xml10
-rw-r--r--res/values/dimens.xml5
-rw-r--r--src/com/android/car/media/MetadataController.java153
-rw-r--r--src/com/android/car/media/PlaybackFragment.java140
-rw-r--r--src/com/android/car/media/widgets/MetadataView.java155
15 files changed, 532 insertions, 301 deletions
diff --git a/res/layout/fragment_metadata.xml b/res/layout/fragment_metadata.xml
new file mode 100644
index 0000000..e22410b
--- /dev/null
+++ b/res/layout/fragment_metadata.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:showIn="@layout/fragment_playback">
+
+ <ImageView
+ android:id="@+id/album_art"
+ android:layout_width="@dimen/playback_album_art_size_large"
+ android:layout_height="@dimen/playback_album_art_size_large"
+ android:layout_marginEnd="@dimen/car_keyline_2"
+ android:layout_marginStart="@dimen/car_keyline_2"
+ android:contentDescription="@string/album_art"
+ android:scaleType="centerCrop"
+ android:transitionName="@string/album_art"
+ app:layout_constraintBottom_toTopOf="@id/metadata_subcontainer"
+ app:layout_constraintEnd_toEndOf="@+id/margin_end"
+ app:layout_constraintStart_toStartOf="@+id/margin_start"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintVertical_chainStyle="packed"
+ tools:src="@drawable/ic_person"/>
+
+ <include android:id="@+id/metadata_subcontainer"
+ layout="@layout/metadata_normal"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/car_padding_5"
+ app:layout_constraintBottom_toTopOf="@+id/playback_controls"
+ app:layout_constraintEnd_toEndOf="@+id/album_art"
+ app:layout_constraintStart_toStartOf="@+id/album_art"
+ app:layout_constraintTop_toBottomOf="@+id/album_art"/>
+
+ <androidx.car.widget.PagedListView
+ android:id="@+id/queue_list"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:visibility="gone"
+ app:dividerEndMargin="@dimen/car_keyline_1"
+ app:dividerStartMargin="@dimen/car_keyline_1"
+ app:listDividerColor="@color/car_list_divider_inverse"/>
+</merge>
diff --git a/res/layout/fragment_metadata_with_queue.xml b/res/layout/fragment_metadata_with_queue.xml
new file mode 100644
index 0000000..ba204a2
--- /dev/null
+++ b/res/layout/fragment_metadata_with_queue.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ tools:showIn="@layout/fragment_playback_with_queue">
+
+
+ <ImageView
+ android:id="@+id/album_art"
+ android:layout_width="@dimen/playback_album_art_size_normal"
+ android:layout_height="@dimen/playback_album_art_size_normal"
+ android:layout_marginTop="@dimen/car_padding_4"
+ android:layout_marginStart="@dimen/car_keyline_1"
+ android:contentDescription="@string/album_art"
+ android:scaleType="centerCrop"
+ android:transitionName="@string/album_art"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="@+id/margin_start"
+ tools:src="@drawable/ic_person"/>
+
+ <include android:id="@+id/metadata_subcontainer"
+ layout="@layout/metadata_normal"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/car_padding_4"
+ android:layout_marginEnd="@dimen/car_keyline_1"
+ app:layout_constraintBottom_toBottomOf="@+id/album_art"
+ app:layout_constraintEnd_toEndOf="@+id/margin_end"
+ app:layout_constraintStart_toEndOf="@+id/album_art"
+ app:layout_constraintTop_toTopOf="@+id/album_art"/>
+
+ <androidx.car.widget.PagedListView
+ android:id="@+id/queue_list"
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_marginTop="@dimen/car_padding_4"
+ app:dividerEndMargin="@dimen/car_keyline_1"
+ app:dividerStartMargin="@dimen/car_keyline_1"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior"
+ app:layout_constraintBottom_toTopOf="@+id/playback_controls"
+ app:layout_constraintTop_toBottomOf="@+id/album_art"
+ app:listDividerColor="@color/car_list_divider_inverse"/>
+
+</merge>
diff --git a/res/layout/fragment_playback.xml b/res/layout/fragment_playback.xml
index 52f7ab2..bda3f01 100644
--- a/res/layout/fragment_playback.xml
+++ b/res/layout/fragment_playback.xml
@@ -18,64 +18,27 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/playback_container"
- android:animateLayoutChanges="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
- <LinearLayout
- android:id="@+id/metadata_container"
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="@dimen/car_padding_4"
- android:paddingBottom="@dimen/car_padding_4"
- android:layout_marginLeft="@dimen/car_margin"
- android:layout_marginRight="@dimen/car_margin"
- android:gravity="center_vertical"
- app:layout_constraintTop_toTopOf="parent">
-
- <ImageView
- android:id="@+id/album_art"
- android:contentDescription="@string/album_art"
- android:layout_marginStart="@dimen/car_keyline_1"
- android:layout_marginEnd="@dimen/car_keyline_1"
- android:layout_width="@dimen/playback_album_art_height"
- android:layout_height="@dimen/playback_album_art_width"
- android:scaleType="centerCrop"
- android:transitionName="@string/album_art"/>
-
- <com.android.car.media.widgets.MetadataView
- android:id="@+id/metadata"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_marginEnd="@dimen/car_keyline_1"
- android:layout_weight="1"
- android:focusable="false"
- app:style="normal"/>
-
- </LinearLayout>
-
- <androidx.car.widget.PagedListView
- android:id="@+id/queue_list"
- android:layout_width="match_parent"
+ <android.support.constraint.Guideline
+ android:id="@+id/margin_start"
+ android:layout_width="0dp"
android:layout_height="0dp"
- android:layout_marginBottom="@dimen/car_padding_4"
- app:listDividerColor="@color/car_list_divider_inverse"
- app:dividerStartMargin="@dimen/car_keyline_1"
- app:dividerEndMargin="@dimen/car_keyline_1"
- app:layout_behavior="@string/appbar_scrolling_view_behavior"
- app:layout_constraintTop_toBottomOf="@+id/metadata_container"
- app:layout_constraintBottom_toTopOf="@+id/playback_controls"/>
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="@dimen/car_margin"/>
- <com.android.car.media.common.PlaybackControlsActionBar
- android:id="@+id/playback_controls"
- android:layout_width="match_parent"
+ <android.support.constraint.Guideline
+ android:id="@+id/margin_end"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_marginLeft="@dimen/car_margin"
- android:layout_marginRight="@dimen/car_margin"
- android:layout_marginTop="@dimen/car_padding_1"
- android:layout_marginBottom="@dimen/car_padding_1"
- app:columns="5"
- app:layout_constraintBottom_toBottomOf="parent"/>
+ android:orientation="vertical"
+ app:layout_constraintGuide_end="@dimen/car_margin"/>
+
+ <include
+ layout="@layout/fragment_metadata"/>
+
+ <include
+ layout="@layout/playback_controls"/>
</android.support.constraint.ConstraintLayout>
diff --git a/res/layout/fragment_playback_with_queue.xml b/res/layout/fragment_playback_with_queue.xml
new file mode 100644
index 0000000..f684603
--- /dev/null
+++ b/res/layout/fragment_playback_with_queue.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<android.support.constraint.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/playback_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.constraint.Guideline
+ android:id="@+id/margin_start"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="@dimen/car_margin"/>
+
+ <android.support.constraint.Guideline
+ android:id="@+id/margin_end"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintGuide_end="@dimen/car_margin"/>
+
+ <include
+ layout="@layout/fragment_metadata_with_queue"/>
+
+ <include
+ layout="@layout/playback_controls"/>
+
+</android.support.constraint.ConstraintLayout>
diff --git a/res/layout/media_activity.xml b/res/layout/media_activity.xml
index c9be97d..bf26aa9 100644
--- a/res/layout/media_activity.xml
+++ b/res/layout/media_activity.xml
@@ -78,8 +78,7 @@
android:id="@+id/metadata"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_weight="0.5"
- app:style="compact"/>
+ android:layout_weight="0.5"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>
diff --git a/res/layout/metadata_compact.xml b/res/layout/metadata_compact.xml
index 3d772a1..1a70254 100644
--- a/res/layout/metadata_compact.xml
+++ b/res/layout/metadata_compact.xml
@@ -15,36 +15,41 @@
limitations under the License.
-->
<merge
- xmlns:android="http://schemas.android.com/apk/res/android" >
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/car_padding_1"
- android:textAppearance="@style/TextAppearance.Car.Body1.Light"
- android:maxLines="1"
android:ellipsize="end"
- android:includeFontPadding="false"/>
+ android:includeFontPadding="false"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearance.Car.Body1.Light"
+ tools:text="Body 1 Header"/>
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/title"
android:layout_marginBottom="@dimen/car_padding_1"
- android:textAppearance="@style/TextAppearance.Car.Body2.Light"
- android:maxLines="1"
android:ellipsize="end"
- android:includeFontPadding="false"/>
+ android:includeFontPadding="false"
+ android:maxLines="1"
+ android:textAppearance="@style/TextAppearance.Car.Body2.Light"
+ tools:text="Body 2"/>
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/playback_seekbar_height"
android:layout_below="@+id/subtitle"
- android:paddingStart="0dp"
android:paddingEnd="0dp"
- android:visibility="invisible"
+ android:paddingStart="0dp"
android:progressDrawable="@drawable/seekbar_background"
- android:thumb="@drawable/seekbar_thumb"/>
+ android:thumb="@drawable/seekbar_thumb"
+ android:visibility="invisible"
+ tools:visibility="visible"
+ tools:progress="70"/>
-</merge> \ No newline at end of file
+</merge>
diff --git a/res/layout/metadata_normal.xml b/res/layout/metadata_normal.xml
index cdb63c4..a98e3e0 100644
--- a/res/layout/metadata_normal.xml
+++ b/res/layout/metadata_normal.xml
@@ -14,47 +14,67 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<merge
- xmlns:android="http://schemas.android.com/apk/res/android" >
+<android.support.constraint.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/metadata_subcontainer"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:focusable="false">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/car_padding_1"
- android:textAppearance="@style/TextAppearance.Car.Body1.Light"
- android:maxLines="@integer/playback_title_text_max_lines"
android:ellipsize="end"
- android:includeFontPadding="false"/>
+ android:includeFontPadding="false"
+ android:maxLines="@integer/playback_title_text_max_lines"
+ android:textAppearance="@style/TextAppearance.Car.Body1.Light"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:text="Body 1 Header"/>
<TextView
android:id="@+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@+id/title"
- android:layout_marginBottom="@dimen/car_padding_4"
- android:textAppearance="@style/TextAppearance.Car.Body2.Light"
- android:maxLines="@integer/playback_subtitle_text_max_lines"
+ android:layout_marginBottom="@dimen/car_padding_2"
+ android:layout_marginTop="@dimen/car_padding_1"
android:ellipsize="end"
- android:includeFontPadding="false"/>
+ android:includeFontPadding="false"
+ android:maxLines="@integer/playback_subtitle_text_max_lines"
+ android:textAppearance="@style/TextAppearance.Car.Body2.Light"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/title"
+ tools:text="Body 2"/>
<TextView
android:id="@+id/time"
- android:layout_width="wrap_content"
+ android:layout_width="160dp"
android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_below="@+id/title"
- android:textAppearance="@style/TextAppearance.Car.Body2.Light"
- android:maxLines="1"
+ android:layout_marginTop="@dimen/car_padding_1"
android:ellipsize="end"
- android:includeFontPadding="false"/>
+ android:includeFontPadding="false"
+ android:maxLines="@integer/playback_subtitle_text_max_lines"
+ android:gravity="end"
+ android:textAppearance="@style/TextAppearance.Car.Body2.Light"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/title"
+ tools:text="3:27 / 4:03"/>
<SeekBar
android:id="@+id/seek_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/playback_seekbar_height"
- android:layout_below="@+id/subtitle"
- android:paddingStart="0dp"
+ android:layout_marginTop="@dimen/car_padding_2"
+ android:background="@color/car_grey_900"
android:paddingEnd="0dp"
- android:visibility="invisible"
+ android:paddingStart="0dp"
android:progressDrawable="@drawable/seekbar_background"
- android:thumb="@drawable/seekbar_thumb"/>
+ android:thumb="@drawable/seekbar_thumb"
+ android:visibility="invisible"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/subtitle"
+ tools:progress="70"
+ tools:visibility="visible"/>
-</merge> \ No newline at end of file
+</android.support.constraint.ConstraintLayout>
diff --git a/res/layout/playback_controls.xml b/res/layout/playback_controls.xml
new file mode 100644
index 0000000..cbd41a7
--- /dev/null
+++ b/res/layout/playback_controls.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+<com.android.car.media.common.PlaybackControlsActionBar
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/playback_controls"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/car_margin"
+ android:layout_marginBottom="@dimen/car_padding_1"
+ app:columns="5"
+ app:layout_constraintBottom_toBottomOf="parent"/>
diff --git a/res/transition/queue_in.xml b/res/transition/queue_in.xml
new file mode 100644
index 0000000..9e7f798
--- /dev/null
+++ b/res/transition/queue_in.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:transitionOrdering="sequential">
+ <arcMotion/>
+ <fade android:fadingMode="fade_out"/>
+ <transitionSet android:transitionOrdering="together">
+ <changeBounds>
+ <targets>
+ <target android:targetId="@+id/album_art"/>
+ </targets>
+ </changeBounds>
+ <changeBounds android:startDelay="50">
+ <targets>
+ <target android:excludeId="@+id/album_art"/>
+ </targets>
+ </changeBounds>
+ </transitionSet>
+ <fade android:fadingMode="fade_in"/>
+</transitionSet>
diff --git a/res/transition/queue_out.xml b/res/transition/queue_out.xml
new file mode 100644
index 0000000..f0c1e3e
--- /dev/null
+++ b/res/transition/queue_out.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
+ android:transitionOrdering="sequential">
+ <arcMotion/>
+ <fade android:fadingMode="fade_out"/>
+ <transitionSet android:transitionOrdering="together">
+ <changeBounds android:startDelay="100">
+ <targets>
+ <target android:targetId="@+id/album_art"/>
+ </targets>
+ </changeBounds>
+ <changeBounds>
+ <targets>
+ <target android:excludeId="@+id/album_art"/>
+ </targets>
+ </changeBounds>
+ </transitionSet>
+ <fade android:fadingMode="fade_in"/>
+</transitionSet>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 4048b22..134f7f5 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -15,16 +15,6 @@
limitations under the License.
-->
<resources>
- <declare-styleable name="MetadataView">
- <!-- Style of media item metadata view -->
- <attr name="style" format="enum">
- <!-- Compact version (to be included in the bottom bar during browsing -->
- <enum name="compact" value="0"/>
- <!-- Normal version (to be included in the playback screen -->
- <enum name="normal" value="1"/>
- </attr>
- </declare-styleable>
-
<declare-styleable name="AppBarView">
<!-- Maximum number of tabs to show on the app bar -->
<attr name="max_tabs" format="integer"/>
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index a6f025f..65c654d 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -44,8 +44,9 @@
<!-- Scale to apply to images before blurring them to create the playback background -->
<item name="playback_background_blur_scale" format="float" type="dimen">1</item>
<!-- Size of the album art thumbnail -->
- <dimen name="playback_album_art_height">156dp</dimen>
- <dimen name="playback_album_art_width">156dp</dimen>
+ <dimen name="playback_album_art_size_normal">156dp</dimen>
+ <!-- Size of the album art thumbnail when large -->
+ <dimen name="playback_album_art_size_large">574dp</dimen>
<!-- Tab height -->
<dimen name="browse_tab_height">76dp</dimen>
diff --git a/src/com/android/car/media/MetadataController.java b/src/com/android/car/media/MetadataController.java
new file mode 100644
index 0000000..f5c382a
--- /dev/null
+++ b/src/com/android/car/media/MetadataController.java
@@ -0,0 +1,153 @@
+package com.android.car.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import com.android.car.media.common.MediaItemMetadata;
+import com.android.car.media.common.PlaybackModel;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.Objects;
+
+/**
+ * Common controller for displaying current track's metadata.
+ */
+public class MetadataController {
+
+ private static final DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss", Locale.US);
+
+ @NonNull
+ private final TextView mTitle;
+ @NonNull
+ private final TextView mSubtitle;
+ @Nullable
+ private final TextView mTime;
+ @NonNull
+ private final SeekBar mSeekBar;
+ @Nullable
+ private final ImageView mAlbumArt;
+
+ @Nullable
+ private PlaybackModel mModel;
+ @Nullable
+ private MediaItemMetadata mCurrentMetadata;
+
+ private final PlaybackModel.PlaybackObserver mPlaybackObserver =
+ new PlaybackModel.PlaybackObserver() {
+ @Override
+ public void onPlaybackStateChanged() {
+ updateState();
+ }
+
+ @Override
+ public void onSourceChanged() {
+ updateState();
+ updateMetadata();
+ }
+
+ @Override
+ public void onMetadataChanged() {
+ updateMetadata();
+ }
+ };
+
+ /**
+ * Create a new MetadataController that operates on the provided Views
+ * @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 time Displays the track's progress as text. May be {@code null}.
+ * @param seekBar Displays the track's progress visually. Must not be {@code null}.
+ * @param albumArt Displays the track's album art. May be {@code null}.
+ */
+ public MetadataController(@NonNull TextView title, @NonNull TextView subtitle,
+ @Nullable TextView time, @NonNull SeekBar seekBar, @Nullable ImageView albumArt) {
+ mTitle = title;
+ mSubtitle = subtitle;
+ mTime = time;
+ mSeekBar = seekBar;
+ mAlbumArt = albumArt;
+ }
+
+ /**
+ * Registers the {@link PlaybackModel} this widget will use to follow playback state.
+ * Consumers of this class must unregister the {@link PlaybackModel} by calling this method with
+ * null.
+ *
+ * @param model {@link PlaybackModel} to subscribe, or null to unsubscribe.
+ */
+ public void setModel(@Nullable PlaybackModel model) {
+ if (mModel != null) {
+ mModel.unregisterObserver(mPlaybackObserver);
+ }
+ mModel = model;
+ if (mModel != null) {
+ mModel.registerObserver(mPlaybackObserver);
+ }
+ }
+
+ private void updateState() {
+ updateProgress();
+
+ if (mModel != null && mModel.isPlaying()) {
+ mSeekBar.post(mSeekBarRunnable);
+ } else {
+ mSeekBar.removeCallbacks(mSeekBarRunnable);
+ }
+ }
+
+ private void updateMetadata() {
+ MediaItemMetadata metadata = mModel != null ? mModel.getMetadata() : null;
+ if (Objects.equals(mCurrentMetadata, metadata)) {
+ return;
+ }
+ mCurrentMetadata = metadata;
+ mTitle.setText(metadata != null ? metadata.getTitle() : null);
+ mSubtitle.setText(metadata != null ? metadata.getSubtitle() : null);
+ if (mAlbumArt != null) {
+ MediaItemMetadata.updateImageView(mAlbumArt.getContext(), metadata, mAlbumArt, 0);
+ }
+ }
+
+ private static final long SEEK_BAR_UPDATE_TIME_INTERVAL_MS = 1000;
+
+ private final Runnable mSeekBarRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (mModel == null || !mModel.isPlaying()) {
+ return;
+ }
+ updateProgress();
+ mSeekBar.postDelayed(this, SEEK_BAR_UPDATE_TIME_INTERVAL_MS);
+
+ }
+ };
+
+ private void updateProgress() {
+ if (mModel == null) {
+ mTime.setVisibility(View.INVISIBLE);
+ mSeekBar.setVisibility(View.INVISIBLE);
+ return;
+ }
+ long maxProgress = mModel.getMaxProgress();
+ int visibility = maxProgress > 0 ? View.VISIBLE : View.INVISIBLE;
+ if (mTime != null) {
+ String time = String.format("%s / %s",
+ TIME_FORMAT.format(new Date(mModel.getProgress())),
+ TIME_FORMAT.format(new Date(maxProgress)));
+ mTime.setVisibility(visibility);
+ mTime.setText(time);
+ }
+ mSeekBar.setVisibility(visibility);
+ mSeekBar.setMax((int) mModel.getMaxProgress());
+ mSeekBar.setProgress((int) mModel.getProgress());
+ }
+
+
+}
diff --git a/src/com/android/car/media/PlaybackFragment.java b/src/com/android/car/media/PlaybackFragment.java
index 6b4994a..b133ff8 100644
--- a/src/com/android/car/media/PlaybackFragment.java
+++ b/src/com/android/car/media/PlaybackFragment.java
@@ -18,30 +18,33 @@ package com.android.car.media;
import android.content.Context;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.constraint.ConstraintLayout;
+import android.support.constraint.ConstraintSet;
import android.support.v4.app.Fragment;
import android.support.v7.widget.RecyclerView;
+import android.transition.Transition;
+import android.transition.TransitionInflater;
+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 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 com.android.car.media.widgets.MetadataView;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-
-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;
/**
* A {@link Fragment} that implements both the playback and the content forward browsing experience.
@@ -50,34 +53,32 @@ import androidx.car.widget.TextListItem;
*/
public class PlaybackFragment extends Fragment {
private static final String TAG = "PlaybackFragment";
- private static final DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss", Locale.US);
private PlaybackModel mModel;
private PlaybackControls mPlaybackControls;
- private ImageView mAlbumArt;
- private MetadataView mMetadataView;
- private PagedListView mQueueList;
private QueueItemsAdapter mQueueAdapter;
- private MediaItemMetadata mCurrentMetadata;
- private boolean mQueueIsVisible;
- private PlaybackModel.PlaybackObserver mPlaybackObserver = new PlaybackModel.PlaybackObserver() {
- @Override
- public void onPlaybackStateChanged() {
- updateState();
- }
- @Override
- public void onSourceChanged() {
- updateState();
- updateMetadata();
- updateAccentColor();
- }
+ private MetadataController mMetadataController;
+ private ConstraintLayout mRootView;
- @Override
- public void onMetadataChanged() {
- updateMetadata();
- }
- };
+ private boolean mQueueIsVisible;
+ 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) {
@@ -95,6 +96,7 @@ public class PlaybackFragment extends Fragment {
textListItem.setOnClickListener(v -> onQueueItemClicked(item));
return textListItem;
}
+
@Override
public int size() {
if (!mModel.hasQueue()) {
@@ -103,6 +105,7 @@ public class PlaybackFragment extends Fragment {
return mModel.getQueue().size();
}
};
+
private static class QueueItemsAdapter extends ListItemAdapter {
QueueItemsAdapter(Context context, ListItemProvider itemProvider) {
super(context, itemProvider, BackgroundStyle.SOLID);
@@ -114,70 +117,91 @@ public class PlaybackFragment extends Fragment {
this.notifyDataSetChanged();
}
}
+
private PlaybackControls.Listener mPlaybackControlsListener = new PlaybackControls.Listener() {
@Override
public void onToggleQueue() {
mQueueIsVisible = !mQueueIsVisible;
mPlaybackControls.setQueueVisible(mQueueIsVisible);
+ setQueueVisible(mQueueIsVisible);
}
};
@Override
- public View onCreateView(LayoutInflater inflater, final ViewGroup container,
+ 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);
mModel = new PlaybackModel(getContext());
- mPlaybackControls = view.findViewById(R.id.playback_controls);
+
+ initPlaybackControls(view.findViewById(R.id.playback_controls));
+ initQueue(view.findViewById(R.id.queue_list));
+ initMetadataController(view);
+ return view;
+ }
+
+ private void initPlaybackControls(PlaybackControls playbackControls) {
+ mPlaybackControls = playbackControls;
mPlaybackControls.setModel(mModel);
mPlaybackControls.setListener(mPlaybackControlsListener);
- ViewGroup playbackContainer = view.findViewById(R.id.playback_container);
- mPlaybackControls.setAnimationViewGroup(playbackContainer);
- mAlbumArt = view.findViewById(R.id.album_art);
- mMetadataView = view.findViewById(R.id.metadata);
- mQueueList = view.findViewById(R.id.queue_list);
- RecyclerView recyclerView = mQueueList.getRecyclerView();
+ 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);
- mQueueList.setAdapter(mQueueAdapter);
+ queueList.setAdapter(mQueueAdapter);
+ }
- return view;
+ 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);
+ mMetadataController.setModel(mModel);
+ }
+
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ mPlaybackControls.setModel(null);
+ mMetadataController.setModel(null);
+ mMetadataController = null;
}
@Override
public void onStart() {
super.onStart();
mModel.registerObserver(mPlaybackObserver);
- mMetadataView.setModel(mModel);
}
@Override
public void onStop() {
super.onStop();
mModel.unregisterObserver(mPlaybackObserver);
- mMetadataView.setModel(null);
- mCurrentMetadata = null;
}
- @Override
- public void onDestroyView() {
- super.onDestroyView();
- mPlaybackControls.setModel(null);
+ public void setQueueVisible(boolean visible) {
+ Transition transition = TransitionInflater.from(getContext()).inflateTransition(
+ visible ? R.transition.queue_in : R.transition.queue_out);
+ TransitionManager.beginDelayedTransition(mRootView, transition);
+ ConstraintSet constraintSet = new ConstraintSet();
+ constraintSet.clone(mRootView.getContext(),
+ visible ? R.layout.fragment_playback_with_queue : R.layout.fragment_playback);
+ constraintSet.applyTo(mRootView);
+
}
private void updateState() {
mQueueAdapter.refresh();
}
- private void updateMetadata() {
- MediaItemMetadata metadata = mModel.getMetadata();
- if (Objects.equals(mCurrentMetadata, metadata)) {
- return;
- }
- mCurrentMetadata = metadata;
- MediaItemMetadata.updateImageView(getContext(), metadata, mAlbumArt, 0);
- }
private void updateAccentColor() {
int defaultColor = getResources().getColor(android.R.color.background_dark, null);
diff --git a/src/com/android/car/media/widgets/MetadataView.java b/src/com/android/car/media/widgets/MetadataView.java
index 4b3fb38..6d9e72a 100644
--- a/src/com/android/car/media/widgets/MetadataView.java
+++ b/src/com/android/car/media/widgets/MetadataView.java
@@ -1,117 +1,43 @@
package com.android.car.media.widgets;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
-import android.annotation.IntDef;
import android.annotation.Nullable;
import android.content.Context;
-import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
-import android.view.View;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
+import com.android.car.media.MetadataController;
import com.android.car.media.R;
-import com.android.car.media.common.MediaItemMetadata;
import com.android.car.media.common.PlaybackModel;
-import java.lang.annotation.Retention;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Objects;
-
/**
* A view that can be used to display the metadata and playback progress of a media item.
* This view can be styled using the "MetadataView" styleable attributes.
*/
public class MetadataView extends RelativeLayout {
- private static final DateFormat TIME_FORMAT = new SimpleDateFormat("m:ss", Locale.US);
-
- @Nullable
- private MediaItemMetadata mCurrentMetadata;
- private TextView mTitle;
- private TextView mTime;
- private TextView mSubtitle;
- private SeekBar mSeekbar;
- @Nullable
- private PlaybackModel mModel;
-
- private PlaybackModel.PlaybackObserver mPlaybackObserver =
- new PlaybackModel.PlaybackObserver() {
- @Override
- public void onPlaybackStateChanged() {
- updateState();
- }
-
- @Override
- public void onSourceChanged() {
- updateState();
- updateMetadata();
- }
-
- @Override
- public void onMetadataChanged() {
- updateMetadata();
- }
- };
-
- /**
- * The possible styles of this widget.
- */
- @IntDef({
- MetadataView.Style.COMPACT,
- MetadataView.Style.NORMAL
- })
- @Retention(SOURCE)
- public @interface Style {
- /** Compact style (small space between elements, no time progress indicator) */
- int COMPACT = 0;
- /** Normal style (normal spacing and progress indicator) */
- int NORMAL = 1;
- }
+ private final MetadataController mMetadataController;
public MetadataView(Context context) {
- super(context);
- init(context, null, 0, 0);
+ this(context, null);
}
public MetadataView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context, attrs, 0, 0);
+ this(context, attrs, 0);
}
public MetadataView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- init(context, attrs, defStyleAttr, 0);
+ this(context, attrs, defStyleAttr, 0);
}
public MetadataView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- init(context, attrs, defStyleAttr, defStyleRes);
- }
-
- private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
- TypedArray ta = context.obtainStyledAttributes(
- attrs, R.styleable.MetadataView, defStyleAttr, defStyleRes);
- @Style int style = ta.getInteger(R.styleable.MetadataView_style, Style.NORMAL);
- ta.recycle();
-
- int layoutId = style == Style.COMPACT
- ? R.layout.metadata_compact
- : R.layout.metadata_normal;
-
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(layoutId, this, true);
-
- mTitle = findViewById(R.id.title);
- mSubtitle = findViewById(R.id.subtitle);
- mSeekbar = findViewById(R.id.seek_bar);
- mTime = findViewById(R.id.time);
+ LayoutInflater.from(context).inflate(R.layout.metadata_compact, this, true);
+ TextView title = findViewById(R.id.title);
+ TextView subtitle = findViewById(R.id.subtitle);
+ SeekBar seekBar = findViewById(R.id.seek_bar);
+ mMetadataController = new MetadataController(title, subtitle, null, seekBar, null);
}
/**
@@ -122,66 +48,7 @@ public class MetadataView extends RelativeLayout {
* @param model {@link PlaybackModel} to subscribe, or null to unsubscribe.
*/
public void setModel(@Nullable PlaybackModel model) {
- if (mModel != null) {
- mModel.unregisterObserver(mPlaybackObserver);
- }
- mModel = model;
- if (mModel != null) {
- mModel.registerObserver(mPlaybackObserver);
- }
- }
-
- private void updateState() {
- updateProgress();
-
- if (mModel != null && mModel.isPlaying()) {
- mSeekbar.post(mSeekBarRunnable);
- } else {
- mSeekbar.removeCallbacks(mSeekBarRunnable);
- }
+ mMetadataController.setModel(model);
}
- private void updateMetadata() {
- MediaItemMetadata metadata = mModel != null ? mModel.getMetadata() : null;
- if (Objects.equals(mCurrentMetadata, metadata)) {
- return;
- }
- mCurrentMetadata = metadata;
- mTitle.setText(metadata != null ? metadata.getTitle() : null);
- mSubtitle.setText(metadata != null ? metadata.getSubtitle() : null);
- }
-
- private static final long SEEK_BAR_UPDATE_TIME_INTERVAL_MS = 1000;
-
- private final Runnable mSeekBarRunnable = new Runnable() {
- @Override
- public void run() {
- if (mModel == null || !mModel.isPlaying()) {
- return;
- }
- updateProgress();
- mSeekbar.postDelayed(this, SEEK_BAR_UPDATE_TIME_INTERVAL_MS);
-
- }
- };
-
- private void updateProgress() {
- if (mModel == null) {
- mTime.setVisibility(View.INVISIBLE);
- mSeekbar.setVisibility(View.INVISIBLE);
- return;
- }
- long maxProgress = mModel.getMaxProgress();
- int visibility = maxProgress > 0 ? View.VISIBLE : View.INVISIBLE;
- if (mTime != null) {
- String time = String.format("%s / %s",
- TIME_FORMAT.format(new Date(mModel.getProgress())),
- TIME_FORMAT.format(new Date(maxProgress)));
- mTime.setVisibility(visibility);
- mTime.setText(time);
- }
- mSeekbar.setVisibility(visibility);
- mSeekbar.setMax((int) mModel.getMaxProgress());
- mSeekbar.setProgress((int) mModel.getProgress());
- }
}