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" >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+