diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2018-05-20 07:20:27 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2018-05-20 07:20:27 +0000 |
commit | 8c87b5efeb47ccad6e04450bc46c7c1a4894091f (patch) | |
tree | 978f40544280f2e2be23f64ded330a4f7f259d79 | |
parent | d66d86d340cf948acbb34cfa7c68afcf4cdd7714 (diff) | |
parent | 1d3e7e02676ed495b9f99701fa4647aa524d3f1b (diff) | |
download | Media-8c87b5efeb47ccad6e04450bc46c7c1a4894091f.tar.gz |
Snap for 4793185 from 1d3e7e02676ed495b9f99701fa4647aa524d3f1b to pi-release
Change-Id: I1876cfe2965675b4b40817ab9115b7f12dce8071
23 files changed, 473 insertions, 223 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 47da7be..76e2464 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -16,6 +16,7 @@ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" + android:sharedUserId="com.android.car.media" package="com.android.car.media" > <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/> diff --git a/res/layout-h1200dp/fragment_metadata.xml b/res/layout-h1200dp/fragment_metadata.xml new file mode 100644 index 0000000..63412aa --- /dev/null +++ b/res/layout-h1200dp/fragment_metadata.xml @@ -0,0 +1,65 @@ +<?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:background="@color/car_body1_light" + 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" + android:layout_marginBottom="@dimen/playback_controls_margin" + app:layout_constraintBottom_toBottomOf="parent" + 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="match_parent" + android:layout_height="0dp" + android:layout_marginTop="@dimen/playback_album_art_size_normal" + android:layout_marginBottom="@dimen/playback_controls_margin" + android:visibility="gone" + app:dividerEndMargin="@dimen/car_keyline_1" + app:dividerStartMargin="@dimen/car_keyline_1" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@+id/margin_top" + app:listDividerColor="@color/car_list_divider_light"/> +</merge> diff --git a/res/layout/fragment_metadata_with_queue.xml b/res/layout-h1200dp/fragment_metadata_with_queue.xml index f3474f0..26bfb26 100644 --- a/res/layout/fragment_metadata_with_queue.xml +++ b/res/layout-h1200dp/fragment_metadata_with_queue.xml @@ -28,6 +28,7 @@ android:layout_marginTop="@dimen/car_padding_4" android:layout_marginStart="@dimen/car_keyline_1" android:contentDescription="@string/album_art" + android:background="@color/car_body1_light" android:scaleType="centerCrop" android:transitionName="@string/album_art" app:layout_constraintTop_toTopOf="parent" diff --git a/res/layout/fragment_playback_with_queue.xml b/res/layout-h1200dp/fragment_playback_with_queue.xml index 113de90..113de90 100644 --- a/res/layout/fragment_playback_with_queue.xml +++ b/res/layout-h1200dp/fragment_playback_with_queue.xml diff --git a/res/layout/browse_state.xml b/res/layout/browse_state.xml new file mode 100644 index 0000000..f85b1a2 --- /dev/null +++ b/res/layout/browse_state.xml @@ -0,0 +1,51 @@ +<?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:tools="http://schemas.android.com/tools" + tools:showIn="@layout/fragment_browse"> + + <ProgressBar + android:id="@+id/loading_spinner" + android:layout_width="match_parent" + android:layout_height="@dimen/car_double_line_list_item_height" + android:layout_centerInParent="true" + android:visibility="gone" + android:indeterminateDrawable="@drawable/music_buffering" /> + + <ImageView + android:id="@+id/error_icon" + android:layout_width="@dimen/missing_permission_icon_size" + android:layout_height="@dimen/missing_permission_icon_size" + android:layout_centerInParent="true" + android:src="@drawable/error_illustration" + android:visibility="gone" + android:tint="@color/car_body2_light"/> + + <TextView + android:id="@+id/error_message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/error_icon" + android:layout_marginTop="@dimen/car_padding_4" + android:textAppearance="@style/TextAppearance.Car.Body2.Light" + android:gravity="center" + android:maxLines="3" + android:text="@string/nothing_to_play" + android:visibility="gone" /> + +</merge>
\ No newline at end of file diff --git a/res/layout/fragment_browse.xml b/res/layout/fragment_browse.xml index 30dcf03..9e9eca5 100644 --- a/res/layout/fragment_browse.xml +++ b/res/layout/fragment_browse.xml @@ -21,33 +21,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <ProgressBar - android:id="@+id/loading_spinner" - android:layout_width="match_parent" - android:layout_height="@dimen/car_double_line_list_item_height" - android:layout_centerInParent="true" - android:layout_alignWithParentIfMissing="true" - android:indeterminateDrawable="@drawable/music_buffering" - android:visibility="gone" /> - - <ImageView - android:id="@+id/error_icon" - android:layout_width="@dimen/missing_permission_icon_size" - android:layout_height="@dimen/missing_permission_icon_size" - android:layout_centerInParent="true" - android:src="@drawable/error_illustration" - android:visibility="gone" /> - - <TextView - android:id="@+id/error_message" - style="@style/TextAppearance.NoContent" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_below="@+id/error_icon" - android:gravity="center" - android:maxLines="3" - android:text="@string/nothing_to_play" - android:visibility="gone" /> + <include layout="@layout/browse_state"/> <androidx.car.widget.PagedListView android:id="@+id/browse_list" diff --git a/res/layout/fragment_empty.xml b/res/layout/fragment_empty.xml index a7d7a53..7f53519 100644 --- a/res/layout/fragment_empty.xml +++ b/res/layout/fragment_empty.xml @@ -16,16 +16,10 @@ --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/browse_container" + android:id="@+id/empty_container" android:layout_width="match_parent" android:layout_height="match_parent"> - <ProgressBar - android:id="@+id/loading_spinner" - android:layout_width="match_parent" - android:layout_height="@dimen/car_double_line_list_item_height" - android:layout_centerInParent="true" - android:visibility="gone" - android:indeterminateDrawable="@drawable/music_buffering" /> + <include layout="@layout/browse_state"/> </RelativeLayout>
\ No newline at end of file diff --git a/res/layout/fragment_metadata.xml b/res/layout/fragment_metadata.xml index ffeaf95..4324681 100644 --- a/res/layout/fragment_metadata.xml +++ b/res/layout/fragment_metadata.xml @@ -18,22 +18,21 @@ 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"> + tools:showIn="@layout/fragment_playback_with_queue"> + <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:layout_width="@dimen/playback_album_art_size_normal" + android:layout_height="@dimen/playback_album_art_size_normal" + android:layout_marginStart="@dimen/car_keyline_1" android:contentDescription="@string/album_art" + android:background="@color/car_body1_light" 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" + app:layout_constraintBottom_toTopOf="@+id/playback_controls" + app:layout_constraintStart_toStartOf="@+id/margin_start" tools:src="@drawable/ic_person"/> <include @@ -41,24 +40,25 @@ layout="@layout/metadata_normal" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginTop="@dimen/car_padding_5" - android:layout_marginBottom="@dimen/playback_controls_margin" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@+id/album_art" - app:layout_constraintStart_toStartOf="@+id/album_art" - app:layout_constraintTop_toBottomOf="@+id/album_art"/> + 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/playback_album_art_size_normal" + android:layout_marginTop="@dimen/car_padding_4" android:layout_marginBottom="@dimen/playback_controls_margin" android:visibility="gone" app:dividerEndMargin="@dimen/car_keyline_1" app:dividerStartMargin="@dimen/car_keyline_1" app:layout_behavior="@string/appbar_scrolling_view_behavior" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintTop_toBottomOf="@+id/margin_top" + app:layout_constraintTop_toBottomOf="@+id/album_art" app:listDividerColor="@color/car_list_divider_light"/> + </merge> diff --git a/res/layout/media_activity.xml b/res/layout/media_activity.xml index 7a45458..050a9c8 100644 --- a/res/layout/media_activity.xml +++ b/res/layout/media_activity.xml @@ -39,8 +39,7 @@ android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_marginTop="@dimen/car_padding_4" - android:visibility="gone" + android:visibility="visible" app:layout_constraintTop_toBottomOf="@+id/app_bar" app:layout_constraintBottom_toTopOf="@+id/browse_controls_container"/> @@ -48,7 +47,6 @@ android:id="@+id/playback_container" android:layout_width="match_parent" android:layout_height="0dp" - android:layout_marginTop="@dimen/car_padding_4" android:visibility="gone" app:layout_constraintTop_toBottomOf="@+id/app_bar" app:layout_constraintBottom_toBottomOf="parent"/> @@ -86,7 +84,7 @@ <com.android.car.media.widgets.AppBarView android:id="@+id/app_bar" - android:layout_height="@dimen/appbar_height" + android:layout_height="@dimen/car_app_bar_height" android:layout_width="match_parent" app:layout_constraintTop_toTopOf="parent"/> diff --git a/res/values-h1200dp/bools.xml b/res/values-h1200dp/bools.xml index 34eae64..b1476c8 100644 --- a/res/values-h1200dp/bools.xml +++ b/res/values-h1200dp/bools.xml @@ -15,6 +15,6 @@ limitations under the License. --> <resources> + <!-- On screens tall enough we enable content forward browsing --> <bool name="forward_content_browse_enabled">true</bool> - <bool name="force_browse_tabs">true</bool> </resources>
\ No newline at end of file diff --git a/res/values-h600dp/dimens.xml b/res/values-h1200dp/dimens.xml index 25639cf..6032220 100644 --- a/res/values-h600dp/dimens.xml +++ b/res/values-h1200dp/dimens.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2016 The Android Open Source Project +<!-- Copyright (C) 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. @@ -14,5 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. --> <resources> - <dimen name="now_playing_metadata_top_margin">32dp</dimen> + <!-- Application Bar --> + <dimen name="car_app_bar_height">116dp</dimen> </resources> diff --git a/res/values/bools.xml b/res/values/bools.xml index b9bd29e..3247ad9 100644 --- a/res/values/bools.xml +++ b/res/values/bools.xml @@ -17,8 +17,4 @@ <resources> <!-- Whether forward content browse is enabled --> <bool name="forward_content_browse_enabled">false</bool> - - <!-- Force the presentation of tabs even if the number of browsable items exceeds the - maximum number of allowed tabs (this is mainly for demo purposes) --> - <bool name="force_browse_tabs">false</bool> </resources>
\ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index f06b143..f4a7439 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -23,9 +23,8 @@ <!-- Music now playing screen --> <dimen name="music_action_icon_inset">0dp</dimen> <dimen name="music_action_ripple_inset">32dp</dimen> - <dimen name="now_playing_metadata_top_margin">0dp</dimen> - <dimen name="missing_permission_icon_size">208dp</dimen> + <dimen name="missing_permission_icon_size">128dp</dimen> <dimen name="controls_tap_target_width">64dp</dimen> <dimen name="controls_tap_target_height">64dp</dimen> diff --git a/res/values/integers.xml b/res/values/integers.xml index 8a70b8a..d0f23ea 100644 --- a/res/values/integers.xml +++ b/res/values/integers.xml @@ -34,5 +34,5 @@ <integer name="app_selector_fade_duration">500</integer> <!-- Time allowed for a process to complete before we show a progress indicator --> - <integer name="progress_indicator_delay">500</integer> + <integer name="progress_indicator_delay">1000</integer> </resources> diff --git a/src/com/android/car/media/BrowseFragment.java b/src/com/android/car/media/BrowseFragment.java index 1a6571d..ecb3f6c 100644 --- a/src/com/android/car/media/BrowseFragment.java +++ b/src/com/android/car/media/BrowseFragment.java @@ -90,14 +90,19 @@ public class BrowseFragment extends Fragment { mBrowseAdapter.update(); if (mBrowseAdapter.getItemCount() > 0) { ViewUtils.showViewAnimated(mBrowseList, mFadeDuration); + ViewUtils.hideViewAnimated(mErrorIcon, mFadeDuration); + ViewUtils.hideViewAnimated(mErrorMessage, mFadeDuration); } else { mErrorMessage.setText(R.string.nothing_to_play); + ViewUtils.hideViewAnimated(mBrowseList, mFadeDuration); + ViewUtils.hideViewAnimated(mErrorIcon, mFadeDuration); ViewUtils.showViewAnimated(mErrorMessage, mFadeDuration); } break; case ERROR: stopLoadingIndicator(); mErrorMessage.setText(R.string.unknown_error); + ViewUtils.hideViewAnimated(mBrowseList, mFadeDuration); ViewUtils.showViewAnimated(mErrorMessage, mFadeDuration); ViewUtils.showViewAnimated(mErrorIcon, mFadeDuration); break; @@ -167,6 +172,7 @@ public class BrowseFragment extends Fragment { /** * Creates a new instance of this fragment. * + * @param mediaSource media source being displayed * @param item media tree node to display on this fragment. * @return a fully initialized {@link BrowseFragment} */ @@ -244,9 +250,6 @@ public class BrowseFragment extends Fragment { if (mMediaSource != null) { mMediaSource.subscribe(mBrowseObserver); } - if (mBrowseAdapter != null) { - mBrowseAdapter.start(); - } } private Runnable mProgressIndicatorRunnable = new Runnable() { @@ -276,6 +279,7 @@ public class BrowseFragment extends Fragment { } if (mBrowseAdapter != null) { mBrowseAdapter.stop(); + mBrowseAdapter = null; } } @@ -299,8 +303,8 @@ public class BrowseFragment extends Fragment { ViewUtils.showViewAnimated(mErrorMessage, mFadeDuration); return; } - mBrowseAdapter = new BrowseAdapter(getContext(), mMediaSource.getMediaBrowser(), - getCurrentMediaItem(), ContentForwardStrategy.DEFAULT_STRATEGY); + mBrowseAdapter = new BrowseAdapter(getContext(), mMediaSource, getCurrentMediaItem(), + ContentForwardStrategy.DEFAULT_STRATEGY); mBrowseList.setAdapter(mBrowseAdapter); mBrowseList.setDividerVisibilityManager(mBrowseAdapter); mBrowseAdapter.registerObserver(mBrowseAdapterObserver); diff --git a/src/com/android/car/media/EmptyFragment.java b/src/com/android/car/media/EmptyFragment.java index bbdfe6d..72b6889 100644 --- a/src/com/android/car/media/EmptyFragment.java +++ b/src/com/android/car/media/EmptyFragment.java @@ -3,11 +3,15 @@ package com.android.car.media; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.ProgressBar; +import android.widget.TextView; +import com.android.car.media.common.MediaSource; import com.android.car.media.widgets.ViewUtils; /** @@ -15,16 +19,20 @@ import com.android.car.media.widgets.ViewUtils; */ public class EmptyFragment extends Fragment { private ProgressBar mProgressBar; + private ImageView mErrorIcon; + private TextView mErrorMessage; + private int mProgressBarDelay; private Handler mHandler = new Handler(); private int mFadeDuration; + private MediaActivity.BrowseState mState = MediaActivity.BrowseState.EMPTY; + private MediaSource mMediaSource; private Runnable mProgressIndicatorRunnable = new Runnable() { @Override public void run() { ViewUtils.showViewAnimated(mProgressBar, mFadeDuration); } }; - @Override public View onCreateView(LayoutInflater inflater, final ViewGroup container, Bundle savedInstanceState) { @@ -34,27 +42,61 @@ public class EmptyFragment extends Fragment { .getInteger(R.integer.progress_indicator_delay); mFadeDuration = getContext().getResources().getInteger( R.integer.new_album_art_fade_in_duration); + mErrorIcon = view.findViewById(R.id.error_icon); + mErrorMessage = view.findViewById(R.id.error_message); + update(); return view; } @Override - public void onStart() { - super.onStart(); - mProgressBar.setVisibility(View.GONE); + public void onPause() { + super.onPause(); + mHandler.removeCallbacks(mProgressIndicatorRunnable); } - @Override - public void onResume() { - super.onResume(); - // Display the indicator after a certain time, to avoid flashing the indicator constantly, - // even when performance is acceptable. - mHandler.postDelayed(mProgressIndicatorRunnable, mProgressBarDelay); + /** + * Updates the state of this fragment + * + * @param state browsing state to display + * @param mediaSource media source currently being browsed + */ + public void setState(MediaActivity.BrowseState state, MediaSource mediaSource) { + mHandler.removeCallbacks(mProgressIndicatorRunnable); + mMediaSource = mediaSource; + mState = state; + if (this.getView() != null) { + update(); + } } - @Override - public void onPause() { - super.onPause(); - mHandler.removeCallbacks(mProgressIndicatorRunnable); - mProgressBar.setVisibility(View.GONE); + private void update() { + switch (mState) { + case LOADING: + // Display the indicator after a certain time, to avoid flashing the indicator + // constantly, even when performance is acceptable. + mHandler.postDelayed(mProgressIndicatorRunnable, mProgressBarDelay); + mErrorIcon.setVisibility(View.GONE); + mErrorMessage.setVisibility(View.GONE); + break; + case ERROR: + mProgressBar.setVisibility(View.GONE); + mErrorIcon.setVisibility(View.VISIBLE); + mErrorMessage.setVisibility(View.VISIBLE); + mErrorMessage.setText(getContext().getString( + R.string.cannot_connect_to_app, + mMediaSource != null + ? mMediaSource.getName() + : getContext().getString(R.string.unknown_media_provider_name))); + break; + case EMPTY: + mProgressBar.setVisibility(View.GONE); + mErrorIcon.setVisibility(View.GONE); + mErrorMessage.setVisibility(View.VISIBLE); + mErrorMessage.setText(getContext().getString(R.string.nothing_to_play)); + break; + default: + // Fail fast on any other state. + throw new IllegalStateException("Invalid state for this fragment: " + mState); + } } } diff --git a/src/com/android/car/media/MediaActivity.java b/src/com/android/car/media/MediaActivity.java index 00f53fc..d7b809b 100644 --- a/src/com/android/car/media/MediaActivity.java +++ b/src/com/android/car/media/MediaActivity.java @@ -23,9 +23,9 @@ import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; import android.os.Bundle; -import android.support.design.widget.AppBarLayout; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; +import android.support.v4.widget.DrawerLayout; import android.transition.Fade; import android.util.Log; import android.util.TypedValue; @@ -56,7 +56,7 @@ import androidx.car.drawer.CarDrawerAdapter; * by broadcast. Drawer menu is controlled by {@link MediaDrawerController}. */ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.Callbacks, - AppSelectionFragment.Callbacks { + AppSelectionFragment.Callbacks, PlaybackFragment.Callbacks { private static final String TAG = "MediaActivity"; /** Intent extra specifying the package with the MediaBrowser */ @@ -84,11 +84,10 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C private CrossfadeImageView mAlbumBackground; private PlaybackFragment mPlaybackFragment; private AppSelectionFragment mAppSelectionFragment; - private AppBarLayout mDrawerBarLayout; private PlaybackControls mPlaybackControls; private MetadataView mMetadataView; private ViewGroup mBrowseControlsContainer; - private Fragment mEmptyFragment; + private EmptyFragment mEmptyFragment; private ViewGroup mBrowseContainer; private ViewGroup mPlaybackContainer; @@ -109,8 +108,6 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C MediaActivity.this.onBrowseConnected(false); } }; - private MediaSource.ItemsSubscription mRootItemsSubscription = - (parentId, items) -> updateTabs(items); private PlaybackModel.PlaybackObserver mPlaybackObserver = new PlaybackModel.PlaybackObserver() { @Override @@ -124,10 +121,23 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C updateMetadata(); } }; + private MediaSource.ItemsSubscription mItemsSubscription = + new MediaSource.ItemsSubscription() { + @Override + public void onChildrenLoaded(MediaSource mediaSource, String parentId, + List<MediaItemMetadata> items) { + if (mediaSource == mMediaSource) { + updateTabs(items); + } else { + Log.w(TAG, "Received items for a wrong source: " + + mediaSource.getPackageName()); + } + } + }; private AppBarView.AppBarListener mAppBarListener = new AppBarView.AppBarListener() { @Override public void onTabSelected(MediaItemMetadata item) { - updateBrowseFragment(item, false); + updateBrowseFragment(BrowseState.LOADED, item); switchToMode(Mode.BROWSING); } @@ -146,6 +156,7 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C @Override public void onAppSelection() { + Log.d(TAG, "onAppSelection clicked"); if (mIsAppSelectorOpen) { closeAppSelector(); } else { @@ -157,28 +168,68 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C mAppBarView.setAppSelection(!mMediaSourcesManager.getMediaSources().isEmpty()); mAppSelectionFragment.refresh(); }; + private DrawerLayout.DrawerListener mDrawerListener = new DrawerLayout.DrawerListener() { + @Override + public void onDrawerSlide(@android.support.annotation.NonNull View view, float v) { + } + + @Override + public void onDrawerOpened(@android.support.annotation.NonNull View view) { + closeAppSelector(); + } + + @Override + public void onDrawerClosed(@android.support.annotation.NonNull View view) { + } + + @Override + public void onDrawerStateChanged(int i) { + } + }; + /** + * Possible modes of the application UI + */ private enum Mode { + /** The user is browsing a media source */ BROWSING, + /** The user is interacting with the full screen playback UI */ PLAYBACK } + /** + * Possible states of the application UI + */ + public enum BrowseState { + /** There is no content to show */ + EMPTY, + /** We are still in the process of obtaining data */ + LOADING, + /** Data has been loaded */ + LOADED, + /** The content can't be shown due an error */ + ERROR + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setMainContent(R.layout.media_activity); - setToolbarElevation(0f); + setToolbarClickThrough(true); mContentForwardBrowseEnabled = getResources() .getBoolean(R.bool.forward_content_browse_enabled); mDrawerController = new MediaDrawerController(this, getDrawerController()); getDrawerController().setRootAdapter(getRootAdapter()); + getDrawerController().addDrawerListener(mDrawerListener); + if (mContentForwardBrowseEnabled) { + getSupportActionBar().hide(); + } mAppBarView = findViewById(R.id.app_bar); mAppBarView.setListener(mAppBarListener); - boolean forceBrowseTabs = getResources().getBoolean(R.bool.force_browse_tabs); - mAppBarView.setVisibility(forceBrowseTabs ? View.VISIBLE : View.GONE); + mAppBarView.setContentForwardEnabled(mContentForwardBrowseEnabled); mPlaybackFragment = new PlaybackFragment(); mAppSelectionFragment = new AppSelectionFragment(); int fadeDuration = getResources().getInteger(R.integer.app_selector_fade_duration); @@ -186,8 +237,6 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C mAppSelectionFragment.setExitTransition(new Fade().setDuration(fadeDuration)); mPlaybackModel = new PlaybackModel(this); mMediaSourcesManager = new MediaSourcesManager(this); - mDrawerBarLayout = findViewById(androidx.car.R.id.appbar); - mDrawerBarLayout.setVisibility(forceBrowseTabs ? View.GONE : View.VISIBLE); mAlbumBackground = findViewById(R.id.media_background); mPlaybackControls = findViewById(R.id.browse_controls); mPlaybackControls.setModel(mPlaybackModel); @@ -216,6 +265,7 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C super.onResume(); mPlaybackModel.registerObserver(mPlaybackObserver); mMediaSourcesManager.registerObserver(mMediaSourcesManagerObserver); + handleIntent(); } @Override @@ -255,19 +305,15 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C super.onBackPressed(); } - @Override - protected void onResumeFragments() { - super.onResumeFragments(); - handleIntent(); - } - private void onBrowseConnected(boolean success) { if (!success) { - updateTabs(new ArrayList<>()); - mMediaSource.unsubscribeChildren(null); + updateTabs(null); + mMediaSource.unsubscribeChildren(null, mItemsSubscription); + mMediaSource.unsubscribe(mMediaSourceObserver); + updateBrowseFragment(BrowseState.ERROR, null); return; } - mMediaSource.subscribeChildren(null, mRootItemsSubscription); + mMediaSource.subscribeChildren(null, mItemsSubscription); } private void handleIntent() { @@ -303,7 +349,6 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C // and display what we have. if (mMediaSource != null) { closeAppSelector(); - updateMetadata(); return; } @@ -312,7 +357,7 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C if (lastMediaSource != null) { closeAppSelector(); updateBrowseSource(lastMediaSource); - updateMetadata(); + switchToMode(Mode.BROWSING); } else { // If we don't have anything from before: open the app selector. openAppSelector(); @@ -329,13 +374,12 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C return; } if (mMediaSource != null) { - mMediaSource.unsubscribeChildren(null); + mMediaSource.unsubscribeChildren(null, mItemsSubscription); mMediaSource.unsubscribe(mMediaSourceObserver); updateTabs(new ArrayList<>()); } mMediaSource = mediaSource; setLastMediaSource(mMediaSource); - mAppBarView.setState(AppBarView.State.BROWSING); if (mMediaSource != null) { if (Log.isLoggable(TAG, Log.INFO)) { Log.i(TAG, "Browsing: " + mediaSource.getName()); @@ -344,10 +388,10 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C MediaManager.getInstance(this).setMediaClientComponent(component); // If content forward browsing is disabled, then no need to subscribe to this media // source. - updateBrowseFragment(null, true); if (mContentForwardBrowseEnabled) { Log.i(TAG, "Content forward is enabled: subscribing to " + mMediaSource.getPackageName()); + updateBrowseFragment(BrowseState.LOADING, null); mMediaSource.subscribe(mMediaSourceObserver); } mAppBarView.setAppIcon(mMediaSource.getRoundPackageIcon()); @@ -358,21 +402,26 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C } } - /** - * @return the package name of the media source requested by the incoming {@link Intent} or - * null if no source was indicated. - */ - private String getRequestedMediaPackageName() { - return getIntent() != null - ? getIntent().getStringExtra(KEY_MEDIA_PACKAGE) - : null; - } - private boolean isCurrentMediaSourcePlaying() { return Objects.equals(mMediaSource, mPlaybackModel.getMediaSource()); } + /** + * Updates the tabs displayed on the app bar, based on the top level items on the browse tree. + * If there is at least one browsable item, we show the browse content of that node. + * If there are only playable items, then we show those items. + * If there are not items at all, we show the empty message. + * If we receive null, we show the error message. + * + * @param items top level items, or null if there was an error trying load those items. + */ private void updateTabs(List<MediaItemMetadata> items) { + if (items == null || items.isEmpty()) { + mAppBarView.setItems(null); + updateBrowseFragment(items == null ? BrowseState.ERROR : BrowseState.EMPTY, null); + return; + } + items = customizeTabs(mMediaSource, items); List<MediaItemMetadata> browsableTopLevel = items.stream() .filter(item -> item.isBrowsable()) @@ -381,11 +430,11 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C if (!browsableTopLevel.isEmpty()) { // If we have at least a few browsable items, we show the tabs mAppBarView.setItems(browsableTopLevel); - updateBrowseFragment(browsableTopLevel.get(0), false); + updateBrowseFragment(BrowseState.LOADED, browsableTopLevel.get(0)); } else { // Otherwise, we show the top of the tree with no fabs mAppBarView.setItems(null); - updateBrowseFragment(null, false); + updateBrowseFragment(BrowseState.LOADED, null); } } @@ -404,9 +453,11 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C } private void switchToMode(Mode mode) { - mMode = mode; + // If content forward is not enable, then we always show the playback UI (browse will be + // done in the drawer) + mMode = mContentForwardBrowseEnabled ? mode : Mode.PLAYBACK; updateMetadata(); - switch (mode) { + switch (mMode) { case PLAYBACK: ViewUtils.showViewAnimated(mPlaybackContainer, mFadeDuration); ViewUtils.hideViewAnimated(mBrowseContainer, mFadeDuration); @@ -424,23 +475,32 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C * Updates the browse area with either a loading state, the root node content, or the * content of a particular media item. * - * @param topItem item to display, or null to display the root node. - * @param isLoading whether we should show the loading state. + * @param state state in the process of loading browse information. + * @param topItem if state == IDLE, this will contain the item to display, + * or null to display the root node. */ - private void updateBrowseFragment(MediaItemMetadata topItem, boolean isLoading) { - if (isLoading) { - mCurrentFragment = mEmptyFragment; - mAppBarView.setActiveItem(null); - } else if (topItem != null) { - mCurrentFragment = BrowseFragment.newInstance(mMediaSource, topItem); - mAppBarView.setActiveItem(topItem); - } else { - mCurrentFragment = BrowseFragment.newInstance(mMediaSource, null); - mAppBarView.setActiveItem(null); + private void updateBrowseFragment(BrowseState state, MediaItemMetadata topItem) { + switch(state) { + case LOADED: + if (topItem != null) { + mCurrentFragment = BrowseFragment.newInstance(mMediaSource, topItem); + mAppBarView.setActiveItem(topItem); + } else { + mCurrentFragment = BrowseFragment.newInstance(mMediaSource, null); + mAppBarView.setActiveItem(null); + } + break; + case EMPTY: + case LOADING: + case ERROR: + mCurrentFragment = mEmptyFragment; + mEmptyFragment.setState(state, mMediaSource); + mAppBarView.setActiveItem(null); + break; } getSupportFragmentManager().beginTransaction() .replace(R.id.fragment_container, mCurrentFragment) - .commit(); + .commitAllowingStateLoss(); } private void updateMetadata() { @@ -536,7 +596,8 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C private void closeAppSelector() { mIsAppSelectorOpen = false; FragmentManager manager = getSupportFragmentManager(); - mAppBarView.setState(AppBarView.State.BROWSING); + mAppBarView.setState(mMode == Mode.PLAYBACK ? AppBarView.State.PLAYING + : AppBarView.State.BROWSING); manager.beginTransaction() .remove(mAppSelectionFragment) .commit(); @@ -554,6 +615,7 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C public void onMediaSourceSelected(MediaSource mediaSource) { closeAppSelector(); if (mediaSource.getMediaBrowser() != null && !mediaSource.isCustom()) { + mCurrentMetadata = null; updateBrowseSource(mediaSource); switchToMode(Mode.BROWSING); } else { @@ -582,4 +644,14 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C .putString(LAST_MEDIA_SOURCE_SHARED_PREF_KEY, mediaSource.getPackageName()) .apply(); } + + + @Override + public void onQueueButtonClicked() { + if (mContentForwardBrowseEnabled) { + mPlaybackFragment.toggleQueueVisibility(); + } else { + mDrawerController.showPlayQueue(); + } + } } diff --git a/src/com/android/car/media/MetadataController.java b/src/com/android/car/media/MetadataController.java index 5f74891..e7261f5 100644 --- a/src/com/android/car/media/MetadataController.java +++ b/src/com/android/car/media/MetadataController.java @@ -14,7 +14,6 @@ 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. @@ -116,9 +115,13 @@ public class MetadataController { MediaItemMetadata metadata = mModel != null ? mModel.getMetadata() : null; mTitle.setText(metadata != null ? metadata.getTitle() : null); mSubtitle.setText(metadata != null ? metadata.getSubtitle() : null); - if (mAlbumArt != null && metadata != null) { + if (mAlbumArt != null && metadata != null && (metadata.getAlbumArtUri() != null + || metadata.getAlbumArtBitmap() != null)) { + mAlbumArt.setVisibility(View.VISIBLE); metadata.getAlbumArt(mAlbumArt.getContext(), mAlbumArtSize, mAlbumArtSize, true) .thenAccept(mAlbumArt::setImageBitmap); + } else if (mAlbumArt != null) { + mAlbumArt.setVisibility(View.GONE); } } diff --git a/src/com/android/car/media/PlaybackFragment.java b/src/com/android/car/media/PlaybackFragment.java index da42832..d88a348 100644 --- a/src/com/android/car/media/PlaybackFragment.java +++ b/src/com/android/car/media/PlaybackFragment.java @@ -61,6 +61,7 @@ public class PlaybackFragment extends Fragment { private PlaybackControls mPlaybackControls; private QueueItemsAdapter mQueueAdapter; private PagedListView mQueue; + private Callbacks mCallbacks; private MetadataController mMetadataController; private ConstraintLayout mRootView; @@ -123,12 +124,22 @@ public class PlaybackFragment extends Fragment { } } + /** + * Callbacks this fragment can trigger + */ + public interface Callbacks { + /** + * Indicates that the "show queue" button has been clicked + */ + void onQueueButtonClicked(); + } + private PlaybackControls.Listener mPlaybackControlsListener = new PlaybackControls.Listener() { @Override public void onToggleQueue() { - mQueueIsVisible = !mQueueIsVisible; - mPlaybackControls.setQueueVisible(mQueueIsVisible); - setQueueVisible(mQueueIsVisible); + if (mCallbacks != null) { + mCallbacks.onQueueButtonClicked(); + } } }; @@ -146,6 +157,18 @@ public class PlaybackFragment extends Fragment { return view; } + @Override + public void onAttach(Context context) { + super.onAttach(context); + mCallbacks = (Callbacks) context; + } + + @Override + public void onDetach() { + super.onDetach(); + mCallbacks = null; + } + private void initPlaybackControls(PlaybackControls playbackControls) { mPlaybackControls = playbackControls; mPlaybackControls.setModel(mModel); @@ -193,9 +216,15 @@ public class PlaybackFragment extends Fragment { mModel.unregisterObserver(mPlaybackObserver); } - public void setQueueVisible(boolean visible) { + /** + * Hides or shows the playback queue + */ + public void toggleQueueVisibility() { + mQueueIsVisible = !mQueueIsVisible; + mPlaybackControls.setQueueVisible(mQueueIsVisible); + Transition transition = TransitionInflater.from(getContext()).inflateTransition( - visible ? R.transition.queue_in : R.transition.queue_out); + mQueueIsVisible ? R.transition.queue_in : R.transition.queue_out); transition.addListener(new TransitionListenerAdapter() { @Override @@ -218,9 +247,8 @@ public class PlaybackFragment extends Fragment { TransitionManager.beginDelayedTransition(mRootView, transition); ConstraintSet constraintSet = new ConstraintSet(); constraintSet.clone(mRootView.getContext(), - visible ? R.layout.fragment_playback_with_queue : R.layout.fragment_playback); + mQueueIsVisible ? R.layout.fragment_playback_with_queue : R.layout.fragment_playback); constraintSet.applyTo(mRootView); - } private void updateState() { diff --git a/src/com/android/car/media/browse/BrowseAdapter.java b/src/com/android/car/media/browse/BrowseAdapter.java index 5d2bff0..dd5d2ee 100644 --- a/src/com/android/car/media/browse/BrowseAdapter.java +++ b/src/com/android/car/media/browse/BrowseAdapter.java @@ -30,6 +30,7 @@ import android.view.View; import android.view.ViewGroup; import com.android.car.media.common.MediaItemMetadata; +import com.android.car.media.common.MediaSource; import java.util.ArrayList; import java.util.Collection; @@ -69,7 +70,7 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem private static final String TAG = "BrowseAdapter"; @NonNull private final Context mContext; - private final MediaBrowser mMediaBrowser; + private final MediaSource mMediaSource; private final ContentForwardStrategy mCFBStrategy; private MediaItemMetadata mParentMediaItem; private LinkedHashMap<String, MediaItemState> mItemStates = new LinkedHashMap<>(); @@ -131,29 +132,14 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem protected void onQueueItemClicked(MediaItemMetadata item) {}; } - private MediaBrowser.SubscriptionCallback mSubscriptionCallback = - new MediaBrowser.SubscriptionCallback() { - @Override - public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children) { - onItemsLoaded(parentId, children); - } - - @Override - public void onChildrenLoaded(String parentId, List<MediaBrowser.MediaItem> children, - Bundle options) { - onItemsLoaded(parentId, children); - } - - @Override - public void onError(String parentId) { - onLoadingError(parentId); - } - - @Override - public void onError(String parentId, Bundle options) { - onLoadingError(parentId); - } - }; + private MediaSource.ItemsSubscription mSubscriptionCallback = + (mediaSource, parentId, items) -> { + if (items != null) { + onItemsLoaded(parentId, items); + } else { + onLoadingError(parentId); + } + }; /** @@ -174,19 +160,19 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem /** Whether we are subscribed to updates for this item or not */ boolean mIsSubscribed; - MediaItemState(MediaBrowser.MediaItem item) { - mItem = new MediaItemMetadata(item); + MediaItemState(MediaItemMetadata item) { + mItem = item; } - void setChildren(List<MediaBrowser.MediaItem> children) { + void setChildren(List<MediaItemMetadata> children) { mPlayableChildren.clear(); mBrowsableChildren.clear(); - for (MediaBrowser.MediaItem child : children) { + for (MediaItemMetadata child : children) { if (child.isBrowsable()) { // Browsable items could also be playable - mBrowsableChildren.add(new MediaItemMetadata(child)); + mBrowsableChildren.add(child); } else if (child.isPlayable()) { - mPlayableChildren.add(new MediaItemMetadata(child)); + mPlayableChildren.add(child); } } } @@ -195,15 +181,15 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem /** * Creates a {@link BrowseAdapter} that displays the children of the given media tree node. * - * @param mediaBrowser the {@link MediaBrowser} to get data from. + * @param mediaSource the {@link MediaSource} to get data from. * @param parentItem the node to display children of, or NULL if the * @param strategy a {@link ContentForwardStrategy} that would determine which items would be * expanded and how. */ - public BrowseAdapter(Context context, @NonNull MediaBrowser mediaBrowser, + public BrowseAdapter(Context context, @NonNull MediaSource mediaSource, @Nullable MediaItemMetadata parentItem, @NonNull ContentForwardStrategy strategy) { mContext = context; - mMediaBrowser = mediaBrowser; + mMediaSource = mediaSource; mParentMediaItem = parentItem; mCFBStrategy = strategy; } @@ -213,10 +199,9 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem * {@link #registerObserver(Observer)} to receive updates on the progress. */ public void start() { - mParentMediaItemId = mParentMediaItem != null - ? mParentMediaItem.getId() - : mMediaBrowser.getRoot(); - mMediaBrowser.subscribe(mParentMediaItemId, mSubscriptionCallback); + mParentMediaItemId = mParentMediaItem != null ? mParentMediaItem.getId() : + mMediaSource.getRoot(); + mMediaSource.subscribeChildren(mParentMediaItemId, mSubscriptionCallback); for (MediaItemState itemState : mItemStates.values()) { subscribe(itemState); } @@ -230,7 +215,7 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem // Not started return; } - mMediaBrowser.unsubscribe(mParentMediaItemId, mSubscriptionCallback); + mMediaSource.unsubscribeChildren(mParentMediaItemId, mSubscriptionCallback); for (MediaItemState itemState : mItemStates.values()) { unsubscribe(itemState); } @@ -244,16 +229,15 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem * @param parentItem new media item to expand. */ public void setParentMediaItemId(@Nullable MediaItemMetadata parentItem) { - String newParentMediaItemId = parentItem != null - ? parentItem.getId() - : mMediaBrowser.getRoot(); + String newParentMediaItemId = parentItem != null ? parentItem.getId() : + mMediaSource.getRoot(); if (Objects.equals(newParentMediaItemId, mParentMediaItemId)) { return; } stop(); mParentMediaItem = parentItem; mParentMediaItemId = newParentMediaItemId; - mMediaBrowser.subscribe(mParentMediaItemId, mSubscriptionCallback); + mMediaSource.subscribeChildren(mParentMediaItemId, mSubscriptionCallback); } /** @@ -332,7 +316,7 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem private void subscribe(MediaItemState state) { if (!state.mIsSubscribed && state.mItem.isBrowsable()) { - mMediaBrowser.subscribe(state.mItem.getId(), mSubscriptionCallback); + mMediaSource.subscribeChildren(state.mItem.getId(), mSubscriptionCallback); state.mIsSubscribed = true; } else { state.mState = State.LOADED; @@ -341,7 +325,7 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem private void unsubscribe(MediaItemState state) { if (state.mIsSubscribed) { - mMediaBrowser.unsubscribe(state.mItem.getId(), mSubscriptionCallback); + mMediaSource.unsubscribeChildren(state.mItem.getId(), mSubscriptionCallback); state.mIsSubscribed = false; } } @@ -370,21 +354,22 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem return mViewData.get(position).mViewType.ordinal(); } - private void onItemsLoaded(String parentId, List<MediaBrowser.MediaItem> children) { + private void onItemsLoaded(String parentId, List<MediaItemMetadata> children) { if (parentId.equals(mParentMediaItemId)) { // Direct children from the requested media item id. Update subscription list. LinkedHashMap<String, MediaItemState> newItemStates = new LinkedHashMap<>(); - for (MediaBrowser.MediaItem item : children) { - MediaItemState itemState = mItemStates.get(item.getMediaId()); + List<MediaItemState> itemsToSubscribe = new ArrayList<>(); + for (MediaItemMetadata item : children) { + MediaItemState itemState = mItemStates.get(item.getId()); if (itemState != null) { // Reuse existing section. - newItemStates.put(item.getMediaId(), itemState); - mItemStates.remove(item.getMediaId()); + newItemStates.put(item.getId(), itemState); + mItemStates.remove(item.getId()); } else { // New section, subscribe to it. itemState = new MediaItemState(item); - newItemStates.put(item.getMediaId(), itemState); - subscribe(itemState); + newItemStates.put(item.getId(), itemState); + itemsToSubscribe.add(itemState); } } // Remove unused sections @@ -392,6 +377,11 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> implem unsubscribe(itemState); } mItemStates = newItemStates; + // Subscribe items once we have updated the map (updates might happen synchronously + // if data is already available). + for (MediaItemState itemState : itemsToSubscribe) { + subscribe(itemState); + } } else { MediaItemState itemState = mItemStates.get(parentId); if (itemState == null) { diff --git a/src/com/android/car/media/drawer/MediaItemsFetcher.java b/src/com/android/car/media/drawer/MediaItemsFetcher.java index 3cef566..36c01c0 100644 --- a/src/com/android/car/media/drawer/MediaItemsFetcher.java +++ b/src/com/android/car/media/drawer/MediaItemsFetcher.java @@ -19,6 +19,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.media.MediaDescription; import android.text.TextUtils; +import android.view.View; import com.android.car.apps.common.BitmapDownloader; import com.android.car.apps.common.BitmapWorkerOptions; @@ -114,22 +115,27 @@ interface MediaItemsFetcher { } Bitmap iconBitmap = description.getIconBitmap(); holder.getIcon().setImageBitmap(iconBitmap); // Ok to set null here for clearing. - if (iconBitmap == null && description.getIconUri() != null) { - int bitmapSize = - context.getResources().getDimensionPixelSize(R.dimen.car_primary_icon_size); - // We don't want to cache android resources as they are needed to be refreshed after - // configuration changes. - int cacheFlag = UriUtils.isAndroidResourceUri(description.getIconUri()) - ? (BitmapWorkerOptions.CACHE_FLAG_DISK_DISABLED - | BitmapWorkerOptions.CACHE_FLAG_MEM_DISABLED) - : 0; - BitmapWorkerOptions options = new BitmapWorkerOptions.Builder(context) - .resource(description.getIconUri()) - .height(bitmapSize) - .width(bitmapSize) - .cacheFlag(cacheFlag) - .build(); - BitmapDownloader.getInstance(context).loadBitmap(options, holder.getIcon()); + if (iconBitmap == null) { + if (description.getIconUri() != null) { + holder.getIcon().setVisibility(View.VISIBLE); + int bitmapSize = + context.getResources().getDimensionPixelSize(R.dimen.car_primary_icon_size); + // We don't want to cache android resources as they are needed to be refreshed after + // configuration changes. + int cacheFlag = UriUtils.isAndroidResourceUri(description.getIconUri()) + ? (BitmapWorkerOptions.CACHE_FLAG_DISK_DISABLED + | BitmapWorkerOptions.CACHE_FLAG_MEM_DISABLED) + : 0; + BitmapWorkerOptions options = new BitmapWorkerOptions.Builder(context) + .resource(description.getIconUri()) + .height(bitmapSize) + .width(bitmapSize) + .cacheFlag(cacheFlag) + .build(); + BitmapDownloader.getInstance(context).loadBitmap(options, holder.getIcon()); + } else { + holder.getIcon().setVisibility(View.GONE); + } } } } diff --git a/src/com/android/car/media/widgets/AppBarView.java b/src/com/android/car/media/widgets/AppBarView.java index bad0641..741e07f 100644 --- a/src/com/android/car/media/widgets/AppBarView.java +++ b/src/com/android/car/media/widgets/AppBarView.java @@ -56,6 +56,7 @@ public class AppBarView extends RelativeLayout { private MediaItemMetadata mSelectedItem; private String mMediaAppTitle; private Drawable mDefaultIcon; + private boolean mContentForwardEnabled; /** * Application bar listener @@ -238,6 +239,13 @@ public class AppBarView extends RelativeLayout { } /** + * Whether content forward browsing is enabled or not + */ + public void setContentForwardEnabled(boolean enabled) { + mContentForwardEnabled = enabled; + } + + /** * Updates the application icon to show next to the application switcher. */ public void setAppIcon(Bitmap icon) { @@ -304,15 +312,18 @@ public class AppBarView extends RelativeLayout { break; case PLAYING: mNavIcon.setImageDrawable(mCollapse); - mNavIconContainer.setVisibility(hasItems ? View.GONE : View.VISIBLE); - mTabsContainer.setVisibility(hasItems ? View.VISIBLE : View.GONE); - mTitle.setVisibility(hasItems ? View.GONE : View.VISIBLE); + mNavIconContainer.setVisibility(hasItems || !mContentForwardEnabled ? View.GONE + : View.VISIBLE); + mTabsContainer.setVisibility(hasItems && mContentForwardEnabled ? View.VISIBLE + : View.GONE); + mTitle.setVisibility(hasItems || !mContentForwardEnabled ? View.GONE + : View.VISIBLE); mAppSwitchIcon.setImageDrawable(mArrowDropDown); break; case APP_SELECTION: mNavIconContainer.setVisibility(View.GONE); mTabsContainer.setVisibility(View.GONE); - mTitle.setVisibility(View.VISIBLE); + mTitle.setVisibility(mContentForwardEnabled ? View.VISIBLE : View.GONE); mAppSwitchIcon.setImageDrawable(mArrowDropUp); break; } diff --git a/src/com/android/car/media/widgets/ViewUtils.java b/src/com/android/car/media/widgets/ViewUtils.java index 62e2d59..6f7ad9a 100644 --- a/src/com/android/car/media/widgets/ViewUtils.java +++ b/src/com/android/car/media/widgets/ViewUtils.java @@ -19,6 +19,11 @@ public class ViewUtils { if (view.getVisibility() == View.GONE) { return; } + if (!view.isLaidOut()) { + // If the view hasn't been displayed yet, just adjust visibility without animation + view.setVisibility(View.GONE); + return; + } view.animate() .alpha(0f) .setDuration(duration) @@ -40,11 +45,20 @@ public class ViewUtils { if (view.getVisibility() == View.VISIBLE) { return; } - view.setAlpha(0f); - view.setVisibility(View.VISIBLE); + if (!view.isLaidOut()) { + // If the view hasn't been displayed yet, just adjust visibility without animation + view.setVisibility(View.VISIBLE); + return; + } view.animate() .alpha(1f) .setDuration(duration) - .setListener(null); + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + view.setAlpha(0f); + view.setVisibility(View.VISIBLE); + } + }); } } |