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 580585d4e..7078ecffb 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 @@ -642,19 +642,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 @@ -2118,6 +2106,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..46ab6da51 100644 --- a/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java +++ b/app/src/main/java/org/schabi/newpipe/util/DeviceUtils.java @@ -1,14 +1,17 @@ package org.schabi.newpipe.util; +import android.annotation.SuppressLint; import android.app.UiModeManager; 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; @@ -22,6 +25,10 @@ import androidx.preference.PreferenceManager; import org.schabi.newpipe.App; import org.schabi.newpipe.R; +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"; @@ -84,6 +91,78 @@ 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. + */ + @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)) { + return true; + } + } + + 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 { + 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) { + return true; + } + } 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() + .getDeclaredMethod("getDesktopModeState"); + final Object desktopModeState = getDesktopModeStateMethod + .invoke(desktopModeManager); + final Class desktopModeStateClass = desktopModeState.getClass(); + final Method getEnabledMethod = desktopModeStateClass + .getDeclaredMethod("getEnabled"); + final int enabledStatus = (int) getEnabledMethod.invoke(desktopModeState); + if (enabledStatus == desktopModeStateClass + .getDeclaredField("ENABLED").getInt(desktopModeStateClass)) { + return true; + } + } 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; + } + public static boolean isTablet(@NonNull final Context context) { final String tabletModeSetting = PreferenceManager.getDefaultSharedPreferences(context) .getString(context.getString(R.string.tablet_mode_key), "");