diff --git a/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java b/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java index 02720e42e..916b13a38 100644 --- a/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java +++ b/app/src/play/java/de/danoeh/antennapod/config/CastCallbackImpl.java @@ -1,14 +1,11 @@ package de.danoeh.antennapod.config; -import android.content.Context; -import android.os.Bundle; import android.support.annotation.NonNull; -import android.support.v7.app.MediaRouteControllerDialog; import android.support.v7.app.MediaRouteControllerDialogFragment; import android.support.v7.app.MediaRouteDialogFactory; -import de.danoeh.antennapod.dialog.CustomMRControllerDialog; import de.danoeh.antennapod.core.CastCallbacks; +import de.danoeh.antennapod.fragment.CustomMRControllerDialogFragment; public class CastCallbackImpl implements CastCallbacks { @Override @@ -17,12 +14,7 @@ public class CastCallbackImpl implements CastCallbacks { @NonNull @Override public MediaRouteControllerDialogFragment onCreateControllerDialogFragment() { - return new MediaRouteControllerDialogFragment() { - @Override - public MediaRouteControllerDialog onCreateControllerDialog(Context context, Bundle savedInstanceState) { - return new CustomMRControllerDialog(context); - } - }; + 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 index 009a8c254..93ec4e3f0 100644 --- a/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java +++ b/app/src/play/java/de/danoeh/antennapod/dialog/CustomMRControllerDialog.java @@ -2,6 +2,7 @@ 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; @@ -14,6 +15,7 @@ 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; @@ -104,10 +106,70 @@ public class CustomMRControllerDialog extends MediaRouteControllerDialog { @Override public View onCreateMediaControlView(Bundle savedInstanceState) { - rootView = new LinearLayout(getContext()); - rootView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); - rootView.setOrientation(LinearLayout.VERTICAL); + 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 -> { @@ -124,38 +186,107 @@ public class CustomMRControllerDialog extends MediaRouteControllerDialog { } }; - 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; + 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); + } } - desiredHeight = MeasureSpec.makeMeasureSpec((int) (intrHeight * scale + 0.5f), MeasureSpec.EXACTLY); } + 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); - super.onMeasure(widthMeasureSpec, desiredHeight); - } - }; - artView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT)); artView.setScaleType(ImageView.ScaleType.FIT_CENTER); artView.setOnClickListener(onClickListener); + artView.setLayoutParams(artParams); rootView.addView(artView); - View playbackControlLayout = View.inflate(getContext(), R.layout.media_router_controller, rootView); + + 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); @@ -179,7 +310,8 @@ public class CustomMRControllerDialog extends MediaRouteControllerDialog { 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; + 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); } @@ -270,14 +402,20 @@ public class CustomMRControllerDialog extends MediaRouteControllerDialog { .observeOn(AndroidSchedulers.mainThread()) .subscribe(result -> { fetchArtSubscription = null; + if (artView == null) { + return; + } if (result.first != null) { - artView.setBackgroundColor(result.second); + 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() { 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/res/layout/media_router_controller.xml b/app/src/play/res/layout/media_router_controller.xml index 7d3889db2..659ac0a78 100644 --- a/app/src/play/res/layout/media_router_controller.xml +++ b/app/src/play/res/layout/media_router_controller.xml @@ -1,50 +1,41 @@ - - - + - + + + - - + - - - - - - - + android:textAppearance="?attr/mediaRouteControllerSecondaryTextStyle" + android:singleLine="true" /> + + diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 5e1057716..e3557f5f8 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -29,6 +29,7 @@ import android.support.v4.media.MediaDescriptionCompat; import android.support.v4.media.MediaMetadataCompat; import android.support.v4.media.session.MediaSessionCompat; import android.support.v4.media.session.PlaybackStateCompat; +import android.support.v4.view.InputDeviceCompat; import android.support.v7.app.NotificationCompat; import android.text.TextUtils; import android.util.Log; @@ -41,8 +42,8 @@ import android.widget.Toast; import com.bumptech.glide.Glide; import com.bumptech.glide.request.target.Target; -import java.util.List; import java.util.ArrayList; +import java.util.List; import de.danoeh.antennapod.core.ClientConfig; import de.danoeh.antennapod.core.R; @@ -302,7 +303,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { List queueItems = new ArrayList<>(); try { - for (FeedItem feedItem: taskManager.getQueue()) { + for (FeedItem feedItem : taskManager.getQueue()) { queueItems.add(new MediaSessionCompat.QueueItem(feedItem.getMedia().getMediaItem().getDescription(), feedItem.getId())); } mediaSession.setQueue(queueItems); @@ -345,7 +346,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { } @Override - public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { + public BrowserRoot onGetRoot(@NonNull String clientPackageName, int clientUid, Bundle rootHints) { Log.d(TAG, "OnGetRoot: clientPackageName=" + clientPackageName + "; clientUid=" + clientUid + " ; rootHints=" + rootHints); return new BrowserRoot( @@ -363,10 +364,10 @@ public class PlaybackService extends MediaBrowserServiceCompat { } @Override - public void onLoadChildren(String parentId, - Result> result) { + public void onLoadChildren(@NonNull String parentId, + @NonNull Result> result) { Log.d(TAG, "OnLoadChildren: parentMediaId=" + parentId); - List mediaItems = new ArrayList(); + List mediaItems = new ArrayList<>(); if (parentId.equals(getResources().getString(R.string.app_name))) { // Root List mediaItems.add(createBrowsableMediaItemForRoot()); @@ -415,7 +416,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { if (keycode != -1) { Log.d(TAG, "Received media button event"); handleKeycode(keycode, intent.getIntExtra(MediaButtonReceiver.EXTRA_SOURCE, - InputDevice.SOURCE_CLASS_NONE)); + InputDeviceCompat.SOURCE_CLASS_NONE)); } else if (!flavorHelper.castDisconnect(castDisconnect)) { started = true; boolean stream = intent.getBooleanExtra(EXTRA_SHOULD_STREAM, @@ -1030,6 +1031,9 @@ public class PlaybackService extends MediaBrowserServiceCompat { } } if (!Thread.currentThread().isInterrupted() && started) { + mediaSession.setSessionActivity(PendingIntent.getActivity(this, 0, + PlaybackService.getPlayerActivityIntent(this), + PendingIntent.FLAG_UPDATE_CURRENT)); mediaSession.setMetadata(builder.build()); } }; @@ -1176,8 +1180,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { .setShowActionsInCompactView(compactActionList.toArray()) .setShowCancelButton(true) .setCancelButtonIntent(stopButtonPendingIntent)) - .setVisibility(Notification.VISIBILITY_PUBLIC) - .setColor(Notification.COLOR_DEFAULT); + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + .setColor(NotificationCompat.COLOR_DEFAULT); notification = notificationBuilder.build(); diff --git a/core/src/main/res/values-land/dimens.xml b/core/src/main/res/values-land/dimens.xml new file mode 100644 index 000000000..73b2b2e98 --- /dev/null +++ b/core/src/main/res/values-land/dimens.xml @@ -0,0 +1,4 @@ + + + @dimen/media_router_controller_playback_control_horizontal_spacing + diff --git a/core/src/main/res/values/dimens.xml b/core/src/main/res/values/dimens.xml index 4f549efd7..01dce6a1c 100644 --- a/core/src/main/res/values/dimens.xml +++ b/core/src/main/res/values/dimens.xml @@ -36,4 +36,9 @@ 48dp + 16dp + 12dp + 24dp + 8dp + diff --git a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java index aef3e3c2b..93431d466 100644 --- a/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java +++ b/core/src/play/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceFlavorHelper.java @@ -75,9 +75,8 @@ public class PlaybackServiceFlavorHelper { boolean castDisconnect(boolean castDisconnect) { if (castDisconnect) { castManager.disconnect(); - return true; } - return false; + return castDisconnect; } boolean onMediaPlayerInfo(Context context, int code, @StringRes int resourceId) {