summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Perez <robertoalexis@google.com>2018-05-04 18:54:44 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2018-05-04 18:54:44 +0000
commit214fcde1072c0aa31730f7960105d31cc403cfb8 (patch)
tree6fe5e3baa2a603d810abfddb66d2955bf09754ca
parentda82c06b85e1ee2b608c679b4cef4c353f481ecf (diff)
parent450c73a79e566f1c4a8758648525b457005e9f9a (diff)
downloadMedia-214fcde1072c0aa31730f7960105d31cc403cfb8.tar.gz
Merge "DO NOT MERGE Remember last browsed application and improving transitions." into pi-dev
-rw-r--r--AndroidManifest.xml2
-rw-r--r--res/layout/appbar_view.xml1
-rw-r--r--res/layout/fragment_browse.xml33
-rw-r--r--res/layout/fragment_metadata.xml25
-rw-r--r--res/layout/fragment_metadata_with_queue.xml3
-rw-r--r--res/layout/initial_no_content.xml59
-rw-r--r--res/layout/media_browse_grid_item.xml2
-rw-r--r--res/layout/metadata_normal.xml1
-rw-r--r--res/values/dimens.xml2
-rw-r--r--res/values/integers.xml3
-rw-r--r--res/values/strings.xml4
-rw-r--r--src/com/android/car/media/BrowseFragment.java95
-rw-r--r--src/com/android/car/media/MediaActivity.java158
-rw-r--r--src/com/android/car/media/PlaybackFragment.java27
-rw-r--r--src/com/android/car/media/browse/BrowseAdapter.java44
-rw-r--r--src/com/android/car/media/browse/BrowseViewData.java18
-rw-r--r--src/com/android/car/media/browse/ContentForwardStrategy.java2
-rw-r--r--src/com/android/car/media/widgets/AppBarView.java16
-rw-r--r--src/com/android/car/media/widgets/MediaItemTabView.java1
19 files changed, 309 insertions, 187 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 08b2d596..47da7be6 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="com.android.car.media" >
<uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/>
+ <uses-permission android:name="android.permission.INTERNET" />
+ <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-sdk
android:minSdkVersion="24"
diff --git a/res/layout/appbar_view.xml b/res/layout/appbar_view.xml
index 31e965e7..7e15e8ca 100644
--- a/res/layout/appbar_view.xml
+++ b/res/layout/appbar_view.xml
@@ -30,7 +30,6 @@
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal"
- android:animateLayoutChanges="true"
android:gravity="center"
android:layout_alignParentLeft="true"
android:layout_centerInParent="true"/>
diff --git a/res/layout/fragment_browse.xml b/res/layout/fragment_browse.xml
index 508292ed..42c28dc6 100644
--- a/res/layout/fragment_browse.xml
+++ b/res/layout/fragment_browse.xml
@@ -14,19 +14,48 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<FrameLayout
+<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
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" />
+
<androidx.car.widget.PagedListView
android:id="@+id/browse_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
+ android:visibility="gone"
app:dayNightStyle="force_night"
app:showPagedListViewDivider="false"/>
-</FrameLayout> \ No newline at end of file
+</RelativeLayout> \ No newline at end of file
diff --git a/res/layout/fragment_metadata.xml b/res/layout/fragment_metadata.xml
index e43ceb6d..9775c045 100644
--- a/res/layout/fragment_metadata.xml
+++ b/res/layout/fragment_metadata.xml
@@ -36,26 +36,29 @@
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"/>
+ <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:visibility="invisible"
+ 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_toTopOf="@+id/playback_controls"
+ app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/margin_top"
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
index d2d4eb33..a87d12f3 100644
--- a/res/layout/fragment_metadata_with_queue.xml
+++ b/res/layout/fragment_metadata_with_queue.xml
@@ -50,10 +50,11 @@
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="@dimen/car_padding_4"
+ android:layout_marginBottom="@dimen/playback_controls_margin"
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_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_art"
app:listDividerColor="@color/car_list_divider_inverse"/>
diff --git a/res/layout/initial_no_content.xml b/res/layout/initial_no_content.xml
deleted file mode 100644
index bc7a199d..00000000
--- a/res/layout/initial_no_content.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright 2016, 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.
--->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:animateLayoutChanges="true" >
-
- <RelativeLayout
- android:id="@+id/initial_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center_horizontal">
-
- <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/tap_to_select_item"
- style="@style/TextAppearance.NoContent"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentBottom="true"
- android:layout_marginBottom="48dp"
- android:gravity="center"
- android:maxLines="3"
- android:text="@string/nothing_to_play"
- android:visibility="gone" />
- </RelativeLayout>
-</FrameLayout>
diff --git a/res/layout/media_browse_grid_item.xml b/res/layout/media_browse_grid_item.xml
index c6112d6c..3cc10d78 100644
--- a/res/layout/media_browse_grid_item.xml
+++ b/res/layout/media_browse_grid_item.xml
@@ -18,6 +18,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
+ android:focusable="true"
+ android:clickable="true"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
diff --git a/res/layout/metadata_normal.xml b/res/layout/metadata_normal.xml
index a98e3e08..f280429a 100644
--- a/res/layout/metadata_normal.xml
+++ b/res/layout/metadata_normal.xml
@@ -66,7 +66,6 @@
android:layout_width="match_parent"
android:layout_height="@dimen/playback_seekbar_height"
android:layout_marginTop="@dimen/car_padding_2"
- android:background="@color/car_grey_900"
android:paddingEnd="0dp"
android:paddingStart="0dp"
android:progressDrawable="@drawable/seekbar_background"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index cf3e68af..67bf4fb8 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -47,6 +47,8 @@
<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>
+ <!-- Bottom marging for the playback queue -->
+ <dimen name="playback_controls_margin">192dp</dimen>
<!-- Tab height -->
<dimen name="browse_tab_height">76dp</dimen>
diff --git a/res/values/integers.xml b/res/values/integers.xml
index eac1eef4..8a70b8ae 100644
--- a/res/values/integers.xml
+++ b/res/values/integers.xml
@@ -32,4 +32,7 @@
<!-- The amount of time it takes for the app selector to fade in and out -->
<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>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 84d60fd1..bdc7e923 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -17,7 +17,7 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<!-- Music -->
<!-- Prompt text to display when the user hasn't picked any songs to play yet. [CHAR LIMIT=57]-->
- <string name="nothing_to_play">To play something, open the menu at the top left.</string>
+ <string name="nothing_to_play">This folder is empty.</string>
<!-- Prompt text to display when we failed to connect to a media app. [CHAR LIMIT=50]-->
<string name="cannot_connect_to_app"><xliff:g name="app">%s</xliff:g> doesn\'t seem to be working right now.</string>
<!-- Prompt text to display when connecting to a media app. [CHAR LIMIT=50] -->
@@ -28,4 +28,6 @@
<string name="unknown_error">Something went wrong</string>
<!-- Text of the button displayed at the bottom of a media browse section to provide access to additional items. [CHAR LIMIT=100] -->
<string name="media_browse_more">More …</string>
+ <!-- Media template title -->
+ <string name="media_app_title">Media</string>
</resources>
diff --git a/src/com/android/car/media/BrowseFragment.java b/src/com/android/car/media/BrowseFragment.java
index 3c56c0e7..7c6b8b4f 100644
--- a/src/com/android/car/media/BrowseFragment.java
+++ b/src/com/android/car/media/BrowseFragment.java
@@ -16,16 +16,23 @@
package com.android.car.media;
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Bundle;
+import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
+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.browse.BrowseAdapter;
import com.android.car.media.browse.ContentForwardStrategy;
@@ -49,11 +56,17 @@ public class BrowseFragment extends Fragment {
private static final String BROWSE_STACK_KEY = "browse_stack";
private PagedListView mBrowseList;
+ private ProgressBar mProgressBar;
+ private ImageView mErrorIcon;
+ private TextView mErrorMessage;
private MediaSource mMediaSource;
private BrowseAdapter mBrowseAdapter;
private String mMediaSourcePackageName;
private MediaItemMetadata mTopMediaItem;
private Callbacks mCallbacks;
+ private int mFadeDuration;
+ private int mProgressBarDelay;
+ private Handler mHandler = new Handler();
private Stack<MediaItemMetadata> mBrowseStack = new Stack<>();
private MediaSource.Observer mBrowseObserver = new MediaSource.Observer() {
@Override
@@ -69,12 +82,27 @@ public class BrowseFragment extends Fragment {
private BrowseAdapter.Observer mBrowseAdapterObserver = new BrowseAdapter.Observer() {
@Override
protected void onDirty() {
- mBrowseAdapter.update();
- if (mBrowseAdapter.getItemCount() > 0) {
- mBrowseList.setVisibility(View.VISIBLE);
- } else {
- mBrowseList.setVisibility(View.GONE);
- // TODO(b/77647430) implement intermediate states.
+ switch (mBrowseAdapter.getState()) {
+ case LOADING:
+ case IDLE:
+ // Still loading... nothing to do.
+ break;
+ case LOADED:
+ stopLoadingIndicator();
+ mBrowseAdapter.update();
+ if (mBrowseAdapter.getItemCount() > 0) {
+ showViewAnimated(mBrowseList);
+ } else {
+ mErrorMessage.setText(R.string.nothing_to_play);
+ showViewAnimated(mErrorMessage);
+ }
+ break;
+ case ERROR:
+ stopLoadingIndicator();
+ mErrorMessage.setText(R.string.unknown_error);
+ showViewAnimated(mErrorMessage);
+ showViewAnimated(mErrorIcon);
+ break;
}
}
@@ -175,7 +203,14 @@ public class BrowseFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_browse, container, false);
+ mProgressBar = view.findViewById(R.id.loading_spinner);
+ mProgressBarDelay = getContext().getResources()
+ .getInteger(R.integer.progress_indicator_delay);
mBrowseList = view.findViewById(R.id.browse_list);
+ mErrorIcon = view.findViewById(R.id.error_icon);
+ mErrorMessage = view.findViewById(R.id.error_message);
+ mFadeDuration = getContext().getResources().getInteger(
+ R.integer.new_album_art_fade_in_duration);
int numColumns = getContext().getResources().getInteger(R.integer.num_browse_columns);
GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), numColumns);
RecyclerView recyclerView = mBrowseList.getRecyclerView();
@@ -206,6 +241,7 @@ public class BrowseFragment extends Fragment {
@Override
public void onStart() {
super.onStart();
+ startLoadingIndicator();
mMediaSource = mCallbacks.getMediaSource(mMediaSourcePackageName);
if (mMediaSource != null) {
mMediaSource.subscribe(mBrowseObserver);
@@ -215,9 +251,28 @@ public class BrowseFragment extends Fragment {
}
}
+ private Runnable mProgressIndicatorRunnable = new Runnable() {
+ @Override
+ public void run() {
+ showViewAnimated(mProgressBar);
+ }
+ };
+
+ private void startLoadingIndicator() {
+ // Display the indicator after a certain time, to avoid flashing the indicator constantly,
+ // even when performance is acceptable.
+ mHandler.postDelayed(mProgressIndicatorRunnable, mProgressBarDelay);
+ }
+
+ private void stopLoadingIndicator() {
+ mHandler.removeCallbacks(mProgressIndicatorRunnable);
+ hideViewAnimated(mProgressBar);
+ }
+
@Override
public void onStop() {
super.onStop();
+ stopLoadingIndicator();
if (mMediaSource != null) {
mMediaSource.unsubscribe(mBrowseObserver);
}
@@ -239,8 +294,11 @@ public class BrowseFragment extends Fragment {
mBrowseAdapter = null;
}
if (!success) {
- mBrowseList.setVisibility(View.GONE);
- // TODO(b/77647430) implement intermediate states.
+ hideViewAnimated(mBrowseList);
+ stopLoadingIndicator();
+ mErrorMessage.setText(R.string.cannot_connect_to_app);
+ showViewAnimated(mErrorIcon);
+ showViewAnimated(mErrorMessage);
return;
}
mBrowseAdapter = new BrowseAdapter(getContext(), mMediaSource.getMediaBrowser(),
@@ -273,4 +331,25 @@ public class BrowseFragment extends Fragment {
return mBrowseStack.lastElement();
}
}
+
+ private void hideViewAnimated(View view) {
+ view.animate()
+ .alpha(0f)
+ .setDuration(mFadeDuration)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ view.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ private void showViewAnimated(View view) {
+ view.setAlpha(0f);
+ view.setVisibility(View.VISIBLE);
+ view.animate()
+ .alpha(1f)
+ .setDuration(mFadeDuration)
+ .setListener(null);
+ }
}
diff --git a/src/com/android/car/media/MediaActivity.java b/src/com/android/car/media/MediaActivity.java
index db3a0239..d158e608 100644
--- a/src/com/android/car/media/MediaActivity.java
+++ b/src/com/android/car/media/MediaActivity.java
@@ -15,8 +15,12 @@
*/
package com.android.car.media;
+import android.annotation.NonNull;
+import android.car.Car;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.support.design.widget.AppBarLayout;
@@ -39,12 +43,8 @@ import com.android.car.media.widgets.AppBarView;
import com.android.car.media.widgets.MetadataView;
import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
-import java.util.Set;
import java.util.stream.Collectors;
import androidx.car.drawer.CarDrawerActivity;
@@ -58,22 +58,15 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
AppSelectionFragment.Callbacks {
private static final String TAG = "MediaActivity";
- /** Intent extra specifying the package with the MediaBrowser **/
+ /** Intent extra specifying the package with the MediaBrowser */
public static final String KEY_MEDIA_PACKAGE = "media_package";
-
- /**
- * Custom media sources which won't be rendered inside the template but will be offered on the
- * app selector.
- */
- private static final Set<String> CUSTOM_MEDIA_SOURCES = new HashSet<>();
- static {
- CUSTOM_MEDIA_SOURCES.add("com.android.car.radio");
- }
+ /** Shared preferences files */
+ public static final String SHARED_PREF = "com.android.car.media";
+ /** Shared preference containing the last controlled source */
+ public static final String LAST_MEDIA_SOURCE_SHARED_PREF_KEY = "last_media_source";
/** Configuration (controlled from resources) */
private boolean mContentForwardBrowseEnabled;
- private boolean mForceBrowseTabs;
- private int mMaxBrowserTabs;
private float mBackgroundBlurRadius;
private float mBackgroundBlurScale;
@@ -82,6 +75,7 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
private MediaSource mMediaSource;
private PlaybackModel mPlaybackModel;
private MediaSourcesManager mMediaSourcesManager;
+ private SharedPreferences mSharedPreferences;
/** Layout views */
private AppBarView mAppBarView;
@@ -94,7 +88,6 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
private ViewGroup mBrowseControlsContainer;
/** Current state */
- private MediaItemMetadata mCurrentMetadata;
private Fragment mCurrentFragment;
private Mode mMode = Mode.BROWSING;
private boolean mIsAppSelectorOpen;
@@ -117,7 +110,6 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
@Override
public void onSourceChanged() {
updateMetadata();
- updateBrowseSource();
}
@Override
@@ -188,7 +180,6 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
mAppSelectionFragment.setExitTransition(new Fade().setDuration(fadeDuration));
mPlaybackModel = new PlaybackModel(this);
mMediaSourcesManager = new MediaSourcesManager(this);
- mMaxBrowserTabs = getResources().getInteger(R.integer.max_browse_tabs);
mDrawerBarLayout = findViewById(androidx.car.R.id.appbar);
mDrawerBarLayout.setVisibility(forceBrowseTabs ? View.GONE : View.VISIBLE);
mAlbumBackground = findViewById(R.id.media_background);
@@ -203,6 +194,7 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
mBackgroundBlurRadius = outValue.getFloat();
getResources().getValue(R.dimen.playback_background_blur_scale, outValue, true);
mBackgroundBlurScale = outValue.getFloat();
+ mSharedPreferences = getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
}
@Override
@@ -240,7 +232,6 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
}
setIntent(intent);
- getDrawerController().closeDrawer();
handleIntent();
}
@@ -260,36 +251,74 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
if (!success) {
updateTabs(new ArrayList<>());
mMediaSource.unsubscribeChildren(null);
- Intent intent = getPackageManager().
- getLaunchIntentForPackage(mMediaSource.getPackageName());
- startActivity(intent);
return;
}
mMediaSource.subscribeChildren(null, mRootItemsSubscription);
}
private void handleIntent() {
- updateBrowseSource();
- switchToMode(getRequestedMediaPackageName() == null || !mContentForwardBrowseEnabled
- ? Mode.PLAYBACK
- : Mode.BROWSING);
+ Intent intent = getIntent();
+ String action = intent != null ? intent.getAction() : null;
+
+ getDrawerController().closeDrawer();
+
+ if (Car.CAR_INTENT_ACTION_MEDIA_TEMPLATE.equals(action)) {
+ // The user either wants to browse a particular media source or switch to the
+ // playback UI.
+ String packageName = intent.getStringExtra(KEY_MEDIA_PACKAGE);
+ if (packageName != null) {
+ // We were told to navigate to a particular package: we open browse for it.
+ updateBrowseSource(new MediaSource(this, packageName));
+ switchToMode(Mode.BROWSING);
+ return;
+ }
+
+ // If didn't receive a package name and we are playing something: show the playback
+ // UI for the playing media source.
+ MediaSource mediaSource = mPlaybackModel.getMediaSource();
+ if (mediaSource != null) {
+ updateBrowseSource(mPlaybackModel.getMediaSource());
+ switchToMode(Mode.PLAYBACK);
+ return;
+ }
+ }
+
+ // In any other case, if we were already browsing something: just close drawers/overlays
+ // and display what we have.
+ if (mMediaSource != null) {
+ closeAppSelector();
+ updateMetadata();
+ return;
+ }
+
+ // If we don't have a current media source, we try with the last one we remember.
+ MediaSource lastMediaSource = getLastMediaSource();
+ if (lastMediaSource != null) {
+ closeAppSelector();
+ updateBrowseSource(lastMediaSource);
+ updateMetadata();
+ } else {
+ // If we don't have anything from before: open the app selector.
+ openAppSelector();
+ }
}
/**
- * Updates the media source being browsed. This could be necessary when the source playing
- * changes, or if the user requests to connect to a different source.
+ * Updates the media source being browsed. This could be necessary when the user selects
+ * a different media source.
*/
- private void updateBrowseSource() {
- MediaSource mediaSource = getCurrentMediaSource();
+ private void updateBrowseSource(MediaSource mediaSource) {
if (Objects.equals(mediaSource, mMediaSource)) {
// No change, nothing to do.
return;
}
if (mMediaSource != null) {
+ mMediaSource.unsubscribeChildren(null);
mMediaSource.unsubscribe(mMediaSourceObserver);
updateTabs(new ArrayList<>());
}
mMediaSource = mediaSource;
+ setLastMediaSource(mMediaSource);
mAppBarView.setState(AppBarView.State.IDLE);
if (mMediaSource != null) {
if (Log.isLoggable(TAG, Log.INFO)) {
@@ -313,18 +342,6 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
}
/**
- * @return the media source that should be browsed. If the user expressed the intention of
- * browsing something different than what is being played, we will return that. Otherwise
- * we return the souce that is playing.
- */
- private MediaSource getCurrentMediaSource() {
- String packageName = getRequestedMediaPackageName();
- return packageName != null
- ? new MediaSource(this, packageName)
- : mPlaybackModel.getMediaSource();
- }
-
- /**
* @return the package name of the media source requested by the incoming {@link Intent} or
* null if no source was indicated.
*/
@@ -339,6 +356,7 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
}
private void updateTabs(List<MediaItemMetadata> items) {
+ items = customizeTabs(mMediaSource, items);
List<MediaItemMetadata> browsableTopLevel = items.stream()
.filter(item -> item.isBrowsable())
.collect(Collectors.toList());
@@ -354,6 +372,20 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
}
}
+ /**
+ * Extension point used to customize media items displayed on the tabs.
+ *
+ * @param mediaSource media source these items belong to.
+ * @param items items to override.
+ * @return an updated list of items.
+ * @deprecated This method will be removed on b/79089344
+ */
+ @Deprecated
+ protected List<MediaItemMetadata> customizeTabs(MediaSource mediaSource,
+ List<MediaItemMetadata> items) {
+ return items;
+ }
+
private void switchToMode(Mode mode) {
Log.i(TAG, "Switch mode to " + mode + " (intent package: " +
getRequestedMediaPackageName() + ")");
@@ -381,16 +413,10 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
mBrowseControlsContainer.setVisibility(mMode == Mode.PLAYBACK
? View.GONE
: View.VISIBLE);
- MediaItemMetadata metadata = mPlaybackModel.getMetadata();
- if (Objects.equals(mCurrentMetadata, metadata)) {
- return;
- }
- mCurrentMetadata = metadata;
mUpdateAlbumArtRunnable.run();
} else {
mAlbumBackground.setImageBitmap(null, true);
mBrowseControlsContainer.setVisibility(View.GONE);
- mCurrentMetadata = null;
}
}
@@ -495,24 +521,40 @@ public class MediaActivity extends CarDrawerActivity implements BrowseFragment.C
return mMediaSourcesManager.getMediaSources()
.stream()
.filter(source -> source.getMediaBrowser() != null
- || CUSTOM_MEDIA_SOURCES.contains(source.getPackageName()))
+ || source.isCustom())
.collect(Collectors.toList());
}
@Override
public void onMediaSourceSelected(MediaSource mediaSource) {
closeAppSelector();
- String packageName = mediaSource.getPackageName();
- if (mediaSource.getMediaBrowser() != null
- && !CUSTOM_MEDIA_SOURCES.contains(packageName)) {
- Intent intent = new Intent();
- intent.putExtra(KEY_MEDIA_PACKAGE, packageName);
- setIntent(intent);
- getDrawerController().closeDrawer();
- handleIntent();
+ if (mediaSource.getMediaBrowser() != null && !mediaSource.isCustom()) {
+ updateBrowseSource(mediaSource);
+ switchToMode(Mode.BROWSING);
} else {
+ String packageName = mediaSource.getPackageName();
Intent intent = getPackageManager().getLaunchIntentForPackage(packageName);
startActivity(intent);
}
}
+
+ private MediaSource getLastMediaSource() {
+ String packageName = mSharedPreferences.getString(LAST_MEDIA_SOURCE_SHARED_PREF_KEY, null);
+ if (packageName == null) {
+ return null;
+ }
+ // Verify that the stored package name corresponds to a currently installed media source.
+ for (MediaSource mediaSource : mMediaSourcesManager.getMediaSources()) {
+ if (mediaSource.getPackageName().equals(packageName)) {
+ return mediaSource;
+ }
+ }
+ return null;
+ }
+
+ private void setLastMediaSource(@NonNull MediaSource mediaSource) {
+ mSharedPreferences.edit()
+ .putString(LAST_MEDIA_SOURCE_SHARED_PREF_KEY, mediaSource.getPackageName())
+ .apply();
+ }
}
diff --git a/src/com/android/car/media/PlaybackFragment.java b/src/com/android/car/media/PlaybackFragment.java
index 0478d8a3..da428326 100644
--- a/src/com/android/car/media/PlaybackFragment.java
+++ b/src/com/android/car/media/PlaybackFragment.java
@@ -34,12 +34,6 @@ 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;
@@ -49,6 +43,12 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
+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.
* It observes a {@link PlaybackModel} and updates its information depending on the currently
@@ -60,6 +60,7 @@ public class PlaybackFragment extends Fragment {
private PlaybackModel mModel;
private PlaybackControls mPlaybackControls;
private QueueItemsAdapter mQueueAdapter;
+ private PagedListView mQueue;
private MetadataController mMetadataController;
private ConstraintLayout mRootView;
@@ -96,6 +97,7 @@ public class PlaybackFragment extends Fragment {
textListItem.setTitle(item.getTitle() != null ? item.getTitle().toString() : null);
textListItem.setBody(item.getSubtitle() != null ? item.getSubtitle().toString() : null);
textListItem.setOnClickListener(v -> onQueueItemClicked(item));
+
return textListItem;
}
@Override
@@ -103,9 +105,10 @@ public class PlaybackFragment extends Fragment {
return mQueueItems.size();
}
};
- private static class QueueItemsAdapter extends ListItemAdapter {
+ private class QueueItemsAdapter extends ListItemAdapter {
QueueItemsAdapter(Context context, ListItemProvider itemProvider) {
super(context, itemProvider, BackgroundStyle.SOLID);
+ setHasStableIds(true);
}
void refresh() {
@@ -113,6 +116,11 @@ public class PlaybackFragment extends Fragment {
// RecyclerView updates.
this.notifyDataSetChanged();
}
+
+ @Override
+ public long getItemId(int position) {
+ return mQueueItems.get(position).getQueueId();
+ }
}
private PlaybackControls.Listener mPlaybackControlsListener = new PlaybackControls.Listener() {
@@ -130,9 +138,10 @@ public class PlaybackFragment extends Fragment {
View view = inflater.inflate(R.layout.fragment_playback, container, false);
mRootView = view.findViewById(R.id.playback_container);
mModel = new PlaybackModel(getContext());
+ mQueue = view.findViewById(R.id.queue_list);
initPlaybackControls(view.findViewById(R.id.playback_controls));
- initQueue(view.findViewById(R.id.queue_list));
+ initQueue(mQueue);
initMetadataController(view);
return view;
}
@@ -203,6 +212,7 @@ public class PlaybackFragment extends Fragment {
updateState();
}
mMetadataController.resumeUpdates();
+ mQueue.getRecyclerView().scrollToPosition(0);
}
});
TransitionManager.beginDelayedTransition(mRootView, transition);
@@ -225,7 +235,6 @@ public class PlaybackFragment extends Fragment {
mQueueAdapter.refresh();
}
-
private void updateAccentColor() {
int defaultColor = getResources().getColor(android.R.color.background_dark, null);
MediaSource mediaSource = mModel.getMediaSource();
diff --git a/src/com/android/car/media/browse/BrowseAdapter.java b/src/com/android/car/media/browse/BrowseAdapter.java
index 0092c5ab..60166888 100644
--- a/src/com/android/car/media/browse/BrowseAdapter.java
+++ b/src/com/android/car/media/browse/BrowseAdapter.java
@@ -63,7 +63,7 @@ import java.util.stream.Collectors;
* <p>Consumers of this adapter should use {@link #registerObserver(Observer)} to receive updates.
*/
public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
- private static final String TAG = "MediaBrowseAdapter";
+ private static final String TAG = "BrowseAdapter";
@NonNull
private final Context mContext;
private final MediaBrowser mMediaBrowser;
@@ -76,7 +76,21 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
private List<MediaItemMetadata> mQueue;
private CharSequence mQueueTitle;
private int mMaxSpanSize = 1;
- private BrowseViewData.State mState = BrowseViewData.State.IDLE;
+ private State mState = State.IDLE;
+
+ /**
+ * Possible states of the adapter
+ */
+ public enum State {
+ /** Loading of this item hasn't started yet */
+ IDLE,
+ /** There is pending information before this item can be displayed */
+ LOADING,
+ /** It was not possible to load metadata for this item */
+ ERROR,
+ /** Metadata for this items has been correctly loaded */
+ LOADED
+ }
/**
* An {@link BrowseAdapter} observer.
@@ -149,7 +163,7 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
*/
final MediaItemMetadata mItem;
/** Current loading state for this item */
- BrowseViewData.State mState = BrowseViewData.State.LOADING;
+ State mState = State.LOADING;
/** Playable children of the given item */
List<MediaItemMetadata> mPlayableChildren = new ArrayList<>();
/** Browsable children of the given item */
@@ -265,9 +279,9 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
/**
* @return the global loading state. Consumers can use this state to determine if more
* information is still pending to arrive or not. This method will report
- * {@link BrowseViewData.State#ERROR} only if the list of immediate children fails to load.
+ * {@link State#ERROR} only if the list of immediate children fails to load.
*/
- public BrowseViewData.State getState() {
+ public State getState() {
return mState;
}
@@ -317,6 +331,8 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
if (!state.mIsSubscribed && state.mItem.isBrowsable()) {
mMediaBrowser.subscribe(state.mItem.getId(), mSubscriptionCallback);
state.mIsSubscribed = true;
+ } else {
+ state.mState = State.LOADED;
}
}
@@ -380,7 +396,7 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
return;
}
itemState.setChildren(children);
- itemState.mState = BrowseViewData.State.LOADED;
+ itemState.mState = State.LOADED;
}
updateGlobalState();
notify(Observer::onDirty);
@@ -395,7 +411,7 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
private void onLoadingError(String parentId) {
if (parentId.equals(mParentMediaItemId)) {
- mState = BrowseViewData.State.ERROR;
+ mState = State.ERROR;
} else {
MediaItemState state = mItemStates.get(parentId);
if (state == null) {
@@ -403,20 +419,20 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
return;
}
state.setChildren(new ArrayList<>());
- state.mState = BrowseViewData.State.ERROR;
+ state.mState = State.ERROR;
+ updateGlobalState();
}
- updateGlobalState();
notify(Observer::onDirty);
}
private void updateGlobalState() {
for (MediaItemState state: mItemStates.values()) {
- if (state.mState == BrowseViewData.State.LOADING) {
- mState = BrowseViewData.State.LOADING;
+ if (state.mState == State.LOADING) {
+ mState = State.LOADING;
return;
}
}
- mState = BrowseViewData.State.LOADED;
+ mState = State.LOADED;
}
private DiffUtil.Callback createDiffUtil(List<BrowseViewData> oldList,
@@ -463,7 +479,7 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
private class ItemsBuilder {
private List<BrowseViewData> result = new ArrayList<>();
- void addItem(MediaItemMetadata item, BrowseViewData.State state,
+ void addItem(MediaItemMetadata item, State state,
BrowseItemViewType viewType, Consumer<Observer> notification) {
View.OnClickListener listener = notification != null ?
view -> BrowseAdapter.this.notify(notification) :
@@ -494,7 +510,7 @@ public class BrowseAdapter extends RecyclerView.Adapter<BrowseViewHolder> {
}
- void addBrowseBlock(MediaItemMetadata header, BrowseViewData.State state,
+ void addBrowseBlock(MediaItemMetadata header, State state,
List<MediaItemMetadata> items, BrowseItemViewType viewType, int maxChildren,
boolean showHeader, boolean showMoreFooter) {
if (showHeader) {
diff --git a/src/com/android/car/media/browse/BrowseViewData.java b/src/com/android/car/media/browse/BrowseViewData.java
index 31d0588e..3a6cc654 100644
--- a/src/com/android/car/media/browse/BrowseViewData.java
+++ b/src/com/android/car/media/browse/BrowseViewData.java
@@ -33,27 +33,13 @@ class BrowseViewData {
/** View type associated with this item */
@NonNull public final BrowseItemViewType mViewType;
/** Current state of this item */
- public final State mState;
+ public final BrowseAdapter.State mState;
/** Text associated with this item */
public final CharSequence mText;
/** Click listener to set for this item */
public final View.OnClickListener mOnClickListener;
/**
- * Possible states of a view
- */
- public enum State {
- /** Loading of this item hasn't started yet */
- IDLE,
- /** There is pending information before this item can be displayed */
- LOADING,
- /** It was not possible to load metadata for this item */
- ERROR,
- /** Metadata for this items has been correctly loaded */
- LOADED
- }
-
- /**
* Creates a {@link BrowseViewData} for a particular {@link MediaItemMetadata}.
*
* @param mediaItem {@link MediaItemMetadata} metadata
@@ -62,7 +48,7 @@ class BrowseViewData {
* @param onClickListener optional {@link android.view.View.OnClickListener}
*/
BrowseViewData(MediaItemMetadata mediaItem, @NonNull BrowseItemViewType viewType,
- @NonNull State state, View.OnClickListener onClickListener) {
+ @NonNull BrowseAdapter.State state, View.OnClickListener onClickListener) {
mMediaItem = mediaItem;
mViewType = viewType;
mState = state;
diff --git a/src/com/android/car/media/browse/ContentForwardStrategy.java b/src/com/android/car/media/browse/ContentForwardStrategy.java
index 7720366d..88fdc1a3 100644
--- a/src/com/android/car/media/browse/ContentForwardStrategy.java
+++ b/src/com/android/car/media/browse/ContentForwardStrategy.java
@@ -89,7 +89,7 @@ public interface ContentForwardStrategy {
@Override
public boolean shouldBeExpanded(MediaItemMetadata mediaItem) {
- return true;
+ return false;
}
@Override
diff --git a/src/com/android/car/media/widgets/AppBarView.java b/src/com/android/car/media/widgets/AppBarView.java
index a8cc4151..02a6daf6 100644
--- a/src/com/android/car/media/widgets/AppBarView.java
+++ b/src/com/android/car/media/widgets/AppBarView.java
@@ -36,7 +36,6 @@ public class AppBarView extends RelativeLayout {
/** Default number of tabs to show on this app bar */
private static int DEFAULT_MAX_TABS = 4;
- private List<MediaSource> mMediaSources = new ArrayList<>();
private LinearLayout mTabsContainer;
private ImageView mAppIcon;
private ImageView mAppSwitchIcon;
@@ -55,6 +54,8 @@ public class AppBarView extends RelativeLayout {
private float mSelectedTabAlpha;
private float mUnselectedTabAlpha;
private MediaItemMetadata mSelectedItem;
+ private String mMediaAppTitle;
+ private Drawable mDefaultIcon;
/**
* Application bar listener
@@ -154,6 +155,8 @@ public class AppBarView extends RelativeLayout {
mSelectedTabAlpha = outValue.getFloat();
getResources().getValue(R.dimen.browse_tab_alpha_unselected, outValue, true);
mUnselectedTabAlpha = outValue.getFloat();
+ mMediaAppTitle = getResources().getString(R.string.media_app_title);
+ mDefaultIcon = getResources().getDrawable(R.drawable.ic_music);
setState(State.IDLE);
}
@@ -231,15 +234,18 @@ public class AppBarView extends RelativeLayout {
* Updates the title to display when the bar is not showing tabs.
*/
public void setTitle(CharSequence title) {
- mTitle.setText(title);
+ mTitle.setText(title != null ? title : mMediaAppTitle);
}
/**
* Updates the application icon to show next to the application switcher.
*/
public void setAppIcon(Bitmap icon) {
- mAppIcon.setImageBitmap(icon);
- mAppIcon.setVisibility(icon != null ? View.VISIBLE : View.GONE);
+ if (icon != null) {
+ mAppIcon.setImageBitmap(icon);
+ } else {
+ mAppIcon.setImageDrawable(mDefaultIcon);
+ }
}
/**
@@ -266,9 +272,9 @@ public class AppBarView extends RelativeLayout {
mSelectedItem.getId(),
((MediaItemMetadata) tabView.getTag()).getId());
tabView.setAlpha(match ? mSelectedTabAlpha : mUnselectedTabAlpha);
- tabView.invalidate();
}
}
+ mTabsContainer.invalidate();
}
/**
diff --git a/src/com/android/car/media/widgets/MediaItemTabView.java b/src/com/android/car/media/widgets/MediaItemTabView.java
index 97019266..581a3738 100644
--- a/src/com/android/car/media/widgets/MediaItemTabView.java
+++ b/src/com/android/car/media/widgets/MediaItemTabView.java
@@ -47,6 +47,7 @@ public class MediaItemTabView extends LinearLayout {
setOrientation(LinearLayout.VERTICAL);
setFocusable(true);
setGravity(Gravity.CENTER);
+ setBackground(context.getDrawable(R.drawable.app_item_background));
int[] attrs = new int[]{android.R.attr.selectableItemBackground};
TypedArray typedArray = context.obtainStyledAttributes(attrs);