From e844c6810b38c8455d4afb3a53e3bf1ce3b385e6 Mon Sep 17 00:00:00 2001 From: Romain Vimont Date: Thu, 23 Nov 2023 09:21:15 +0100 Subject: [PATCH] Keep the display mode daemon process alive On Android 14, a separate process was spawn on every display mode request (to turn the screen off or on). But starting a java process takes time (a few hundred milliseconds), causing a noticeable latency between the request to turn the screen off (MOD+o) or on (MOD+Shift+o) and the actual power mode change. To minimize this latency, keep the process alive between requests, so that only the first one will have to spawn the process. It would be possible to spawn the process in advance, so that even the first request would be immediate, but any problem would impact all Android 14 users even without using the "turn screen off" feature. PR #4446 --- .../genymobile/scrcpy/DisplayPowerMode.java | 85 ++++++++++++------- 1 file changed, 52 insertions(+), 33 deletions(-) diff --git a/server/src/main/java/com/genymobile/scrcpy/DisplayPowerMode.java b/server/src/main/java/com/genymobile/scrcpy/DisplayPowerMode.java index 68bef2b830..3ffadf79a2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/DisplayPowerMode.java +++ b/server/src/main/java/com/genymobile/scrcpy/DisplayPowerMode.java @@ -4,13 +4,19 @@ import android.os.IBinder; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.lang.reflect.Method; /** * On Android 14, the methods used to turn the device screen off have been moved from SurfaceControl (in framework.jar) to DisplayControl (a system * server class). As a consequence, they could not be called directly. See {@url https://github.com/Genymobile/scrcpy/issues/3927}. *

- * Instead, run a separate process with a different classpath and LD_PRELOAD just to set the display power mode. + * Instead, run a separate process with a different classpath and LD_PRELOAD just to set the display power mode. The scrcpy server can request to + * this process to set the display mode by writing the mode (a single byte, the value of one of the SurfaceControl.POWER_MODE_* constants, + * typically 0=off, 2=on) to the process stdin. In return, it receives the status of the request (0=ok, 1=error) on the process stdout. + *

+ * This separate process is started on the first display mode request. *

* Since the client must not block, and calling/joining a process is blocking (this specific one takes a few hundred milliseconds to complete), * this class uses an internal thread to execute the requests asynchronously, and serialize them (so that two successive requests are guaranteed to @@ -23,18 +29,26 @@ public final class DisplayPowerMode { private static final class Proxy implements Runnable { + private Process process; private Thread thread; private int requestedMode = -1; private boolean stopped; - synchronized void requestMode(int mode) { - if (thread == null) { - thread = new Thread(this, "DisplayPowerModeProxy"); - thread.setDaemon(true); - thread.start(); + synchronized boolean requestMode(int mode) { + try { + if (process == null) { + process = executeDisplayPowerModeDaemon(); + thread = new Thread(this, "DisplayPowerModeProxy"); + thread.setDaemon(true); + thread.start(); + } + requestedMode = mode; + notify(); + return true; + } catch (IOException e) { + Ln.e("Could not start display power mode daemon", e); + return false; } - requestedMode = mode; - notify(); } void stopAndJoin() { @@ -60,8 +74,10 @@ void stopAndJoin() { @Override public void run() { try { - int mode; + OutputStream out = process.getOutputStream(); + InputStream in = process.getInputStream(); while (true) { + int mode; synchronized (this) { while (!stopped && requestedMode == -1) { wait(); @@ -76,13 +92,14 @@ public void run() { } try { - Process process = executeSystemServerSetDisplayPowerMode(mode); - int status = process.waitFor(); + out.write(mode); + out.flush(); + int status = in.read(); if (status != 0) { Ln.e("Set display power mode failed remotely: status=" + status); } - } catch (Exception e) { - Ln.e("Failed to execute process", e); + } catch (IOException e) { + Ln.e("Could not request display power mode", e); } } } catch (InterruptedException e) { @@ -96,8 +113,8 @@ private DisplayPowerMode() { } // Called from the scrcpy process - public static void setRemoteDisplayPowerMode(int mode) { - PROXY.requestMode(mode); + public static boolean setRemoteDisplayPowerMode(int mode) { + return PROXY.requestMode(mode); } public static void stopAndJoin() { @@ -105,9 +122,9 @@ public static void stopAndJoin() { } // Called from the proxy thread in the scrcpy process - private static Process executeSystemServerSetDisplayPowerMode(int mode) throws IOException { + private static Process executeDisplayPowerModeDaemon() throws IOException { String[] ldPreloadLibs = {"/system/lib64/libandroid_servers.so"}; - String[] cmd = {"app_process", "/", DisplayPowerMode.class.getName(), String.valueOf(mode)}; + String[] cmd = {"app_process", "/", DisplayPowerMode.class.getName()}; ProcessBuilder builder = new ProcessBuilder(cmd); builder.environment().put("LD_PRELOAD", String.join(" ", ldPreloadLibs)); @@ -139,23 +156,25 @@ public static void main(String... args) { Ln.disableSystemStreams(); Ln.initLogLevel(Ln.Level.DEBUG); - int status = run(args) ? 0 : 1; - System.exit(status); - } - - private static boolean run(String... args) { - if (args.length != 1) { - Ln.e("Exactly one argument expected: the value of one of the SurfaceControl.POWER_MODE_* constants (typically 0 or 2)"); - return false; - } - try { - int mode = Integer.parseInt(args[0]); - setDisplayPowerModeUsingDisplayControl(mode); - return true; - } catch (Throwable e) { - Ln.e("Could not set display power mode", e); - return false; + while (true) { + // Wait for requests + int request = System.in.read(); + if (request == -1) { + // EOF + return; + } + try { + setDisplayPowerModeUsingDisplayControl(request); + System.out.write(0); // ok + } catch (Throwable e) { + Ln.e("Could not set display power mode", e); + System.out.write(1); // error + } + System.out.flush(); + } + } catch (IOException e) { + // Expected when the server is dead } } }