From ff774a18700337d31071cfe307f11ecff233e5e1 Mon Sep 17 00:00:00 2001 From: Hanif Shersy Date: Sun, 1 May 2022 02:06:43 +1000 Subject: [PATCH 1/9] Fix persistent hover overlay when mouse connected --- .../fragments/detail/VideoDetailFragment.java | 38 ++++++++++----- .../org/schabi/newpipe/util/DeviceUtils.java | 48 +++++++++++++++++++ 2 files changed, 73 insertions(+), 13 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 bf84c7325..6ee02a7d5 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 @@ -639,19 +639,7 @@ public final class VideoDetailFragment ? View.VISIBLE : View.GONE ); - - if (DeviceUtils.isTv(getContext())) { - // remove ripple effects from detail controls - final int transparent = ContextCompat.getColor(requireContext(), - R.color.transparent_background_color); - binding.detailControlsPlaylistAppend.setBackgroundColor(transparent); - binding.detailControlsBackground.setBackgroundColor(transparent); - binding.detailControlsPopup.setBackgroundColor(transparent); - binding.detailControlsDownload.setBackgroundColor(transparent); - binding.detailControlsShare.setBackgroundColor(transparent); - binding.detailControlsOpenInBrowser.setBackgroundColor(transparent); - binding.detailControlsPlayWithKodi.setBackgroundColor(transparent); - } + accommodateForTvAndDesktopMode(); } @Override @@ -2106,6 +2094,30 @@ public final class VideoDetailFragment } } + /** + * Make changes to the UI to accommodate for better usability on bigger screens such as TVs + * or in Android's desktop mode (DeX etc.) + */ + private void accommodateForTvAndDesktopMode() { + if (DeviceUtils.isTv(getContext())) { + // remove ripple effects from detail controls + final int transparent = ContextCompat.getColor(requireContext(), + R.color.transparent_background_color); + binding.detailControlsPlaylistAppend.setBackgroundColor(transparent); + binding.detailControlsBackground.setBackgroundColor(transparent); + binding.detailControlsPopup.setBackgroundColor(transparent); + binding.detailControlsDownload.setBackgroundColor(transparent); + binding.detailControlsShare.setBackgroundColor(transparent); + binding.detailControlsOpenInBrowser.setBackgroundColor(transparent); + binding.detailControlsPlayWithKodi.setBackgroundColor(transparent); + } + if (DeviceUtils.isDesktopMode(getContext())) { + // Remove the "hover" overlay (since it is visible on all mouse events and interferes + // with the video content being played) + binding.detailThumbnailRootLayout.setForeground(null); + } + } + private void checkLandscape() { if ((!player.isPlaying() && player.getPlayQueue() != playQueue) || player.getPlayQueue() == null) { diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 3c20dc04b..d42926788 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -1,5 +1,6 @@ package org.schabi.newpipe.util; +import android.annotation.SuppressLint; import android.app.UiModeManager; import android.content.Context; import android.content.pm.PackageManager; @@ -22,6 +23,9 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.App; import org.schabi.newpipe.R; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + public final class DeviceUtils { private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv"; @@ -84,6 +88,50 @@ public final class DeviceUtils { return DeviceUtils.isTV; } + public static boolean isDesktopMode(final Context context) { + final boolean isDesktopMode = ContextCompat.getSystemService(context, UiModeManager.class) + .getCurrentModeType() == Configuration.UI_MODE_TYPE_DESK; + + // DeX check for standalone and multi-window mode + boolean isDeXMode = false; + try { + final Configuration config = context.getResources().getConfiguration(); + final Class configClass = config.getClass(); + final int semDesktopModeEnabledConst = + configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass); + final int currentMode = + configClass.getField("semDesktopModeEnabled").getInt(config); + if (semDesktopModeEnabledConst == currentMode) { + isDeXMode = true; + } + } catch (final NoSuchFieldException | IllegalAccessException e) { + // empty + } + @SuppressLint("WrongConstant") final Object desktopModeManager = context + .getApplicationContext() + .getSystemService("desktopmode"); + if (desktopModeManager != null) { + try { + final Method getDesktopModeStateMethod = desktopModeManager.getClass() + .getDeclaredMethod("getDesktopModeState"); + final Object desktopModeState = getDesktopModeStateMethod + .invoke(desktopModeManager); + final Class desktopModeStateClass = desktopModeState.getClass(); + final Method getEnabledMethod = desktopModeStateClass + .getDeclaredMethod("getEnabled"); + final int enabled = (int) getEnabledMethod.invoke(desktopModeState); + final boolean isEnabled = enabled == desktopModeStateClass + .getDeclaredField("ENABLED").getInt(desktopModeStateClass); + + isDeXMode = isEnabled; + } catch (NoSuchFieldException | NoSuchMethodException + | IllegalAccessException | InvocationTargetException e) { + // Device does not support DeX 3.0 + } + } + return isDesktopMode || isDeXMode; + } + public static boolean isTablet(@NonNull final Context context) { final String tabletModeSetting = PreferenceManager.getDefaultSharedPreferences(context) .getString(context.getString(R.string.tablet_mode_key), ""); From cfda073aa572a06a0c2914caf12551d5e0c663e2 Mon Sep 17 00:00:00 2001 From: Hanif Shersy Date: Mon, 2 May 2022 12:23:29 +1000 Subject: [PATCH 2/9] Fix DeX mode check --- .../main/java/org/schabi/newpipe/util/DeviceUtils.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index d42926788..102541d64 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -120,10 +120,10 @@ public final class DeviceUtils { final Method getEnabledMethod = desktopModeStateClass .getDeclaredMethod("getEnabled"); final int enabled = (int) getEnabledMethod.invoke(desktopModeState); - final boolean isEnabled = enabled == desktopModeStateClass - .getDeclaredField("ENABLED").getInt(desktopModeStateClass); - - isDeXMode = isEnabled; + if (enabled == desktopModeStateClass + .getDeclaredField("ENABLED").getInt(desktopModeStateClass)) { + isDeXMode = true; + } } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { // Device does not support DeX 3.0 From 5e2ef7ff0d3c95df191e857ab329c67b8a77f9db Mon Sep 17 00:00:00 2001 From: Hanif Shersy Date: Tue, 3 May 2022 09:49:11 +1000 Subject: [PATCH 3/9] Address review comments --- .../java/org/schabi/newpipe/util/DeviceUtils.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 102541d64..c4d3a7bbf 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -89,11 +89,11 @@ public final class DeviceUtils { } public static boolean isDesktopMode(final Context context) { - final boolean isDesktopMode = ContextCompat.getSystemService(context, UiModeManager.class) - .getCurrentModeType() == Configuration.UI_MODE_TYPE_DESK; - + if (ContextCompat.getSystemService(context, UiModeManager.class) + .getCurrentModeType() == Configuration.UI_MODE_TYPE_DESK) { + return true; + } // DeX check for standalone and multi-window mode - boolean isDeXMode = false; try { final Configuration config = context.getResources().getConfiguration(); final Class configClass = config.getClass(); @@ -102,7 +102,7 @@ public final class DeviceUtils { final int currentMode = configClass.getField("semDesktopModeEnabled").getInt(config); if (semDesktopModeEnabledConst == currentMode) { - isDeXMode = true; + return true; } } catch (final NoSuchFieldException | IllegalAccessException e) { // empty @@ -122,14 +122,14 @@ public final class DeviceUtils { final int enabled = (int) getEnabledMethod.invoke(desktopModeState); if (enabled == desktopModeStateClass .getDeclaredField("ENABLED").getInt(desktopModeStateClass)) { - isDeXMode = true; + return true; } } catch (NoSuchFieldException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { // Device does not support DeX 3.0 } } - return isDesktopMode || isDeXMode; + return false; } public static boolean isTablet(@NonNull final Context context) { From a1773d166fcd7c71c03e0a59390246d2509a01c3 Mon Sep 17 00:00:00 2001 From: Hanif Shersy Date: Tue, 3 May 2022 17:46:34 +1000 Subject: [PATCH 4/9] Fix JSDoc checkstyle warning --- .../schabi/newpipe/fragments/detail/VideoDetailFragment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6ee02a7d5..c42db8b32 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 @@ -2096,7 +2096,7 @@ public final class VideoDetailFragment /** * Make changes to the UI to accommodate for better usability on bigger screens such as TVs - * or in Android's desktop mode (DeX etc.) + * or in Android's desktop mode (DeX etc). */ private void accommodateForTvAndDesktopMode() { if (DeviceUtils.isTv(getContext())) { From 88eed6cc2323a5170a2f57b921392087e269551d Mon Sep 17 00:00:00 2001 From: Hanif Shersy Date: Wed, 4 May 2022 18:39:08 +1000 Subject: [PATCH 5/9] Add JSDoc comment and a performance note for `isDesktopMode` --- .../java/org/schabi/newpipe/util/DeviceUtils.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index c4d3a7bbf..081a205ea 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -88,12 +88,19 @@ public final class DeviceUtils { return DeviceUtils.isTV; } + /** + * Checks if the device is in desktop or DeX mode. This function should only + * be invoked once on view load as it is using reflection for the DeX checks. + * @param context the context to use for services and config. + * @return true if the Android device is in desktop mode or using DeX. + */ public static boolean isDesktopMode(final Context context) { if (ContextCompat.getSystemService(context, UiModeManager.class) .getCurrentModeType() == Configuration.UI_MODE_TYPE_DESK) { return true; } - // DeX check for standalone and multi-window mode + // DeX check for standalone and multi-window mode, from: + // https://developer.samsung.com/samsung-dex/modify-optimizing.html try { final Configuration config = context.getResources().getConfiguration(); final Class configClass = config.getClass(); @@ -119,8 +126,8 @@ public final class DeviceUtils { final Class desktopModeStateClass = desktopModeState.getClass(); final Method getEnabledMethod = desktopModeStateClass .getDeclaredMethod("getEnabled"); - final int enabled = (int) getEnabledMethod.invoke(desktopModeState); - if (enabled == desktopModeStateClass + final int enabledStatus = (int) getEnabledMethod.invoke(desktopModeState); + if (enabledStatus == desktopModeStateClass .getDeclaredField("ENABLED").getInt(desktopModeStateClass)) { return true; } From 9e5c68c575bff4d0a0014d0a06b517b0d95213cf Mon Sep 17 00:00:00 2001 From: cybersphinx Date: Mon, 11 Jul 2022 22:48:30 +0200 Subject: [PATCH 6/9] Add check for input devices with cursor. --- .../org/schabi/newpipe/util/DeviceUtils.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 081a205ea..6ead63df5 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -6,10 +6,12 @@ import android.content.Context; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.graphics.Point; +import android.hardware.input.InputManager; import android.os.BatteryManager; import android.os.Build; import android.provider.Settings; import android.util.TypedValue; +import android.view.InputDevice; import android.view.KeyEvent; import android.view.WindowInsets; import android.view.WindowManager; @@ -26,6 +28,8 @@ import org.schabi.newpipe.R; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import static android.content.Context.INPUT_SERVICE; + public final class DeviceUtils { private static final String AMAZON_FEATURE_FIRE_TV = "amazon.hardware.fire_tv"; @@ -95,6 +99,25 @@ public final class DeviceUtils { * @return true if the Android device is in desktop mode or using DeX. */ public static boolean isDesktopMode(final Context context) { + // Adapted from https://stackoverflow.com/a/64615568 + // to check for all devices that have an active cursor + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + final InputManager im = (InputManager) context.getSystemService(INPUT_SERVICE); + for (final int id : im.getInputDeviceIds()) { + final InputDevice inputDevice = im.getInputDevice(id); + if ( + inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS) + || inputDevice.supportsSource(InputDevice.SOURCE_MOUSE) + || inputDevice.supportsSource(InputDevice.SOURCE_STYLUS) + || inputDevice.supportsSource(InputDevice.SOURCE_TOUCHPAD) + || inputDevice.supportsSource(InputDevice.SOURCE_TRACKBALL) + ) { + return true; + } + } + return false; + } + if (ContextCompat.getSystemService(context, UiModeManager.class) .getCurrentModeType() == Configuration.UI_MODE_TYPE_DESK) { return true; From d2b6bda7a2e0a6064cad66594658f9c49dfc4e58 Mon Sep 17 00:00:00 2001 From: cybersphinx Date: Sat, 16 Jul 2022 12:45:56 +0200 Subject: [PATCH 7/9] Remove errant return. --- app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 6ead63df5..36ba33633 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -115,7 +115,6 @@ public final class DeviceUtils { return true; } } - return false; } if (ContextCompat.getSystemService(context, UiModeManager.class) From c38f1505620170545799d0f55db84e99e6896090 Mon Sep 17 00:00:00 2001 From: cybersphinx Date: Sat, 16 Jul 2022 20:56:24 +0200 Subject: [PATCH 8/9] Remove now obsolete API check. --- .../org/schabi/newpipe/util/DeviceUtils.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index 36ba33633..d96721b0d 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -100,20 +100,18 @@ public final class DeviceUtils { */ public static boolean isDesktopMode(final Context context) { // Adapted from https://stackoverflow.com/a/64615568 - // to check for all devices that have an active cursor - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - final InputManager im = (InputManager) context.getSystemService(INPUT_SERVICE); - for (final int id : im.getInputDeviceIds()) { - final InputDevice inputDevice = im.getInputDevice(id); - if ( - inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS) - || inputDevice.supportsSource(InputDevice.SOURCE_MOUSE) - || inputDevice.supportsSource(InputDevice.SOURCE_STYLUS) - || inputDevice.supportsSource(InputDevice.SOURCE_TOUCHPAD) - || inputDevice.supportsSource(InputDevice.SOURCE_TRACKBALL) - ) { - return true; - } + // to check for all input devices that have an active cursor + final InputManager im = (InputManager) context.getSystemService(INPUT_SERVICE); + for (final int id : im.getInputDeviceIds()) { + final InputDevice inputDevice = im.getInputDevice(id); + if ( + inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS) + || inputDevice.supportsSource(InputDevice.SOURCE_MOUSE) + || inputDevice.supportsSource(InputDevice.SOURCE_STYLUS) + || inputDevice.supportsSource(InputDevice.SOURCE_TOUCHPAD) + || inputDevice.supportsSource(InputDevice.SOURCE_TRACKBALL) + ) { + return true; } } From abf1cc536dc41523ebb4a42c31e99d8cb9562870 Mon Sep 17 00:00:00 2001 From: AudricV <74829229+AudricV@users.noreply.github.com> Date: Wed, 9 Nov 2022 16:22:21 +0100 Subject: [PATCH 9/9] Improve code of DeviceUtils.isDesktopMode - Avoid NullPointerException crashes if there is no UiModeManager or desktop system service mode - Use final for every exception - Suppress missing fields warnings - Add missing NonNull annotation --- .../org/schabi/newpipe/util/DeviceUtils.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java index d96721b0d..46ab6da51 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -25,7 +25,6 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.App; import org.schabi.newpipe.R; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static android.content.Context.INPUT_SERVICE; @@ -98,27 +97,29 @@ public final class DeviceUtils { * @param context the context to use for services and config. * @return true if the Android device is in desktop mode or using DeX. */ - public static boolean isDesktopMode(final Context context) { + @SuppressWarnings("JavaReflectionMemberAccess") + public static boolean isDesktopMode(@NonNull final Context context) { // Adapted from https://stackoverflow.com/a/64615568 // to check for all input devices that have an active cursor final InputManager im = (InputManager) context.getSystemService(INPUT_SERVICE); for (final int id : im.getInputDeviceIds()) { final InputDevice inputDevice = im.getInputDevice(id); - if ( - inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS) - || inputDevice.supportsSource(InputDevice.SOURCE_MOUSE) - || inputDevice.supportsSource(InputDevice.SOURCE_STYLUS) - || inputDevice.supportsSource(InputDevice.SOURCE_TOUCHPAD) - || inputDevice.supportsSource(InputDevice.SOURCE_TRACKBALL) - ) { + if (inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS) + || inputDevice.supportsSource(InputDevice.SOURCE_MOUSE) + || inputDevice.supportsSource(InputDevice.SOURCE_STYLUS) + || inputDevice.supportsSource(InputDevice.SOURCE_TOUCHPAD) + || inputDevice.supportsSource(InputDevice.SOURCE_TRACKBALL)) { return true; } } - if (ContextCompat.getSystemService(context, UiModeManager.class) - .getCurrentModeType() == Configuration.UI_MODE_TYPE_DESK) { + final UiModeManager uiModeManager = + ContextCompat.getSystemService(context, UiModeManager.class); + if (uiModeManager != null + && uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_DESK) { return true; } + // DeX check for standalone and multi-window mode, from: // https://developer.samsung.com/samsung-dex/modify-optimizing.html try { @@ -131,12 +132,14 @@ public final class DeviceUtils { if (semDesktopModeEnabledConst == currentMode) { return true; } - } catch (final NoSuchFieldException | IllegalAccessException e) { - // empty + } catch (final NoSuchFieldException | IllegalAccessException ignored) { + // Device doesn't seem to support DeX } + @SuppressLint("WrongConstant") final Object desktopModeManager = context .getApplicationContext() .getSystemService("desktopmode"); + if (desktopModeManager != null) { try { final Method getDesktopModeStateMethod = desktopModeManager.getClass() @@ -151,11 +154,12 @@ public final class DeviceUtils { .getDeclaredField("ENABLED").getInt(desktopModeStateClass)) { return true; } - } catch (NoSuchFieldException | NoSuchMethodException - | IllegalAccessException | InvocationTargetException e) { - // Device does not support DeX 3.0 + } catch (final Exception ignored) { + // Device does not support DeX 3.0 or something went wrong when trying to determine + // if it supports this feature } } + return false; }