From 08db1d59e57d5c0db46911e2894b85b7c1de613e Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Sat, 25 Jul 2020 04:14:29 +0300 Subject: [PATCH] Android TV: ability to select all buttons in the main player, as well as in the main fragment --- .../fragments/detail/VideoDetailFragment.java | 37 ++++- .../newpipe/player/VideoPlayerImpl.java | 28 ++-- .../newpipe/views/FocusOverlayView.java | 15 +- .../activity_main_player.xml | 153 +++++++++--------- .../fragment_video_detail.xml | 3 + .../main/res/layout/activity_main_player.xml | 148 +++++++++-------- 6 files changed, 216 insertions(+), 168 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index be7d0316a..1d83094dc 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -34,6 +34,7 @@ import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.PlaybackParameters; @@ -2154,6 +2155,30 @@ public class VideoDetailFragment // Bottom mini player //////////////////////////////////////////////////////////////////////////*/ + /** + * That's for Android TV support. Move focus from main fragment to the player or back + * based on what is currently selected + * @param toMain if true than the main fragment will be focused or the player otherwise + * */ + private void moveFocusToMainFragment(final boolean toMain) { + final ViewGroup mainFragment = requireActivity().findViewById(R.id.fragment_holder); + // Hamburger button steels a focus even under bottomSheet + final Toolbar toolbar = requireActivity().findViewById(R.id.toolbar); + final int afterDescendants = ViewGroup.FOCUS_AFTER_DESCENDANTS; + final int blockDescendants = ViewGroup.FOCUS_BLOCK_DESCENDANTS; + if (toMain) { + mainFragment.setDescendantFocusability(afterDescendants); + toolbar.setDescendantFocusability(afterDescendants); + ((ViewGroup) requireView()).setDescendantFocusability(blockDescendants); + mainFragment.requestFocus(); + } else { + mainFragment.setDescendantFocusability(blockDescendants); + toolbar.setDescendantFocusability(blockDescendants); + ((ViewGroup) requireView()).setDescendantFocusability(afterDescendants); + thumbnailBackgroundButton.requestFocus(); + } + } + private void setupBottomPlayer() { final CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) appBarLayout.getLayoutParams(); @@ -2177,15 +2202,17 @@ public class VideoDetailFragment @Override public void onStateChanged(@NonNull final View bottomSheet, final int newState) { bottomSheetState = newState; - final ViewGroup mainFragment = requireActivity().findViewById(R.id.fragment_holder); + switch (newState) { case BottomSheetBehavior.STATE_HIDDEN: - mainFragment.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); + moveFocusToMainFragment(true); + bottomSheetBehavior.setPeekHeight(0); cleanUp(); break; case BottomSheetBehavior.STATE_EXPANDED: - mainFragment.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); + moveFocusToMainFragment(false); + bottomSheetBehavior.setPeekHeight(peekHeight); // Disable click because overlay buttons located on top of buttons // from the player @@ -2202,8 +2229,8 @@ public class VideoDetailFragment } break; case BottomSheetBehavior.STATE_COLLAPSED: - mainFragment.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); - mainFragment.requestFocus(); + moveFocusToMainFragment(true); + // Re-enable clicks setOverlayElementsClickable(true); if (player != null) { diff --git a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java index 0c5bfd8f6..8a4f7fdc5 100644 --- a/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java +++ b/app/src/main/java/org/schabi/newpipe/player/VideoPlayerImpl.java @@ -480,7 +480,6 @@ public class VideoPlayerImpl extends VideoPlayer case KeyEvent.KEYCODE_BACK: if (DeviceUtils.isTv(service) && isControlsVisible()) { hideControls(0, 0); - hideSystemUIIfNeeded(); return true; } break; @@ -499,7 +498,9 @@ public class VideoPlayerImpl extends VideoPlayer } if (!isControlsVisible()) { - playPauseButton.requestFocus(); + if (!queueVisible) { + playPauseButton.requestFocus(); + } showControlsThenHide(); showSystemUIPartially(); return true; @@ -805,7 +806,7 @@ public class VideoPlayerImpl extends VideoPlayer if (v.getId() == playPauseButton.getId()) { hideControls(0, 0); } else { - safeHideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); + hideControls(DEFAULT_CONTROLS_DURATION, DEFAULT_CONTROLS_HIDE_TIME); } } }); @@ -830,6 +831,7 @@ public class VideoPlayerImpl extends VideoPlayer updatePlaybackButtons(); getControlsRoot().setVisibility(View.INVISIBLE); + queueLayout.requestFocus(); animateView(queueLayout, SLIDE_AND_ALPHA, true, DEFAULT_CONTROLS_DURATION); @@ -848,6 +850,7 @@ public class VideoPlayerImpl extends VideoPlayer queueLayout.setTranslationY(-queueLayout.getHeight() * 5); }); queueVisible = false; + playPauseButton.requestFocus(); } private void onMoreOptionsClicked() { @@ -1095,7 +1098,9 @@ public class VideoPlayerImpl extends VideoPlayer animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> { playPauseButton.setImageResource(R.drawable.ic_pause_white_24dp); animatePlayButtons(true, 200); - playPauseButton.requestFocus(); + if (!queueVisible) { + playPauseButton.requestFocus(); + } }); updateWindowFlags(ONGOING_PLAYBACK_WINDOW_FLAGS); @@ -1114,7 +1119,9 @@ public class VideoPlayerImpl extends VideoPlayer animateView(playPauseButton, AnimationUtils.Type.SCALE_AND_ALPHA, false, 80, 0, () -> { playPauseButton.setImageResource(R.drawable.ic_play_arrow_white_24dp); animatePlayButtons(true, 200); - playPauseButton.requestFocus(); + if (!queueVisible) { + playPauseButton.requestFocus(); + } }); updateWindowFlags(IDLE_WINDOW_FLAGS); @@ -1401,12 +1408,10 @@ public class VideoPlayerImpl extends VideoPlayer return isFullscreen; } - @Override public void showControlsThenHide() { - if (queueVisible) { - return; + if (DEBUG) { + Log.d(TAG, "showControlsThenHide() called"); } - showOrHideButtons(); showSystemUIPartially(); super.showControlsThenHide(); @@ -1414,10 +1419,9 @@ public class VideoPlayerImpl extends VideoPlayer @Override public void showControls(final long duration) { - if (queueVisible) { - return; + if (DEBUG) { + Log.d(TAG, "showControls() called with: duration = [" + duration + "]"); } - showOrHideButtons(); showSystemUIPartially(); super.showControls(duration); diff --git a/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java b/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java index 1c868f66a..b23f98d79 100644 --- a/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java +++ b/app/src/main/java/org/schabi/newpipe/views/FocusOverlayView.java @@ -38,6 +38,7 @@ import android.view.ViewTreeObserver; import android.view.Window; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.view.WindowCallbackWrapper; @@ -113,7 +114,9 @@ public final class FocusOverlayView extends Drawable implements if (focusedView != null) { focusedView.getGlobalVisibleRect(focusRect); - } else { + } + + if (shouldClearFocusRect(focusedView, focusRect)) { focusRect.setEmpty(); } @@ -184,6 +187,16 @@ public final class FocusOverlayView extends Drawable implements public void setColorFilter(final ColorFilter colorFilter) { } + /* + * When any view in the player looses it's focus (after setVisibility(GONE)) the focus gets + * added to the whole fragment which has a width and height equal to the window frame. + * The easiest way to avoid the unneeded frame is to skip highlighting of rect that is + * equal to the overlayView bounds + * */ + private boolean shouldClearFocusRect(@Nullable final View focusedView, final Rect focusedRect) { + return focusedView == null || focusedRect.equals(getBounds()); + } + public static void setupFocusObserver(final Dialog dialog) { Rect displayRect = new Rect(); diff --git a/app/src/main/res/layout-large-land/activity_main_player.xml b/app/src/main/res/layout-large-land/activity_main_player.xml index 91627f2d4..46edda8b7 100644 --- a/app/src/main/res/layout-large-land/activity_main_player.xml +++ b/app/src/main/res/layout-large-land/activity_main_player.xml @@ -38,81 +38,6 @@ tools:ignore="ContentDescription" tools:visibility="visible"/> - - - - - - - - - - - - - - - @@ -431,6 +358,7 @@ android:paddingBottom="4dp" android:paddingTop="8dp" tools:progress="25" + android:nextFocusDown="@id/screenRotationButton" tools:secondaryProgress="50"/> @@ -522,6 +451,80 @@ + + + + + + + + + + + + + + @@ -602,6 +604,7 @@ android:alpha="0.9" android:paddingLeft="@dimen/video_item_search_padding" android:paddingRight="@dimen/video_item_search_padding" + android:descendantFocusability="blocksDescendants" android:background="?attr/windowBackground" > - - - - - - - - - - - - - - - - + + + + + + + + + + + + + +