Add --display-ime-policy option

Add an option to select where the IME should be displayed.

Possible values are "local", "fallback" and "hide".

PR #5703 <https://github.com/Genymobile/scrcpy/pull/5703>

Signed-off-by: Romain Vimont <rom@rom1v.com>
This commit is contained in:
chengjian.scj
2024-12-25 15:30:37 +08:00
committed by Romain Vimont
parent 986328ff9e
commit fd8bef68b7
15 changed files with 239 additions and 7 deletions

View File

@@ -4,6 +4,7 @@ import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.Settings;
import com.genymobile.scrcpy.util.SettingsException;
import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.os.BatteryManager;
import android.system.ErrnoException;
@@ -97,18 +98,31 @@ public final class CleanUp {
}
}
boolean powerOffScreen = options.getPowerOffScreenOnClose();
int displayId = options.getDisplayId();
int restoreDisplayImePolicy = -1;
if (displayId > 0) {
int displayImePolicy = options.getDisplayImePolicy();
if (displayImePolicy != -1) {
int currentDisplayImePolicy = ServiceManager.getWindowManager().getDisplayImePolicy(displayId);
if (currentDisplayImePolicy != displayImePolicy) {
ServiceManager.getWindowManager().setDisplayImePolicy(displayId, displayImePolicy);
restoreDisplayImePolicy = currentDisplayImePolicy;
}
}
}
boolean powerOffScreen = options.getPowerOffScreenOnClose();
try {
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout);
run(displayId, restoreStayOn, disableShowTouches, powerOffScreen, restoreScreenOffTimeout, restoreDisplayImePolicy);
} catch (IOException e) {
Ln.e("Clean up I/O exception", e);
}
}
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout)
throws IOException {
private void run(int displayId, int restoreStayOn, boolean disableShowTouches, boolean powerOffScreen, int restoreScreenOffTimeout,
int restoreDisplayImePolicy) throws IOException {
String[] cmd = {
"app_process",
"/",
@@ -118,6 +132,7 @@ public final class CleanUp {
String.valueOf(disableShowTouches),
String.valueOf(powerOffScreen),
String.valueOf(restoreScreenOffTimeout),
String.valueOf(restoreDisplayImePolicy),
};
ProcessBuilder builder = new ProcessBuilder(cmd);
@@ -178,6 +193,7 @@ public final class CleanUp {
boolean disableShowTouches = Boolean.parseBoolean(args[2]);
boolean powerOffScreen = Boolean.parseBoolean(args[3]);
int restoreScreenOffTimeout = Integer.parseInt(args[4]);
int restoreDisplayImePolicy = Integer.parseInt(args[5]);
// Dynamic option
boolean restoreDisplayPower = false;
@@ -223,6 +239,11 @@ public final class CleanUp {
}
}
if (restoreDisplayImePolicy != -1) {
Ln.i("Restoring \"display IME policy\"");
ServiceManager.getWindowManager().setDisplayImePolicy(displayId, restoreDisplayImePolicy);
}
// Change the power of the main display when mirroring a virtual display
int targetDisplayId = displayId != Device.DISPLAY_ID_NONE ? displayId : 0;
if (Device.isScreenOn(targetDisplayId)) {

View File

@@ -12,6 +12,7 @@ import com.genymobile.scrcpy.video.CameraAspectRatio;
import com.genymobile.scrcpy.video.CameraFacing;
import com.genymobile.scrcpy.video.VideoCodec;
import com.genymobile.scrcpy.video.VideoSource;
import com.genymobile.scrcpy.wrappers.WindowManager;
import android.graphics.Rect;
import android.util.Pair;
@@ -48,6 +49,7 @@ public class Options {
private boolean showTouches;
private boolean stayAwake;
private int screenOffTimeout = -1;
private int displayImePolicy = -1;
private List<CodecOption> videoCodecOptions;
private List<CodecOption> audioCodecOptions;
@@ -186,6 +188,10 @@ public class Options {
return screenOffTimeout;
}
public int getDisplayImePolicy() {
return displayImePolicy;
}
public List<CodecOption> getVideoCodecOptions() {
return videoCodecOptions;
}
@@ -482,6 +488,9 @@ public class Options {
options.captureOrientationLock = pair.first;
options.captureOrientation = pair.second;
break;
case "display_ime_policy":
options.displayImePolicy = parseDisplayImePolicy(value);
break;
case "send_device_meta":
options.sendDeviceMeta = Boolean.parseBoolean(value);
break;
@@ -626,4 +635,17 @@ public class Options {
return Pair.create(lock, Orientation.getByName(value));
}
private static int parseDisplayImePolicy(String value) {
switch (value) {
case "local":
return WindowManager.DISPLAY_IME_POLICY_LOCAL;
case "fallback":
return WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
case "hide":
return WindowManager.DISPLAY_IME_POLICY_HIDE;
default:
throw new IllegalArgumentException("Invalid display IME policy: " + value);
}
}
}

View File

@@ -80,9 +80,15 @@ public final class Server {
throw new ConfigurationException("Camera mirroring is not supported");
}
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10 && options.getNewDisplay() != null) {
Ln.e("New virtual display is not supported before Android 10");
throw new ConfigurationException("New virtual display is not supported");
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
if (options.getNewDisplay() != null) {
Ln.e("New virtual display is not supported before Android 10");
throw new ConfigurationException("New virtual display is not supported");
}
if (options.getDisplayImePolicy() != -1) {
Ln.e("Display IME policy is not supported before Android 10");
throw new ConfigurationException("Display IME policy is not supported");
}
}
CleanUp cleanUp = null;

View File

@@ -49,6 +49,7 @@ public class NewDisplayCapture extends SurfaceCapture {
private Size mainDisplaySize;
private int mainDisplayDpi;
private int maxSize;
private int displayImePolicy;
private final Rect crop;
private final boolean captureOrientationLocked;
private final Orientation captureOrientation;
@@ -68,6 +69,7 @@ public class NewDisplayCapture extends SurfaceCapture {
this.newDisplay = options.getNewDisplay();
assert newDisplay != null;
this.maxSize = options.getMaxSize();
this.displayImePolicy = options.getDisplayImePolicy();
this.crop = options.getCrop();
assert options.getCaptureOrientationLock() != null;
this.captureOrientationLocked = options.getCaptureOrientationLock() != Orientation.Lock.Unlocked;
@@ -191,6 +193,10 @@ public class NewDisplayCapture extends SurfaceCapture {
virtualDisplayId = virtualDisplay.getDisplay().getDisplayId();
Ln.i("New display: " + displaySize.getWidth() + "x" + displaySize.getHeight() + "/" + dpi + " (id=" + virtualDisplayId + ")");
if (displayImePolicy != -1) {
ServiceManager.getWindowManager().setDisplayImePolicy(virtualDisplayId, displayImePolicy);
}
displaySizeMonitor.start(virtualDisplayId, this::invalidate);
} catch (Exception e) {
Ln.e("Could not create display", e);

View File

@@ -4,12 +4,19 @@ import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.util.Ln;
import android.annotation.TargetApi;
import android.os.Build;
import android.os.IInterface;
import android.view.IDisplayWindowListener;
import java.lang.reflect.Method;
public final class WindowManager {
// <https://android.googlesource.com/platform/frameworks/base.git/+/2103ff441c66772c80c8560e322dcd9a45be7dcd/core/java/android/view/WindowManager.java#692>
public static final int DISPLAY_IME_POLICY_LOCAL = 0;
public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1;
public static final int DISPLAY_IME_POLICY_HIDE = 2;
private final IInterface manager;
private Method getRotationMethod;
@@ -22,6 +29,9 @@ public final class WindowManager {
private Method thawDisplayRotationMethod;
private int thawDisplayRotationMethodVersion;
private Method getDisplayImePolicyMethod;
private Method setDisplayImePolicyMethod;
static WindowManager create() {
IInterface manager = ServiceManager.getService("window", "android.view.IWindowManager");
return new WindowManager(manager);
@@ -198,4 +208,59 @@ public final class WindowManager {
Ln.e("Could not unregister display window listener", e);
}
}
@TargetApi(AndroidVersions.API_29_ANDROID_10)
private Method getGetDisplayImePolicyMethod() throws NoSuchMethodException {
if (getDisplayImePolicyMethod == null) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) {
getDisplayImePolicyMethod = manager.getClass().getMethod("getDisplayImePolicy", int.class);
} else {
getDisplayImePolicyMethod = manager.getClass().getMethod("shouldShowIme", int.class);
}
}
return getDisplayImePolicyMethod;
}
@TargetApi(AndroidVersions.API_29_ANDROID_10)
public int getDisplayImePolicy(int displayId) {
try {
Method method = getGetDisplayImePolicyMethod();
if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) {
return (int) method.invoke(manager, displayId);
}
boolean shouldShowIme = (boolean) method.invoke(manager, displayId);
return shouldShowIme ? DISPLAY_IME_POLICY_LOCAL : DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
return -1;
}
}
@TargetApi(AndroidVersions.API_29_ANDROID_10)
private Method getSetDisplayImePolicyMethod() throws NoSuchMethodException {
if (setDisplayImePolicyMethod == null) {
if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) {
setDisplayImePolicyMethod = manager.getClass().getMethod("setDisplayImePolicy", int.class, int.class);
} else {
setDisplayImePolicyMethod = manager.getClass().getMethod("setShouldShowIme", int.class, boolean.class);
}
}
return setDisplayImePolicyMethod;
}
@TargetApi(AndroidVersions.API_29_ANDROID_10)
public void setDisplayImePolicy(int displayId, int displayImePolicy) {
try {
Method method = getSetDisplayImePolicyMethod();
if (Build.VERSION.SDK_INT >= AndroidVersions.API_31_ANDROID_12) {
method.invoke(manager, displayId, displayImePolicy);
} else if (displayImePolicy != DISPLAY_IME_POLICY_HIDE) {
method.invoke(manager, displayId, displayImePolicy == DISPLAY_IME_POLICY_LOCAL);
} else {
Ln.w("DISPLAY_IME_POLICY_HIDE is not supported before Android 12");
}
} catch (ReflectiveOperationException e) {
Ln.e("Could not invoke method", e);
}
}
}