From 5d4b8a7e6d18ee0e19a764945dc67efbd46f3e97 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Nov 2023 23:50:41 +0100 Subject: [PATCH] Fix turn screen off on Android 14 On Android 14, the methods to access the display have been moved to DisplayControl, which is not in the core framework. Use a specific ClassLoader to access this class and its native dependencies. Fixes #3927 Refs #3927 comment Refs #4446 comment PR #4456 Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com> Signed-off-by: Romain Vimont --- .../java/com/genymobile/scrcpy/Device.java | 10 ++- .../scrcpy/wrappers/DisplayControl.java | 80 +++++++++++++++++++ .../scrcpy/wrappers/SurfaceControl.java | 9 +++ 3 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 9fbe923906..b51ad8d377 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -1,6 +1,7 @@ package com.genymobile.scrcpy; import com.genymobile.scrcpy.wrappers.ClipboardManager; +import com.genymobile.scrcpy.wrappers.DisplayControl; import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; @@ -315,8 +316,12 @@ public boolean setClipboardText(String text) { */ public static boolean setScreenPowerMode(int mode) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + // On Android 14, these internal methods have been moved to DisplayControl + boolean useDisplayControl = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && !SurfaceControl.hasPhysicalDisplayIdsMethod(); + // Change the power mode for all physical displays - long[] physicalDisplayIds = SurfaceControl.getPhysicalDisplayIds(); + long[] physicalDisplayIds = useDisplayControl ? DisplayControl.getPhysicalDisplayIds() : SurfaceControl.getPhysicalDisplayIds(); if (physicalDisplayIds == null) { Ln.e("Could not get physical display ids"); return false; @@ -324,7 +329,8 @@ public static boolean setScreenPowerMode(int mode) { boolean allOk = true; for (long physicalDisplayId : physicalDisplayIds) { - IBinder binder = SurfaceControl.getPhysicalDisplayToken(physicalDisplayId); + IBinder binder = useDisplayControl ? DisplayControl.getPhysicalDisplayToken( + physicalDisplayId) : SurfaceControl.getPhysicalDisplayToken(physicalDisplayId); allOk &= SurfaceControl.setDisplayPowerMode(binder, mode); } return allOk; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java new file mode 100644 index 0000000000..4e19beb9a0 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayControl.java @@ -0,0 +1,80 @@ +package com.genymobile.scrcpy.wrappers; + +import com.genymobile.scrcpy.Ln; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.os.Build; +import android.os.IBinder; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +@SuppressLint({"PrivateApi", "SoonBlockedPrivateApi", "BlockedPrivateApi"}) +@TargetApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public final class DisplayControl { + + private static final Class CLASS; + + static { + Class displayControlClass = null; + try { + Class classLoaderFactoryClass = Class.forName("com.android.internal.os.ClassLoaderFactory"); + Method createClassLoaderMethod = classLoaderFactoryClass.getDeclaredMethod("createClassLoader", String.class, String.class, String.class, + ClassLoader.class, int.class, boolean.class, String.class); + ClassLoader classLoader = (ClassLoader) createClassLoaderMethod.invoke(null, "/system/framework/services.jar", null, null, + ClassLoader.getSystemClassLoader(), 0, true, null); + + displayControlClass = classLoader.loadClass("com.android.server.display.DisplayControl"); + + Method loadMethod = Runtime.class.getDeclaredMethod("loadLibrary0", Class.class, String.class); + loadMethod.setAccessible(true); + loadMethod.invoke(Runtime.getRuntime(), displayControlClass, "android_servers"); + } catch (Throwable e) { + Ln.e("Could not initialize DisplayControl", e); + // Do not throw an exception here, the methods will fail when they are called + } + CLASS = displayControlClass; + } + + private static Method getPhysicalDisplayTokenMethod; + private static Method getPhysicalDisplayIdsMethod; + + private DisplayControl() { + // only static methods + } + + private static Method getGetPhysicalDisplayTokenMethod() throws NoSuchMethodException { + if (getPhysicalDisplayTokenMethod == null) { + getPhysicalDisplayTokenMethod = CLASS.getMethod("getPhysicalDisplayToken", long.class); + } + return getPhysicalDisplayTokenMethod; + } + + public static IBinder getPhysicalDisplayToken(long physicalDisplayId) { + try { + Method method = getGetPhysicalDisplayTokenMethod(); + return (IBinder) method.invoke(null, physicalDisplayId); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + return null; + } + } + + private static Method getGetPhysicalDisplayIdsMethod() throws NoSuchMethodException { + if (getPhysicalDisplayIdsMethod == null) { + getPhysicalDisplayIdsMethod = CLASS.getMethod("getPhysicalDisplayIds"); + } + return getPhysicalDisplayIdsMethod; + } + + public static long[] getPhysicalDisplayIds() { + try { + Method method = getGetPhysicalDisplayIdsMethod(); + return (long[]) method.invoke(null); + } catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) { + Ln.e("Could not invoke method", e); + return null; + } + } +} diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java index 595ee6d477..98259e7ff5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/SurfaceControl.java @@ -139,6 +139,15 @@ private static Method getGetPhysicalDisplayIdsMethod() throws NoSuchMethodExcept return getPhysicalDisplayIdsMethod; } + public static boolean hasPhysicalDisplayIdsMethod() { + try { + getGetPhysicalDisplayIdsMethod(); + return true; + } catch (NoSuchMethodException e) { + return false; + } + } + public static long[] getPhysicalDisplayIds() { try { Method method = getGetPhysicalDisplayIdsMethod();