Move some of the media error handling code to car-media-common
Fixes: 165084717
Test: manual
Change-Id: I5caf97530ccfec43d421cc139d4cd020df180aeb
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 33d7f0d..258963f 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -35,30 +35,7 @@
<!-- Title to display when in playback view. [CHAR LIMIT=50] -->
<string name="fragment_playback_title">Now Playing</string>
- <!-- MediaActivity default error message [CHAR LIMIT=100] -->
- <string name="default_error_message">Something’s wrong. Try later.</string>
- <!-- Error message set when the application state is invalid to fulfill the request. [CHAR LIMIT=100] -->
- <string name="error_code_app_error">Can’t do that right now</string>
- <!-- Error message set when the request is not supported by the application. [CHAR LIMIT=100] -->
- <string name="error_code_not_supported">This app can’t do that</string>
- <!-- Error message set when the request cannot be performed because authentication has expired. [CHAR LIMIT=100] -->
- <string name="error_code_authentication_expired">Sign in to use this app</string>
- <!-- Error message set when a premium account is required for the request to succeed. [CHAR LIMIT=100] -->
- <string name="error_code_premium_account_required">Premium access required</string>
- <!-- Error message set when too many concurrent streams are detected. [CHAR LIMIT=100] -->
- <string name="error_code_concurrent_stream_limit">Listening on too many devices</string>
- <!-- Error message set when the content is blocked due to parental controls. [CHAR LIMIT=100] -->
- <string name="error_code_parental_control_restricted">That content is blocked</string>
- <!-- Error message set when the content is blocked due to being regionally unavailable. [CHAR LIMIT=100] -->
- <string name="error_code_not_available_in_region">Can’t get that content here</string>
- <!-- Error message set when the requested content is already playing. [CHAR LIMIT=100] -->
- <string name="error_code_content_already_playing">Already playing that content</string>
- <!-- Error message set when the application cannot skip any more songs because skip limit is reached. [CHAR LIMIT=100] -->
- <string name="error_code_skip_limit_reached">Can’t skip any more tracks</string>
- <!-- Error message set when the action is interrupted due to some external event. [CHAR LIMIT=100] -->
- <string name="error_code_action_aborted">Couldn’t finish. Try again.</string>
- <!-- Error message set when the playback navigation (previous, next) is not possible because the queue was exhausted. [CHAR LIMIT=100] -->
- <string name="error_code_end_of_queue">Nothing else is queued up</string>
+
<!-- Title string for the service used to bind to the current media source -->
<string name="service_notification_title">Connecting to media</string>
<!-- Title of the sound settings menu item. Will be displayed if the button is in the overflow menu. [CHAR_LIMIT=50] -->
diff --git a/src/com/android/car/media/BrowseViewController.java b/src/com/android/car/media/BrowseViewController.java
index fe2d8e8..106c151 100644
--- a/src/com/android/car/media/BrowseViewController.java
+++ b/src/com/android/car/media/BrowseViewController.java
@@ -43,6 +43,7 @@
import com.android.car.media.browse.BrowseAdapter;
import com.android.car.media.common.GridSpacingItemDecoration;
import com.android.car.media.common.MediaItemMetadata;
+import com.android.car.media.common.browse.BrowsedMediaItems;
import com.android.car.media.common.browse.MediaBrowserViewModel;
import com.android.car.media.common.source.MediaSource;
import com.android.car.media.widgets.AppBarController;
@@ -563,18 +564,7 @@
}
}
- /**
- * Filters the items that are valid for the root (tabs) or the current node. Returns null when
- * the given list is null to preserve its error signal.
- */
- @Nullable
- private List<MediaItemMetadata> filterItems(boolean forRoot,
- @Nullable List<MediaItemMetadata> items) {
- if (items == null) return null;
- Predicate<MediaItemMetadata> predicate = forRoot ? MediaItemMetadata::isBrowsable
- : item -> (item.isPlayable() || item.isBrowsable());
- return items.stream().filter(predicate).collect(Collectors.toList());
- }
+
private void onItemsUpdate(boolean forRoot, FutureData<List<MediaItemMetadata>> futureData) {
@@ -608,7 +598,8 @@
stopLoadingIndicator();
- List<MediaItemMetadata> items = filterItems(forRoot, futureData.getData());
+ List<MediaItemMetadata> items =
+ BrowsedMediaItems.filterItems(forRoot, futureData.getData());
if (forRoot) {
boolean browseTreeHasChildren = items != null && !items.isEmpty();
if (Log.isLoggable(TAG, Log.INFO)) {
diff --git a/src/com/android/car/media/ErrorScreenController.java b/src/com/android/car/media/ErrorScreenController.java
new file mode 100644
index 0000000..bd62f81
--- /dev/null
+++ b/src/com/android/car/media/ErrorScreenController.java
@@ -0,0 +1,41 @@
+package com.android.car.media;
+
+import android.app.PendingIntent;
+import android.car.content.pm.CarPackageManager;
+import android.view.ViewGroup;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.car.media.common.PlaybackErrorViewController;
+import com.android.car.media.common.source.MediaSource;
+
+/**
+ * A view controller that displays the playback state error iif there is no browse tree.
+ */
+public class ErrorScreenController extends ViewControllerBase {
+
+ private final PlaybackErrorViewController mPlaybackErrorViewController;
+
+ ErrorScreenController(FragmentActivity activity,
+ CarPackageManager carPackageManager, ViewGroup container) {
+ super(activity, carPackageManager, container, R.layout.fragment_error);
+
+ mPlaybackErrorViewController = new PlaybackErrorViewController(mContent);
+ }
+
+ @Override
+ void onMediaSourceChanged(@Nullable MediaSource mediaSource) {
+ super.onMediaSourceChanged(mediaSource);
+
+ mAppBarController.setListener(new BasicAppBarListener());
+ mAppBarController.setTitle(getAppBarDefaultTitle(mediaSource));
+
+ mPlaybackErrorViewController.hideErrorNoAnim();
+ }
+
+ public void setError(String message, String label, PendingIntent pendingIntent,
+ boolean distractionOptimized) {
+ mPlaybackErrorViewController.setError(message, label, pendingIntent, distractionOptimized);
+ }
+}
diff --git a/src/com/android/car/media/ErrorViewController.java b/src/com/android/car/media/ErrorViewController.java
deleted file mode 100644
index f67e22c..0000000
--- a/src/com/android/car/media/ErrorViewController.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package com.android.car.media;
-
-import android.app.PendingIntent;
-import android.car.content.pm.CarPackageManager;
-import android.car.drivingstate.CarUxRestrictions;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentActivity;
-
-import com.android.car.apps.common.UxrButton;
-import com.android.car.apps.common.UxrTextView;
-import com.android.car.apps.common.util.ViewUtils;
-import com.android.car.media.common.source.MediaSource;
-
-/**
- * A view controller that displays the playback state error iif there is no browse tree.
- */
-public class ErrorViewController extends ViewControllerBase {
- private final String TAG = "ErrorViewController";
-
- // mErrorMessageView is defined explicitly as a UxrTextView instead of a TextView to
- // provide clarity as it may be misleading to assume that mErrorMessageView extends all TextView
- // methods. In addition, it increases discoverability of runtime issues that may occur.
- private final UxrTextView mErrorMessageView;
- private final UxrButton mErrorButton;
-
-
- ErrorViewController(FragmentActivity activity,
- CarPackageManager carPackageManager, ViewGroup container) {
- super(activity, carPackageManager, container, R.layout.fragment_error);
-
- mErrorMessageView = mContent.findViewById(R.id.error_message);
- mErrorButton = mContent.findViewById(R.id.error_button);
- }
-
- @Override
- void onMediaSourceChanged(@Nullable MediaSource mediaSource) {
- super.onMediaSourceChanged(mediaSource);
-
- mAppBarController.setListener(new BasicAppBarListener());
- mAppBarController.setTitle(getAppBarDefaultTitle(mediaSource));
-
- ViewUtils.hideViewAnimated(mErrorMessageView, 0);
- ViewUtils.hideViewAnimated(mErrorButton, 0);
- }
-
- public void setError(String message, String label, PendingIntent pendingIntent,
- boolean isDistractionOptimized) {
- mErrorMessageView.setText(message);
-
- // Only show the error button if the error is actionable.
- if (label != null && pendingIntent != null) {
- mErrorButton.setText(label);
-
- mErrorButton.setUxRestrictions(isDistractionOptimized
- ? CarUxRestrictions.UX_RESTRICTIONS_BASELINE
- : CarUxRestrictions.UX_RESTRICTIONS_NO_SETUP);
-
- mErrorButton.setOnClickListener(v -> {
- try {
- pendingIntent.send();
- } catch (PendingIntent.CanceledException e) {
- if (Log.isLoggable(TAG, Log.ERROR)) {
- Log.e(TAG, "Pending intent canceled");
- }
- }
- });
- mErrorButton.setVisibility(View.VISIBLE);
- } else {
- mErrorButton.setVisibility(View.GONE);
- }
-
- ViewUtils.showViewAnimated(mErrorMessageView, mFadeDuration);
- ViewUtils.showViewAnimated(mErrorButton, mFadeDuration);
- }
-}
diff --git a/src/com/android/car/media/MediaActivity.java b/src/com/android/car/media/MediaActivity.java
index a0a4f2c..0c02e56 100644
--- a/src/com/android/car/media/MediaActivity.java
+++ b/src/com/android/car/media/MediaActivity.java
@@ -31,7 +31,6 @@
import android.content.Intent;
import android.content.res.Resources;
import android.os.Bundle;
-import android.support.v4.media.session.PlaybackStateCompat;
import android.text.TextUtils;
import android.util.Log;
import android.util.Size;
@@ -54,7 +53,7 @@
import com.android.car.apps.common.util.VectorMath;
import com.android.car.apps.common.util.CarPackageManagerUtils;
import com.android.car.apps.common.util.ViewUtils;
-import com.android.car.media.common.MediaConstants;
+import com.android.car.media.common.PlaybackErrorsHelper;
import com.android.car.media.common.MediaItemMetadata;
import com.android.car.media.common.MinimizedPlaybackControlBar;
import com.android.car.media.common.playback.PlaybackViewModel;
@@ -63,7 +62,6 @@
import com.android.car.ui.AlertDialogBuilder;
import com.android.car.ui.utils.CarUxRestrictionsUtil;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
@@ -90,7 +88,7 @@
private ViewGroup mBrowseContainer;
private ViewGroup mPlaybackContainer;
private ViewGroup mErrorContainer;
- private ErrorViewController mErrorController;
+ private ErrorScreenController mErrorController;
private ViewGroup mSearchContainer;
private Toast mToast;
@@ -133,30 +131,6 @@
FATAL_ERROR
}
- private static final Map<Integer, Integer> ERROR_CODE_MESSAGES_MAP;
-
- static {
- Map<Integer, Integer> map = new HashMap<>();
- map.put(PlaybackStateCompat.ERROR_CODE_APP_ERROR, R.string.error_code_app_error);
- map.put(PlaybackStateCompat.ERROR_CODE_NOT_SUPPORTED, R.string.error_code_not_supported);
- map.put(PlaybackStateCompat.ERROR_CODE_AUTHENTICATION_EXPIRED,
- R.string.error_code_authentication_expired);
- map.put(PlaybackStateCompat.ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED,
- R.string.error_code_premium_account_required);
- map.put(PlaybackStateCompat.ERROR_CODE_CONCURRENT_STREAM_LIMIT,
- R.string.error_code_concurrent_stream_limit);
- map.put(PlaybackStateCompat.ERROR_CODE_PARENTAL_CONTROL_RESTRICTED,
- R.string.error_code_parental_control_restricted);
- map.put(PlaybackStateCompat.ERROR_CODE_NOT_AVAILABLE_IN_REGION,
- R.string.error_code_not_available_in_region);
- map.put(PlaybackStateCompat.ERROR_CODE_CONTENT_ALREADY_PLAYING,
- R.string.error_code_content_already_playing);
- map.put(PlaybackStateCompat.ERROR_CODE_SKIP_LIMIT_REACHED,
- R.string.error_code_skip_limit_reached);
- map.put(PlaybackStateCompat.ERROR_CODE_ACTION_ABORTED, R.string.error_code_action_aborted);
- map.put(PlaybackStateCompat.ERROR_CODE_END_OF_QUEUE, R.string.error_code_end_of_queue);
- ERROR_CODE_MESSAGES_MAP = Collections.unmodifiableMap(map);
- }
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -247,100 +221,62 @@
private void handlePlaybackState(PlaybackViewModel.PlaybackStateWrapper state,
boolean ignoreSameState) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG,
- "handlePlaybackState(); state change: " + (mCurrentPlaybackStateWrapper != null
- ? mCurrentPlaybackStateWrapper.getState() : null) + " -> " + (
- state != null ? state.getState() : null));
-
- }
-
- // TODO(arnaudberry) rethink interactions between customized layouts and dynamic visibility.
- mCanShowMiniPlaybackControls = (state != null) && state.shouldDisplay();
- updateMiniPlaybackControls(true);
-
- if (state == null) {
- mCurrentPlaybackStateWrapper = null;
- return;
- }
-
- String displayedMessage = getDisplayedMessage(state);
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Displayed error message: [" + displayedMessage + "]");
- }
- if (ignoreSameState && mCurrentPlaybackStateWrapper != null
- && mCurrentPlaybackStateWrapper.getState() == state.getState()
- && TextUtils.equals(displayedMessage,
- getDisplayedMessage(mCurrentPlaybackStateWrapper))) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Ignore same playback state.");
- }
- return;
- }
-
- mCurrentPlaybackStateWrapper = state;
-
- maybeCancelToast();
- maybeCancelDialog();
-
- Bundle extras = state.getExtras();
- PendingIntent intent = extras == null ? null : extras.getParcelable(
- MediaConstants.ERROR_RESOLUTION_ACTION_INTENT);
- String label = extras == null ? null : extras.getString(
- MediaConstants.ERROR_RESOLUTION_ACTION_LABEL);
-
- boolean isFatalError = false;
- if (!TextUtils.isEmpty(displayedMessage)) {
- if (mBrowseController.browseTreeHasChildren()) {
- if (intent != null && !isUxRestricted()) {
- showDialog(intent, displayedMessage, label, getString(android.R.string.cancel));
- } else {
- showToast(displayedMessage);
- }
- } else {
- boolean isDistractionOptimized = intent == null
- ? false
- : CarPackageManagerUtils.isDistractionOptimized(mCarPackageManager, intent);
- getErrorController().setError(displayedMessage, label, intent,
- isDistractionOptimized);
- isFatalError = true;
- }
- }
- if (isFatalError) {
- changeMode(Mode.FATAL_ERROR);
- } else if (mMode == Mode.FATAL_ERROR) {
- changeMode(Mode.BROWSING);
- }
+ mErrorsHelper.handlePlaybackState(TAG, state, ignoreSameState);
}
- private ErrorViewController getErrorController() {
+ private final PlaybackErrorsHelper mErrorsHelper = new PlaybackErrorsHelper(this) {
+
+ @Override
+ public void handlePlaybackState(@NonNull String tag,
+ PlaybackViewModel.PlaybackStateWrapper state, boolean ignoreSameState) {
+
+ // TODO rethink interactions between customized layouts and dynamic visibility.
+ mCanShowMiniPlaybackControls = (state != null) && state.shouldDisplay();
+ updateMiniPlaybackControls(true);
+ super.handlePlaybackState(tag, state, ignoreSameState);
+ }
+
+ @Override
+ public void handleNewPlaybackState(String displayedMessage, PendingIntent intent,
+ String label) {
+ maybeCancelToast();
+ maybeCancelDialog();
+
+ boolean isFatalError = false;
+ if (!TextUtils.isEmpty(displayedMessage)) {
+ if (mBrowseController.browseTreeHasChildren()) {
+ if (intent != null && !isUxRestricted()) {
+ showDialog(intent, displayedMessage, label,
+ getString(android.R.string.cancel));
+ } else {
+ showToast(displayedMessage);
+ }
+ } else {
+ boolean isDistractionOptimized =
+ intent != null && CarPackageManagerUtils.isDistractionOptimized(
+ mCarPackageManager, intent);
+ getErrorController().setError(displayedMessage, label, intent,
+ isDistractionOptimized);
+ isFatalError = true;
+ }
+ }
+ if (isFatalError) {
+ changeMode(MediaActivity.Mode.FATAL_ERROR);
+ } else if (mMode == MediaActivity.Mode.FATAL_ERROR) {
+ changeMode(MediaActivity.Mode.BROWSING);
+ }
+ }
+ };
+
+ private ErrorScreenController getErrorController() {
if (mErrorController == null) {
- mErrorController = new ErrorViewController(this, mCarPackageManager, mErrorContainer);
+ mErrorController = new ErrorScreenController(this, mCarPackageManager, mErrorContainer);
MediaSource mediaSource = getMediaSourceViewModel().getPrimaryMediaSource().getValue();
mErrorController.onMediaSourceChanged(mediaSource);
}
return mErrorController;
}
- private String getDisplayedMessage(@Nullable PlaybackViewModel.PlaybackStateWrapper state) {
- if (state == null) {
- return null;
- }
- if (!TextUtils.isEmpty(state.getErrorMessage())) {
- return state.getErrorMessage().toString();
- }
- // ERROR_CODE_UNKNOWN_ERROR means there is no error in PlaybackState.
- if (state.getErrorCode() != PlaybackStateCompat.ERROR_CODE_UNKNOWN_ERROR) {
- Integer messageId = ERROR_CODE_MESSAGES_MAP.get(state.getErrorCode());
- return messageId != null ? getString(messageId) : getString(
- R.string.default_error_message);
- }
- if (state.getState() == PlaybackStateCompat.STATE_ERROR) {
- return getString(R.string.default_error_message);
- }
- return null;
- }
-
private void showDialog(PendingIntent intent, String message, String positiveBtnText,
String negativeButtonText) {
AlertDialogBuilder dialog = new AlertDialogBuilder(this);
diff --git a/src/com/android/car/media/ViewControllerBase.java b/src/com/android/car/media/ViewControllerBase.java
index 156c6b2..14bbda0 100644
--- a/src/com/android/car/media/ViewControllerBase.java
+++ b/src/com/android/car/media/ViewControllerBase.java
@@ -44,7 +44,7 @@
/**
* Functionality common to content view controllers. It mainly handles the AppBar view,
- * which is common to all them.
+ * which is common to all of them.
*/
abstract class ViewControllerBase {
private static final String TAG = "ViewControllerBase";