Compare commits

..

1 Commits

Author SHA1 Message Date
Romain Vimont
67fa766f09 Fix turn screen off on Android 14
On Android 14, execute a separate process with a different classpath and
LD_PRELOAD to execute the methods required to turn the device screen
off.

Fixes #3927 <https://github.com/Genymobile/scrcpy/issues/3927>
Refs #3927 comment <https://github.com/Genymobile/scrcpy/issues/3927#issuecomment-1790031953>

Co-authored-by: Simon Chan <1330321+yume-chan@users.noreply.github.com>
2023-11-21 17:59:45 +01:00
2 changed files with 37 additions and 60 deletions

View File

@@ -4,20 +4,13 @@ import android.annotation.SuppressLint;
import android.os.IBinder;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
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}.
* <p>
* 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.
* <p>
* This separate process is started on the first display mode request.
* Instead, run a separate process with a different classpath and LD_PRELOAD just to set the display power mode.
* <p>
* 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
@@ -30,26 +23,18 @@ 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 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;
synchronized void requestMode(int mode) {
if (thread == null) {
thread = new Thread(this, "DisplayPowerModeProxy");
thread.setDaemon(true);
thread.start();
}
requestedMode = mode;
notify();
}
void stopAndJoin() {
@@ -75,10 +60,8 @@ public final class DisplayPowerMode {
@Override
public void run() {
try {
OutputStream out = process.getOutputStream();
InputStream in = process.getInputStream();
int mode;
while (true) {
int mode;
synchronized (this) {
while (!stopped && requestedMode == -1) {
wait();
@@ -93,14 +76,13 @@ public final class DisplayPowerMode {
}
try {
out.write(mode);
out.flush();
int status = in.read();
Process process = executeSystemServerSetDisplayPowerMode(mode);
int status = process.waitFor();
if (status != 0) {
Ln.e("Set display power mode failed remotely: status=" + status);
}
} catch (IOException e) {
Ln.e("Could not request display power mode", e);
} catch (Exception e) {
Ln.e("Failed to execute process", e);
}
}
} catch (InterruptedException e) {
@@ -114,8 +96,8 @@ public final class DisplayPowerMode {
}
// Called from the scrcpy process
public static boolean setRemoteDisplayPowerMode(int mode) {
return PROXY.requestMode(mode);
public static void setRemoteDisplayPowerMode(int mode) {
PROXY.requestMode(mode);
}
public static void stopAndJoin() {
@@ -123,9 +105,9 @@ public final class DisplayPowerMode {
}
// Called from the proxy thread in the scrcpy process
private static Process executeDisplayPowerModeDaemon() throws IOException {
private static Process executeSystemServerSetDisplayPowerMode(int mode) throws IOException {
String[] ldPreloadLibs = {"/system/lib64/libandroid_servers.so"};
String[] cmd = {"app_process", "/", DisplayPowerMode.class.getName()};
String[] cmd = {"app_process", "/", DisplayPowerMode.class.getName(), String.valueOf(mode)};
ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("LD_PRELOAD", String.join(" ", ldPreloadLibs));
@@ -154,31 +136,26 @@ public final class DisplayPowerMode {
}
public static void main(String... args) {
// This process uses stdin/stdout to communicate with the caller, make sure nothing else writes to stdout
// (and never use Ln methods other than Ln.w() and Ln.e()).
PrintStream nullStream = new PrintStream(new Ln.NullOutputStream());
System.setOut(nullStream);
PrintStream stdout = Ln.CONSOLE_OUT;
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 {
while (true) {
// Wait for requests
int request = System.in.read();
if (request == -1) {
// EOF
return;
}
try {
setDisplayPowerModeUsingDisplayControl(request);
stdout.write(0); // ok
} catch (Throwable e) {
Ln.e("Could not set display power mode", e);
stdout.write(1); // error
}
stdout.flush();
}
} catch (IOException e) {
// Expected when the server is dead
int mode = Integer.parseInt(args[0]);
setDisplayPowerModeUsingDisplayControl(mode);
return true;
} catch (Throwable e) {
Ln.e("Could not set display power mode", e);
return false;
}
}
}

View File

@@ -16,8 +16,8 @@ public final class Ln {
private static final String TAG = "scrcpy";
private static final String PREFIX = "[server] ";
public static final PrintStream CONSOLE_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out));
public static final PrintStream CONSOLE_ERR = new PrintStream(new FileOutputStream(FileDescriptor.err));
private static final PrintStream CONSOLE_OUT = new PrintStream(new FileOutputStream(FileDescriptor.out));
private static final PrintStream CONSOLE_ERR = new PrintStream(new FileOutputStream(FileDescriptor.err));
enum Level {
VERBOSE, DEBUG, INFO, WARN, ERROR