summaryrefslogtreecommitdiff
path: root/app/src/play/java/de
diff options
context:
space:
mode:
authorMartin Fietz <martin.fietz@gmail.com>2017-10-17 22:55:30 +0200
committerMartin Fietz <martin.fietz@gmail.com>2017-10-17 22:55:30 +0200
commite884e989c781a2527b737ff30f0fcadd23e791f5 (patch)
tree0bcfc92171c2b52be85e9d38641fde3b8662aee4 /app/src/play/java/de
parentb47dc10b2a4ef46bf5e61dcd0c4640265b033721 (diff)
parent014e34e912cfcd9fae84643a8ab2a61872c18559 (diff)
downloadAntennaPod-e884e989c781a2527b737ff30f0fcadd23e791f5.zip
Merge branch 'develop'
Diffstat (limited to 'app/src/play/java/de')
-rw-r--r--app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java244
-rw-r--r--app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java21
-rw-r--r--app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java483
-rw-r--r--app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java15
-rw-r--r--app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java31
5 files changed, 794 insertions, 0 deletions
diff --git a/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java b/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java
new file mode 100644
index 000000000..87304b3d6
--- /dev/null
+++ b/app/src/play/java/de/danoeh/antennapod/activity/CastEnabledActivity.java
@@ -0,0 +1,244 @@
+package de.danoeh.antennapod.activity;
+
+import android.content.SharedPreferences;
+import android.media.AudioManager;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.support.annotation.CallSuper;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import com.google.android.gms.cast.ApplicationMetadata;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.cast.CastConsumer;
+import de.danoeh.antennapod.core.cast.CastManager;
+import de.danoeh.antennapod.core.cast.DefaultCastConsumer;
+import de.danoeh.antennapod.core.cast.SwitchableMediaRouteActionProvider;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.service.playback.PlaybackService;
+
+/**
+ * Activity that allows for showing the MediaRouter button whenever there's a cast device in the
+ * network.
+ */
+public abstract class CastEnabledActivity extends AppCompatActivity
+ implements SharedPreferences.OnSharedPreferenceChangeListener {
+ public static final String TAG = "CastEnabledActivity";
+
+ protected CastManager castManager;
+ protected SwitchableMediaRouteActionProvider mediaRouteActionProvider;
+ private final CastButtonVisibilityManager castButtonVisibilityManager = new CastButtonVisibilityManager();
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext()).
+ registerOnSharedPreferenceChangeListener(this);
+
+ castManager = CastManager.getInstance();
+ castManager.addCastConsumer(castConsumer);
+ castButtonVisibilityManager.setPrefEnabled(UserPreferences.isCastEnabled());
+ onCastConnectionChanged(castManager.isConnected());
+ }
+
+ @Override
+ protected void onDestroy() {
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+ .unregisterOnSharedPreferenceChangeListener(this);
+ castManager.removeCastConsumer(castConsumer);
+ super.onDestroy();
+ }
+
+ @Override
+ @CallSuper
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.cast_enabled, menu);
+ castButtonVisibilityManager.setMenu(menu);
+ return true;
+ }
+
+ @Override
+ @CallSuper
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ super.onPrepareOptionsMenu(menu);
+ MenuItem mediaRouteButton = menu.findItem(R.id.media_route_menu_item);
+ if (mediaRouteButton == null) {
+ Log.wtf(TAG, "MediaRoute item could not be found on the menu!", new Exception());
+ mediaRouteActionProvider = null;
+ return true;
+ }
+ mediaRouteActionProvider = castManager.addMediaRouterButton(mediaRouteButton);
+ if (mediaRouteActionProvider != null) {
+ mediaRouteActionProvider.setEnabled(castButtonVisibilityManager.shouldEnable());
+ }
+ return true;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ castButtonVisibilityManager.setResumed(true);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ castButtonVisibilityManager.setResumed(false);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (UserPreferences.PREF_CAST_ENABLED.equals(key)) {
+ boolean newValue = UserPreferences.isCastEnabled();
+ Log.d(TAG, "onSharedPreferenceChanged(), isCastEnabled set to " + newValue);
+ castButtonVisibilityManager.setPrefEnabled(newValue);
+ // PlaybackService has its own listener, so if it's active we don't have to take action here.
+ if (!newValue && !PlaybackService.isRunning) {
+ CastManager.getInstance().disconnect();
+ }
+ }
+ }
+
+ CastConsumer castConsumer = new DefaultCastConsumer() {
+ @Override
+ public void onApplicationConnected(ApplicationMetadata appMetadata, String sessionId, boolean wasLaunched) {
+ onCastConnectionChanged(true);
+ }
+
+ @Override
+ public void onDisconnected() {
+ onCastConnectionChanged(false);
+ }
+ };
+
+ private void onCastConnectionChanged(boolean connected) {
+ if (connected) {
+ castButtonVisibilityManager.onConnected();
+ setVolumeControlStream(AudioManager.USE_DEFAULT_STREAM_TYPE);
+ } else {
+ castButtonVisibilityManager.onDisconnected();
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ }
+ }
+
+ /**
+ * Should be called by any activity or fragment for which the cast button should be shown.
+ *
+ * @param showAsAction refer to {@link MenuItem#setShowAsAction(int)}
+ */
+ public final void requestCastButton(int showAsAction) {
+ castButtonVisibilityManager.requestCastButton(showAsAction);
+ }
+
+ private class CastButtonVisibilityManager {
+ private volatile boolean prefEnabled = false;
+ private volatile boolean viewRequested = false;
+ private volatile boolean resumed = false;
+ private volatile boolean connected = false;
+ private volatile int showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
+ private Menu menu;
+
+ public synchronized void setPrefEnabled(boolean newValue) {
+ if (prefEnabled != newValue && resumed && (viewRequested || connected)) {
+ if (newValue) {
+ castManager.incrementUiCounter();
+ } else {
+ castManager.decrementUiCounter();
+ }
+ }
+ prefEnabled = newValue;
+ if (mediaRouteActionProvider != null) {
+ mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
+ }
+ }
+
+ public synchronized void setResumed(boolean newValue) {
+ if (resumed == newValue) {
+ Log.e(TAG, "resumed should never change to the same value");
+ return;
+ }
+ resumed = newValue;
+ if (prefEnabled && (viewRequested || connected)) {
+ if (resumed) {
+ castManager.incrementUiCounter();
+ } else {
+ castManager.decrementUiCounter();
+ }
+ }
+ }
+
+ public synchronized void setViewRequested(boolean newValue) {
+ if (viewRequested != newValue && resumed && prefEnabled && !connected) {
+ if (newValue) {
+ castManager.incrementUiCounter();
+ } else {
+ castManager.decrementUiCounter();
+ }
+ }
+ viewRequested = newValue;
+ if (mediaRouteActionProvider != null) {
+ mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
+ }
+ }
+
+ public synchronized void setConnected(boolean newValue) {
+ if (connected != newValue && resumed && prefEnabled && !prefEnabled) {
+ if (newValue) {
+ castManager.incrementUiCounter();
+ } else {
+ castManager.decrementUiCounter();
+ }
+ }
+ connected = newValue;
+ if (mediaRouteActionProvider != null) {
+ mediaRouteActionProvider.setEnabled(prefEnabled && (viewRequested || connected));
+ }
+ }
+
+ public synchronized boolean shouldEnable() {
+ return prefEnabled && viewRequested;
+ }
+
+ public void setMenu(Menu menu) {
+ setViewRequested(false);
+ showAsAction = MenuItem.SHOW_AS_ACTION_IF_ROOM;
+ this.menu = menu;
+ setShowAsAction();
+ }
+
+ public void requestCastButton(int showAsAction) {
+ setViewRequested(true);
+ this.showAsAction = showAsAction;
+ setShowAsAction();
+ }
+
+ public void onConnected() {
+ setConnected(true);
+ setShowAsAction();
+ }
+
+ public void onDisconnected() {
+ setConnected(false);
+ setShowAsAction();
+ }
+
+ private void setShowAsAction() {
+ if (menu == null) {
+ Log.d(TAG, "setShowAsAction() without a menu");
+ return;
+ }
+ MenuItem item = menu.findItem(R.id.media_route_menu_item);
+ if (item == null) {
+ Log.e(TAG, "setShowAsAction(), but cast button not inflated");
+ return;
+ }
+ MenuItemCompat.setShowAsAction(item, connected? MenuItem.SHOW_AS_ACTION_ALWAYS : showAsAction);
+ }
+ }
+}
diff --git a/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java b/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java
new file mode 100644
index 000000000..916b13a38
--- /dev/null
+++ b/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java
@@ -0,0 +1,21 @@
+package de.danoeh.antennapod.config;
+
+import android.support.annotation.NonNull;
+import android.support.v7.app.MediaRouteControllerDialogFragment;
+import android.support.v7.app.MediaRouteDialogFactory;
+
+import de.danoeh.antennapod.core.CastCallbacks;
+import de.danoeh.antennapod.fragment.CustomMRControllerDialogFragment;
+
+public class CastCallbackImpl implements CastCallbacks {
+ @Override
+ public MediaRouteDialogFactory getMediaRouterDialogFactory() {
+ return new MediaRouteDialogFactory() {
+ @NonNull
+ @Override
+ public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() {
+ return new CustomMRControllerDialogFragment();
+ }
+ };
+ }
+}
diff --git a/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java b/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java
new file mode 100644
index 000000000..7b07d3f84
--- /dev/null
+++ b/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java
@@ -0,0 +1,483 @@
+package de.danoeh.antennapod.dialog;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.support.annotation.NonNull;
+import android.support.v4.media.MediaDescriptionCompat;
+import android.support.v4.media.MediaMetadataCompat;
+import android.support.v4.media.session.MediaControllerCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v4.util.Pair;
+import android.support.v4.view.MarginLayoutParamsCompat;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v7.app.MediaRouteControllerDialog;
+import android.support.v7.graphics.Palette;
+import android.support.v7.media.MediaRouter;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.target.Target;
+
+import java.util.concurrent.ExecutionException;
+
+import de.danoeh.antennapod.R;
+import de.danoeh.antennapod.core.glide.ApGlideSettings;
+import rx.Observable;
+import rx.Subscription;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.schedulers.Schedulers;
+
+public class CustomMRControllerDialog extends MediaRouteControllerDialog {
+ public static final String TAG = "CustomMRContrDialog";
+
+ private MediaRouter mediaRouter;
+ private MediaSessionCompat.Token token;
+
+ private ImageView artView;
+ private TextView titleView;
+ private TextView subtitleView;
+ private ImageButton playPauseButton;
+ private LinearLayout rootView;
+
+ private boolean viewsCreated = false;
+
+ private Subscription fetchArtSubscription;
+
+ private MediaControllerCompat mediaController;
+ private MediaControllerCompat.Callback mediaControllerCallback;
+
+ public CustomMRControllerDialog(Context context) {
+ this(context, 0);
+ }
+
+ public CustomMRControllerDialog(Context context, int theme) {
+ super(context, theme);
+ mediaRouter = MediaRouter.getInstance(getContext());
+ token = mediaRouter.getMediaSessionToken();
+ try {
+ if (token != null) {
+ mediaController = new MediaControllerCompat(getContext(), token);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error creating media controller", e);
+ }
+
+ if (mediaController != null) {
+ mediaControllerCallback = new MediaControllerCompat.Callback() {
+ @Override
+ public void onSessionDestroyed() {
+ if (mediaController != null) {
+ mediaController.unregisterCallback(mediaControllerCallback);
+ mediaController = null;
+ }
+ }
+
+ @Override
+ public void onMetadataChanged(MediaMetadataCompat metadata) {
+ updateViews();
+ }
+
+ @Override
+ public void onPlaybackStateChanged(PlaybackStateCompat state) {
+ updateState();
+ }
+ };
+ mediaController.registerCallback(mediaControllerCallback);
+ }
+ }
+
+ @Override
+ public View onCreateMediaControlView(Bundle savedInstanceState) {
+ boolean landscape = getContext().getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE;
+ if (landscape) {
+ /*
+ * When a horizontal LinearLayout measures itself, it first measures its children and
+ * settles their widths on the first pass, and only then figures out its height, never
+ * revisiting the widths measurements.
+ * When one has a child view that imposes a certain aspect ratio (such as an ImageView),
+ * then its width and height are related to each other, and so if one allows for a large
+ * height, then it will request for itself a large width as well. However, on the first
+ * child measurement, the LinearLayout imposes a very relaxed height bound, that the
+ * child uses to tell the width it wants, a value which the LinearLayout will interpret
+ * as final, even though the child will want to change it once a more restrictive height
+ * bound is imposed later.
+ *
+ * Our solution is, given that the heights of the children do not depend on their widths
+ * in this case, we first figure out the layout's height and only then perform the
+ * usual sequence of measurements.
+ *
+ * Note: this solution does not take into account any vertical paddings nor children's
+ * vertical margins in determining the height, as this View as well as its children are
+ * defined in code and no paddings/margins that would influence these computations are
+ * introduced.
+ *
+ * There were no resources online for this type of issue as far as I could gather.
+ */
+ rootView = new LinearLayout(getContext()) {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // We'd like to find the overall height before adjusting the widths within the LinearLayout
+ int maxHeight = Integer.MIN_VALUE;
+ if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
+ for (int i = 0; i < getChildCount(); i++) {
+ int height = Integer.MIN_VALUE;
+ View child = getChildAt(i);
+ ViewGroup.LayoutParams lp = child.getLayoutParams();
+ // we only measure children whose layout_height is not MATCH_PARENT
+ if (lp.height >= 0) {
+ height = lp.height;
+ } else if (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
+ child.measure(widthMeasureSpec, heightMeasureSpec);
+ height = child.getMeasuredHeight();
+ }
+ maxHeight = Math.max(maxHeight, height);
+ }
+ }
+ if (maxHeight > 0) {
+ super.onMeasure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY));
+ } else {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+ }
+ };
+ rootView.setOrientation(LinearLayout.HORIZONTAL);
+ } else {
+ rootView = new LinearLayout(getContext());
+ rootView.setOrientation(LinearLayout.VERTICAL);
+ }
+ FrameLayout.LayoutParams rootParams = new FrameLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ rootParams.setMargins(0, 0, 0,
+ getContext().getResources().getDimensionPixelSize(R.dimen.media_router_controller_bottom_margin));
+ rootView.setLayoutParams(rootParams);
+
+ // Start the session activity when a content item (album art, title or subtitle) is clicked.
+ View.OnClickListener onClickListener = v -> {
+ if (mediaController != null) {
+ PendingIntent pi = mediaController.getSessionActivity();
+ if (pi != null) {
+ try {
+ pi.send();
+ dismiss();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, pi + " was not sent, it had been canceled.");
+ }
+ }
+ }
+ };
+
+ LinearLayout.LayoutParams artParams;
+ /*
+ * On portrait orientation, we want to limit the artView's height to 9/16 of the available
+ * width. Reason is that we need to choose the height wisely otherwise we risk the dialog
+ * being much larger than the screen, and there doesn't seem to be a good way to know the
+ * available height beforehand.
+ *
+ * On landscape orientation, we want to limit the artView's width to its available height.
+ * Otherwise, horizontal images would take too much space and severely restrict the space
+ * for episode title and play/pause button.
+ *
+ * Internal implementation of ImageView only uses the source image's aspect ratio, but we
+ * want to impose our own and fallback to the source image's when it is more favorable.
+ * Solutions were inspired, among other similar sources, on
+ * http://stackoverflow.com/questions/18077325/scale-image-to-fill-imageview-width-and-keep-aspect-ratio
+ */
+ if (landscape) {
+ artView = new ImageView(getContext()) {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int desiredWidth = widthMeasureSpec;
+ int desiredMeasureMode = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY ?
+ MeasureSpec.EXACTLY : MeasureSpec.AT_MOST;
+ if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY) {
+ Drawable drawable = getDrawable();
+ if (drawable != null) {
+ int intrHeight = drawable.getIntrinsicHeight();
+ int intrWidth = drawable.getIntrinsicWidth();
+ int originalHeight = MeasureSpec.getSize(heightMeasureSpec);
+ if (intrHeight < intrWidth) {
+ desiredWidth = MeasureSpec.makeMeasureSpec(
+ originalHeight, desiredMeasureMode);
+ } else {
+ desiredWidth = MeasureSpec.makeMeasureSpec(
+ Math.round((float) originalHeight * intrWidth / intrHeight),
+ desiredMeasureMode);
+ }
+ }
+ }
+ super.onMeasure(desiredWidth, heightMeasureSpec);
+ }
+ };
+ artParams = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ MarginLayoutParamsCompat.setMarginStart(artParams,
+ getContext().getResources().getDimensionPixelSize(R.dimen.media_router_controller_playback_control_horizontal_spacing));
+ } else {
+ artView = new ImageView(getContext()) {
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int desiredHeight = heightMeasureSpec;
+ if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY) {
+ Drawable drawable = getDrawable();
+ if (drawable != null) {
+ int originalWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int intrHeight = drawable.getIntrinsicHeight();
+ int intrWidth = drawable.getIntrinsicWidth();
+ float scale;
+ if (intrHeight*16 > intrWidth*9) {
+ // image is taller than 16:9
+ scale = (float) originalWidth * 9 / 16 / intrHeight;
+ } else {
+ // image is more horizontal than 16:9
+ scale = (float) originalWidth / intrWidth;
+ }
+ desiredHeight = MeasureSpec.makeMeasureSpec(
+ Math.round(intrHeight * scale),
+ MeasureSpec.EXACTLY);
+ }
+ }
+ super.onMeasure(widthMeasureSpec, desiredHeight);
+ }
+ };
+ artParams = new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+ // When we fetch the bitmap, we want to know if we should set a background color or not.
+ artView.setTag(landscape);
+
+ artView.setScaleType(ImageView.ScaleType.FIT_CENTER);
+ artView.setOnClickListener(onClickListener);
+
+ artView.setLayoutParams(artParams);
+ rootView.addView(artView);
+
+ ViewGroup wrapper = rootView;
+
+ if (landscape) {
+ // Here we wrap with a frame layout because we want to set different layout parameters
+ // for landscape orientation.
+ wrapper = new FrameLayout(getContext());
+ wrapper.setLayoutParams(new LinearLayout.LayoutParams(
+ 0,
+ ViewGroup.LayoutParams.WRAP_CONTENT, 1f));
+ rootView.addView(wrapper);
+ rootView.setWeightSum(1f);
+ }
+
+ View playbackControlLayout = View.inflate(getContext(), R.layout.media_router_controller, wrapper);
+
+ titleView = (TextView) playbackControlLayout.findViewById(R.id.mrc_control_title);
+ subtitleView = (TextView) playbackControlLayout.findViewById(R.id.mrc_control_subtitle);
+ playbackControlLayout.findViewById(R.id.mrc_control_title_container).setOnClickListener(onClickListener);
+ playPauseButton = (ImageButton) playbackControlLayout.findViewById(R.id.mrc_control_play_pause);
+ playPauseButton.setOnClickListener(v -> {
+ PlaybackStateCompat state;
+ if (mediaController != null && (state = mediaController.getPlaybackState()) != null) {
+ boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_PLAYING;
+ if (isPlaying) {
+ mediaController.getTransportControls().pause();
+ } else {
+ mediaController.getTransportControls().play();
+ }
+ // Announce the action for accessibility.
+ AccessibilityManager accessibilityManager = (AccessibilityManager)
+ getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (accessibilityManager != null && accessibilityManager.isEnabled()) {
+ AccessibilityEvent event = AccessibilityEvent.obtain(
+ AccessibilityEventCompat.TYPE_ANNOUNCEMENT);
+ event.setPackageName(getContext().getPackageName());
+ event.setClassName(getClass().getName());
+ int resId = isPlaying ?
+ android.support.v7.mediarouter.R.string.mr_controller_pause :
+ android.support.v7.mediarouter.R.string.mr_controller_play;
+ event.getText().add(getContext().getString(resId));
+ accessibilityManager.sendAccessibilityEvent(event);
+ }
+ }
+ });
+
+ viewsCreated = true;
+ updateViews();
+ return rootView;
+ }
+
+ @Override
+ public void onDetachedFromWindow() {
+ if (fetchArtSubscription != null) {
+ fetchArtSubscription.unsubscribe();
+ fetchArtSubscription = null;
+ }
+ super.onDetachedFromWindow();
+ }
+
+ private void updateViews() {
+ if (!viewsCreated || token == null || mediaController == null) {
+ rootView.setVisibility(View.GONE);
+ return;
+ }
+ MediaMetadataCompat metadata = mediaController.getMetadata();
+ MediaDescriptionCompat description = metadata == null ? null : metadata.getDescription();
+ if (description == null) {
+ rootView.setVisibility(View.GONE);
+ return;
+ }
+
+ PlaybackStateCompat state = mediaController.getPlaybackState();
+ MediaRouter.RouteInfo route = MediaRouter.getInstance(getContext()).getSelectedRoute();
+
+ CharSequence title = description.getTitle();
+ boolean hasTitle = !TextUtils.isEmpty(title);
+ CharSequence subtitle = description.getSubtitle();
+ boolean hasSubtitle = !TextUtils.isEmpty(subtitle);
+
+ boolean showTitle = false;
+ boolean showSubtitle = false;
+ if (route.getPresentationDisplay() != null &&
+ route.getPresentationDisplay().getDisplayId() != MediaRouter.RouteInfo.PRESENTATION_DISPLAY_ID_NONE) {
+ // The user is currently casting screen.
+ titleView.setText(android.support.v7.mediarouter.R.string.mr_controller_casting_screen);
+ showTitle = true;
+ } else if (state == null || state.getState() == PlaybackStateCompat.STATE_NONE) {
+ // Show "No media selected" as we don't yet know the playback state.
+ // (Only exception is bluetooth where we don't show anything.)
+ if (!route.isBluetooth()) {
+ titleView.setText(android.support.v7.mediarouter.R.string.mr_controller_no_media_selected);
+ showTitle = true;
+ }
+ } else if (!hasTitle && !hasSubtitle) {
+ titleView.setText(android.support.v7.mediarouter.R.string.mr_controller_no_info_available);
+ showTitle = true;
+ } else {
+ if (hasTitle) {
+ titleView.setText(title);
+ showTitle = true;
+ }
+ if (hasSubtitle) {
+ subtitleView.setText(subtitle);
+ showSubtitle = true;
+ }
+ }
+ if (showSubtitle) {
+ titleView.setSingleLine();
+ } else {
+ titleView.setMaxLines(2);
+ }
+ titleView.setVisibility(showTitle ? View.VISIBLE : View.GONE);
+ subtitleView.setVisibility(showSubtitle ? View.VISIBLE : View.GONE);
+
+ updateState();
+
+ if(rootView.getVisibility() != View.VISIBLE) {
+ artView.setVisibility(View.GONE);
+ rootView.setVisibility(View.VISIBLE);
+ }
+
+ if (fetchArtSubscription != null) {
+ fetchArtSubscription.unsubscribe();
+ }
+
+ fetchArtSubscription = Observable.fromCallable(() -> fetchArt(description))
+ .subscribeOn(Schedulers.newThread())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(result -> {
+ fetchArtSubscription = null;
+ if (artView == null) {
+ return;
+ }
+ if (result.first != null) {
+ if (!((Boolean) artView.getTag())) {
+ artView.setBackgroundColor(result.second);
+ }
+ artView.setImageBitmap(result.first);
+ artView.setVisibility(View.VISIBLE);
+ } else {
+ artView.setVisibility(View.GONE);
+ }
+ }, error -> Log.e(TAG, Log.getStackTraceString(error)));
+
+ }
+
+ private void updateState() {
+ PlaybackStateCompat state;
+ if (!viewsCreated || mediaController == null ||
+ (state = mediaController.getPlaybackState()) == null) {
+ return;
+ }
+ boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_BUFFERING
+ || state.getState() == PlaybackStateCompat.STATE_PLAYING;
+ boolean supportsPlay = (state.getActions() & (PlaybackStateCompat.ACTION_PLAY
+ | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
+ boolean supportsPause = (state.getActions() & (PlaybackStateCompat.ACTION_PAUSE
+ | PlaybackStateCompat.ACTION_PLAY_PAUSE)) != 0;
+ if (isPlaying && supportsPause) {
+ playPauseButton.setVisibility(View.VISIBLE);
+ playPauseButton.setImageResource(getThemeResource(getContext(),
+ android.support.v7.mediarouter.R.attr.mediaRoutePauseDrawable));
+ playPauseButton.setContentDescription(getContext().getResources()
+ .getText(android.support.v7.mediarouter.R.string.mr_controller_pause));
+ } else if (!isPlaying && supportsPlay) {
+ playPauseButton.setVisibility(View.VISIBLE);
+ playPauseButton.setImageResource(getThemeResource(getContext(),
+ android.support.v7.mediarouter.R.attr.mediaRoutePlayDrawable));
+ playPauseButton.setContentDescription(getContext().getResources()
+ .getText(android.support.v7.mediarouter.R.string.mr_controller_play));
+ } else {
+ playPauseButton.setVisibility(View.GONE);
+ }
+ }
+
+ private static int getThemeResource(Context context, int attr) {
+ TypedValue value = new TypedValue();
+ return context.getTheme().resolveAttribute(attr, value, true) ? value.resourceId : 0;
+ }
+
+ private Pair<Bitmap, Integer> fetchArt(@NonNull MediaDescriptionCompat description) {
+ Bitmap iconBitmap = description.getIconBitmap();
+ Uri iconUri = description.getIconUri();
+ Bitmap art = null;
+ if (iconBitmap != null) {
+ art = iconBitmap;
+ } else if (iconUri != null) {
+ try {
+ art = Glide.with(getContext().getApplicationContext())
+ .load(iconUri.toString())
+ .asBitmap()
+ .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY)
+ .into(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL)
+ .get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Image art load failed", e);
+ }
+ }
+ int backgroundColor = 0;
+ if (art != null && art.getWidth()*9 < art.getHeight()*16) {
+ // Portrait art requires dominant color as background color.
+ Palette palette = new Palette.Builder(art).maximumColorCount(1).generate();
+ backgroundColor = palette.getSwatches().isEmpty()
+ ? 0 : palette.getSwatches().get(0).getRgb();
+ }
+ return new Pair<>(art, backgroundColor);
+ }
+}
diff --git a/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java b/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java
new file mode 100644
index 000000000..a960ec998
--- /dev/null
+++ b/app/src/play/java/de/danoeh/antennapod/fragment/CustomMRControllerDialogFragment.java
@@ -0,0 +1,15 @@
+package de.danoeh.antennapod.fragment;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.app.MediaRouteControllerDialog;
+import android.support.v7.app.MediaRouteControllerDialogFragment;
+
+import de.danoeh.antennapod.dialog.CustomMRControllerDialog;
+
+public class CustomMRControllerDialogFragment extends MediaRouteControllerDialogFragment {
+ @Override
+ public MediaRouteControllerDialog onCreateControllerDialog(Context context, Bundle savedInstanceState) {
+ return new CustomMRControllerDialog(context);
+ }
+}
diff --git a/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java b/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java
new file mode 100644
index 000000000..b9b4e891f
--- /dev/null
+++ b/app/src/play/java/de/danoeh/antennapod/preferences/PreferenceControllerFlavorHelper.java
@@ -0,0 +1,31 @@
+package de.danoeh.antennapod.preferences;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GoogleApiAvailability;
+
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+
+/**
+ * Implements functions from PreferenceController that are flavor dependent.
+ */
+public class PreferenceControllerFlavorHelper {
+
+ static void setupFlavoredUI(PreferenceController.PreferenceUI ui) {
+ //checks whether Google Play Services is installed on the device (condition necessary for Cast support)
+ ui.findPreference(UserPreferences.PREF_CAST_ENABLED).setOnPreferenceChangeListener((preference, o) -> {
+ if (o instanceof Boolean && ((Boolean) o)) {
+ final int googlePlayServicesCheck = GoogleApiAvailability.getInstance()
+ .isGooglePlayServicesAvailable(ui.getActivity());
+ if (googlePlayServicesCheck == ConnectionResult.SUCCESS) {
+ return true;
+ } else {
+ GoogleApiAvailability.getInstance()
+ .getErrorDialog(ui.getActivity(), googlePlayServicesCheck, 0)
+ .show();
+ return false;
+ }
+ }
+ return true;
+ });
+ }
+}