Refactor clean up configuration to simplify

All options were configured dynamically by sending a single byte to an
output stream. But in practice, only the power mode must be changed
dynamically, the others are configured once on start.

For simplicity, pass the value of static options as command line
arguments, and handle dynamic options in a loop only from a separate
thread once the clean up process is started.

This will allow to easily add cleanup options with values which do not
fit in 1 byte.

Also handle the clean up thread (and the loading of initial settings
values) from the CleanUp class, to expose a simpler clean up API.

Refs 9efa162949c2a3e3e42564862ff390700270394d
PR #5447 <https://github.com/Genymobile/scrcpy/pull/5447>
This commit is contained in:
Romain Vimont 2024-11-03 22:34:02 +01:00
parent 5936167ff7
commit d3db9c4065
2 changed files with 106 additions and 124 deletions

View File

@ -5,6 +5,8 @@ import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.Settings; import com.genymobile.scrcpy.util.Settings;
import com.genymobile.scrcpy.util.SettingsException; import com.genymobile.scrcpy.util.SettingsException;
import android.os.BatteryManager;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
@ -16,59 +18,110 @@ import java.io.OutputStream;
*/ */
public final class CleanUp { public final class CleanUp {
private static final int MSG_TYPE_MASK = 0b11; // Dynamic options
private static final int MSG_TYPE_RESTORE_STAY_ON = 0; private static final int PENDING_CHANGE_DISPLAY_POWER = 1 << 0;
private static final int MSG_TYPE_DISABLE_SHOW_TOUCHES = 1; private int pendingChanges;
private static final int MSG_TYPE_RESTORE_DISPLAY_POWER = 2; private boolean pendingRestoreDisplayPower;
private static final int MSG_TYPE_POWER_OFF_SCREEN = 3;
private static final int MSG_PARAM_SHIFT = 2; private Thread thread;
private final OutputStream out; private CleanUp(int displayId, Options options) {
thread = new Thread(() -> runCleanUp(displayId, options), "cleanup");
public CleanUp(OutputStream out) { thread.start();
this.out = out;
} }
public static CleanUp configure(int displayId) throws IOException { public static CleanUp start(int displayId, Options options) {
String[] cmd = {"app_process", "/", CleanUp.class.getName(), String.valueOf(displayId)}; return new CleanUp(displayId, options);
}
public void interrupt() {
thread.interrupt();
}
public void join() throws InterruptedException {
thread.join();
}
private void runCleanUp(int displayId, Options options) {
boolean disableShowTouches = false;
if (options.getShowTouches()) {
try {
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
// If "show touches" was disabled, it must be disabled back on clean up
disableShowTouches = !"1".equals(oldValue);
} catch (SettingsException e) {
Ln.e("Could not change \"show_touches\"", e);
}
}
int restoreStayOn = -1;
if (options.getStayAwake()) {
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
try {
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
try {
int currentStayOn = Integer.parseInt(oldValue);
// Restore only if the current value is different
if (currentStayOn != stayOn) {
restoreStayOn = currentStayOn;
}
} catch (NumberFormatException e) {
// ignore
}
} catch (SettingsException e) {
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
}
}
boolean powerOffScreen = options.getPowerOffScreenOnClose();
try {
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen);
} catch (InterruptedException e) {
// ignore
} catch (IOException e) {
Ln.e("Clean up I/O exception", e);
}
}
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen) throws IOException, InterruptedException {
String[] cmd = {
"app_process",
"/",
CleanUp.class.getName(),
String.valueOf(displayId),
String.valueOf(restoreStayOn),
String.valueOf(disableShowTouches),
String.valueOf(powerOffScreen),
};
ProcessBuilder builder = new ProcessBuilder(cmd); ProcessBuilder builder = new ProcessBuilder(cmd);
builder.environment().put("CLASSPATH", Server.SERVER_PATH); builder.environment().put("CLASSPATH", Server.SERVER_PATH);
Process process = builder.start(); Process process = builder.start();
return new CleanUp(process.getOutputStream()); OutputStream out = process.getOutputStream();
}
private boolean sendMessage(int type, int param) { while (true) {
assert (type & ~MSG_TYPE_MASK) == 0; int localPendingChanges;
int msg = type | param << MSG_PARAM_SHIFT; boolean localPendingRestoreDisplayPower;
try { synchronized (this) {
out.write(msg); while (pendingChanges == 0) {
out.flush(); wait();
return true; }
} catch (IOException e) { localPendingChanges = pendingChanges;
Ln.w("Could not configure cleanup (type=" + type + ", param=" + param + ")", e); localPendingRestoreDisplayPower = pendingRestoreDisplayPower;
return false; pendingChanges = 0;
}
if ((localPendingChanges & PENDING_CHANGE_DISPLAY_POWER) != 0) {
out.write(localPendingRestoreDisplayPower ? 1 : 0);
out.flush();
}
} }
} }
public boolean setRestoreStayOn(int restoreValue) { public synchronized void setRestoreDisplayPower(boolean restoreDisplayPower) {
// Restore the value (between 0 and 7), -1 to not restore pendingRestoreDisplayPower = restoreDisplayPower;
// <https://developer.android.com/reference/android/provider/Settings.Global#STAY_ON_WHILE_PLUGGED_IN> pendingChanges |= PENDING_CHANGE_DISPLAY_POWER;
assert restoreValue >= -1 && restoreValue <= 7; notify();
return sendMessage(MSG_TYPE_RESTORE_STAY_ON, restoreValue & 0b1111);
}
public boolean setDisableShowTouches(boolean disableOnExit) {
return sendMessage(MSG_TYPE_DISABLE_SHOW_TOUCHES, disableOnExit ? 1 : 0);
}
public boolean setRestoreDisplayPower(boolean restoreOnExit) {
return sendMessage(MSG_TYPE_RESTORE_DISPLAY_POWER, restoreOnExit ? 1 : 0);
}
public boolean setPowerOffScreen(boolean powerOffScreenOnExit) {
return sendMessage(MSG_TYPE_POWER_OFF_SCREEN, powerOffScreenOnExit ? 1 : 0);
} }
public static void unlinkSelf() { public static void unlinkSelf() {
@ -83,35 +136,20 @@ public final class CleanUp {
unlinkSelf(); unlinkSelf();
int displayId = Integer.parseInt(args[0]); int displayId = Integer.parseInt(args[0]);
int restoreStayOn = Integer.parseInt(args[1]);
boolean disableShowTouches = Boolean.parseBoolean(args[2]);
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
int restoreStayOn = -1; // Dynamic option
boolean disableShowTouches = false;
boolean restoreDisplayPower = false; boolean restoreDisplayPower = false;
boolean powerOffScreen = false;
try { try {
// Wait for the server to die // Wait for the server to die
int msg; int msg;
while ((msg = System.in.read()) != -1) { while ((msg = System.in.read()) != -1) {
int type = msg & MSG_TYPE_MASK; // Only restore display power
int param = msg >> MSG_PARAM_SHIFT; assert msg == 0 || msg == 1;
switch (type) { restoreDisplayPower = msg != 0;
case MSG_TYPE_RESTORE_STAY_ON:
restoreStayOn = param > 7 ? -1 : param;
break;
case MSG_TYPE_DISABLE_SHOW_TOUCHES:
disableShowTouches = param != 0;
break;
case MSG_TYPE_RESTORE_DISPLAY_POWER:
restoreDisplayPower = param != 0;
break;
case MSG_TYPE_POWER_OFF_SCREEN:
powerOffScreen = param != 0;
break;
default:
Ln.w("Unexpected msg type: " + type);
break;
}
} }
} catch (IOException e) { } catch (IOException e) {
// Expected when the server is dead // Expected when the server is dead

View File

@ -16,8 +16,6 @@ import com.genymobile.scrcpy.device.NewDisplay;
import com.genymobile.scrcpy.device.Streamer; import com.genymobile.scrcpy.device.Streamer;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils; import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.util.Settings;
import com.genymobile.scrcpy.util.SettingsException;
import com.genymobile.scrcpy.video.CameraCapture; import com.genymobile.scrcpy.video.CameraCapture;
import com.genymobile.scrcpy.video.NewDisplayCapture; import com.genymobile.scrcpy.video.NewDisplayCapture;
import com.genymobile.scrcpy.video.ScreenCapture; import com.genymobile.scrcpy.video.ScreenCapture;
@ -25,7 +23,6 @@ import com.genymobile.scrcpy.video.SurfaceCapture;
import com.genymobile.scrcpy.video.SurfaceEncoder; import com.genymobile.scrcpy.video.SurfaceEncoder;
import com.genymobile.scrcpy.video.VideoSource; import com.genymobile.scrcpy.video.VideoSource;
import android.os.BatteryManager;
import android.os.Build; import android.os.Build;
import java.io.File; import java.io.File;
@ -76,51 +73,6 @@ public final class Server {
// not instantiable // not instantiable
} }
private static void initAndCleanUp(Options options, CleanUp cleanUp) {
// This method is called from its own thread, so it may only configure cleanup actions which are NOT dynamic (i.e. they are configured once
// and for all, they cannot be changed from another thread)
if (options.getShowTouches()) {
try {
String oldValue = Settings.getAndPutValue(Settings.TABLE_SYSTEM, "show_touches", "1");
// If "show touches" was disabled, it must be disabled back on clean up
if (!"1".equals(oldValue)) {
if (!cleanUp.setDisableShowTouches(true)) {
Ln.e("Could not disable show touch on exit");
}
}
} catch (SettingsException e) {
Ln.e("Could not change \"show_touches\"", e);
}
}
if (options.getStayAwake()) {
int stayOn = BatteryManager.BATTERY_PLUGGED_AC | BatteryManager.BATTERY_PLUGGED_USB | BatteryManager.BATTERY_PLUGGED_WIRELESS;
try {
String oldValue = Settings.getAndPutValue(Settings.TABLE_GLOBAL, "stay_on_while_plugged_in", String.valueOf(stayOn));
try {
int restoreStayOn = Integer.parseInt(oldValue);
if (restoreStayOn != stayOn) {
// Restore only if the current value is different
if (!cleanUp.setRestoreStayOn(restoreStayOn)) {
Ln.e("Could not restore stay on on exit");
}
}
} catch (NumberFormatException e) {
// ignore
}
} catch (SettingsException e) {
Ln.e("Could not change \"stay_on_while_plugged_in\"", e);
}
}
if (options.getPowerOffScreenOnClose()) {
if (!cleanUp.setPowerOffScreen(true)) {
Ln.e("Could not power off screen on exit");
}
}
}
private static void scrcpy(Options options) throws IOException, ConfigurationException { private static void scrcpy(Options options) throws IOException, ConfigurationException {
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) { if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
Ln.e("Camera mirroring is not supported before Android 12"); Ln.e("Camera mirroring is not supported before Android 12");
@ -150,14 +102,12 @@ public final class Server {
} }
CleanUp cleanUp = null; CleanUp cleanUp = null;
Thread initThread = null;
NewDisplay newDisplay = options.getNewDisplay(); NewDisplay newDisplay = options.getNewDisplay();
int displayId = newDisplay == null ? options.getDisplayId() : Device.DISPLAY_ID_NONE; int displayId = newDisplay == null ? options.getDisplayId() : Device.DISPLAY_ID_NONE;
if (options.getCleanup()) { if (options.getCleanup()) {
cleanUp = CleanUp.configure(displayId); cleanUp = CleanUp.start(displayId, options);
initThread = startInitThread(options, cleanUp);
} }
int scid = options.getScid(); int scid = options.getScid();
@ -240,8 +190,8 @@ public final class Server {
completion.await(); completion.await();
} finally { } finally {
if (initThread != null) { if (cleanUp != null) {
initThread.interrupt(); cleanUp.interrupt();
} }
for (AsyncProcessor asyncProcessor : asyncProcessors) { for (AsyncProcessor asyncProcessor : asyncProcessors) {
asyncProcessor.stop(); asyncProcessor.stop();
@ -250,8 +200,8 @@ public final class Server {
connection.shutdown(); connection.shutdown();
try { try {
if (initThread != null) { if (cleanUp != null) {
initThread.join(); cleanUp.join();
} }
for (AsyncProcessor asyncProcessor : asyncProcessors) { for (AsyncProcessor asyncProcessor : asyncProcessors) {
asyncProcessor.join(); asyncProcessor.join();
@ -264,12 +214,6 @@ public final class Server {
} }
} }
private static Thread startInitThread(final Options options, final CleanUp cleanUp) {
Thread thread = new Thread(() -> initAndCleanUp(options, cleanUp), "init-cleanup");
thread.start();
return thread;
}
public static void main(String... args) { public static void main(String... args) {
int status = 0; int status = 0;
try { try {