From bd67110a20752675a193650de4b84b5bed104426 Mon Sep 17 00:00:00 2001 From: Jerry Date: Fri, 8 Sep 2023 17:31:28 +0800 Subject: [PATCH] test virtual display --- app/src/cli.c | 21 ++++++++ app/src/options.c | 2 + app/src/options.h | 2 + app/src/scrcpy.c | 2 + app/src/server.c | 7 ++- app/src/server.h | 2 + .../java/com/genymobile/scrcpy/Device.java | 5 +- .../com/genymobile/scrcpy/FakeContext.java | 14 +++++ .../java/com/genymobile/scrcpy/Options.java | 7 +++ .../scrcpy/wrappers/DisplayManager.java | 54 +++++++++++++++++++ 10 files changed, 114 insertions(+), 2 deletions(-) diff --git a/app/src/cli.c b/app/src/cli.c index ad50b088..d61f924c 100644 --- a/app/src/cli.c +++ b/app/src/cli.c @@ -80,6 +80,8 @@ enum { OPT_KILL_ADB_ON_CLOSE, OPT_TIME_LIMIT, OPT_ROOT, + OPT_SU_QUIRK, + OPT_VIRTUAL_DISPLAY, }; struct sc_option { @@ -541,6 +543,19 @@ static const struct sc_option options[] = { .longopt = "root", .text = "Launch the server as root (disabled by default).", }, + { + .longopt_id = OPT_SU_QUIRK, + .longopt = "su-quirk", + .text = "Enable workaround for quirky su (disabled by default).", + }, + { + .longopt_id = OPT_VIRTUAL_DISPLAY, + .longopt = "virtual-display", + .argdesc = "properties", + .text = "Start a new virtual display for your applications to " + "launch into. Requires Android version >= 10." + "Display size and DPI may be specified. e.g. 1920x1080dpi300" + }, { .shortopt = 's', .longopt = "serial", @@ -1986,6 +2001,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[], case OPT_ROOT: opts->root = true; break; + case OPT_SU_QUIRK: + opts->su_quirk = true; + break; + case OPT_VIRTUAL_DISPLAY: + opts->virtual_display_params = optarg; + break; default: // getopt prints the error message on stderr return false; diff --git a/app/src/options.c b/app/src/options.c index 563c1222..e8a830cd 100644 --- a/app/src/options.c +++ b/app/src/options.c @@ -83,4 +83,6 @@ const struct scrcpy_options scrcpy_options_default = { .list_displays = false, .kill_adb_on_close = false, .root = false, + .su_quirk = false, + .virtual_display_params = NULL, }; diff --git a/app/src/options.h b/app/src/options.h index 942114b4..01a2422a 100644 --- a/app/src/options.h +++ b/app/src/options.h @@ -183,6 +183,8 @@ struct scrcpy_options { bool list_displays; bool kill_adb_on_close; bool root; + bool su_quirk; + const char *virtual_display_params; }; extern const struct scrcpy_options scrcpy_options_default; diff --git a/app/src/scrcpy.c b/app/src/scrcpy.c index b7d3e7f1..ef5cbf5d 100644 --- a/app/src/scrcpy.c +++ b/app/src/scrcpy.c @@ -381,6 +381,8 @@ scrcpy(struct scrcpy_options *options) { .list_displays = options->list_displays, .kill_adb_on_close = options->kill_adb_on_close, .root = options->root, + .su_quirk = options->su_quirk, + .virtual_display_params = options->virtual_display_params, }; static const struct sc_server_callbacks cbs = { diff --git a/app/src/server.c b/app/src/server.c index e52b4965..44baaa90 100644 --- a/app/src/server.c +++ b/app/src/server.c @@ -198,7 +198,8 @@ execute_server(struct sc_server *server, if (params->root) { cmd[count++] = "su"; cmd[count++] = "1000"; // AID_SYSTEM, AID_GRAPHICS is also supported for FLAG_SECURE but lacks other perms - cmd[count++] = "-c"; + if (!params->su_quirk) cmd[count++] = "-c"; + cmd[count++] = "env"; } cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH; @@ -323,6 +324,10 @@ execute_server(struct sc_server *server, if (params->list_displays) { ADD_PARAM("list_displays=true"); } + if (params->virtual_display_params) { + ADD_PARAM("virtual_display=%s", params->virtual_display_params); + } + #undef ADD_PARAM diff --git a/app/src/server.h b/app/src/server.h index 5c5a8e8d..5fc3a38c 100644 --- a/app/src/server.h +++ b/app/src/server.h @@ -60,6 +60,8 @@ struct sc_server_params { bool list_displays; bool kill_adb_on_close; bool root; + bool su_quirk; + const char *virtual_display_params; }; struct sc_server { diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 4ab689b0..0d391f61 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -68,7 +68,10 @@ public final class Device { private final boolean supportsInputEvents; public Device(Options options) throws ConfigurationException { - displayId = options.getDisplayId(); + String virtualDisplayParams = options.getVirtualDisplayParams(); + displayId = virtualDisplayParams == null + ? options.getDisplayId() + : ServiceManager.getDisplayManager().createVirtualDisplay(virtualDisplayParams).getDisplay().getDisplayId(); DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); if (displayInfo == null) { Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage()); diff --git a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java index 1d9c4a56..195098fa 100644 --- a/server/src/main/java/com/genymobile/scrcpy/FakeContext.java +++ b/server/src/main/java/com/genymobile/scrcpy/FakeContext.java @@ -3,10 +3,13 @@ package com.genymobile.scrcpy; import android.annotation.TargetApi; import android.content.AttributionSource; import android.content.MutableContextWrapper; +import android.content.res.Resources; import android.os.Build; import android.os.Process; import android.system.Os; +import java.lang.reflect.Constructor; + public final class FakeContext extends MutableContextWrapper { public static final String PACKAGE_NAME = Os.getuid() == 1000 ? "android" : "com.android.shell"; @@ -23,6 +26,17 @@ public final class FakeContext extends MutableContextWrapper { super(null); } + @Override + public Resources getResources() { + try { + Constructor constructor = Class.forName("android.content.res.Resources").getDeclaredConstructor(); + constructor.setAccessible(true); + return (Resources) constructor.newInstance(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + @Override public String getPackageName() { return PACKAGE_NAME; diff --git a/server/src/main/java/com/genymobile/scrcpy/Options.java b/server/src/main/java/com/genymobile/scrcpy/Options.java index aab6fce8..444e54d8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Options.java +++ b/server/src/main/java/com/genymobile/scrcpy/Options.java @@ -44,6 +44,7 @@ public class Options { private boolean sendFrameMeta = true; // send PTS so that the client may record properly private boolean sendDummyByte = true; // write a byte on start to detect connection issues private boolean sendCodecMeta = true; // write the codec metadata before the stream + private String virtualDisplayParams = null; public Ln.Level getLogLevel() { return logLevel; @@ -176,6 +177,9 @@ public class Options { public boolean getSendCodecMeta() { return sendCodecMeta; } + public String getVirtualDisplayParams() { + return virtualDisplayParams; + } @SuppressWarnings("MethodLength") public static Options parse(String... args) { @@ -327,6 +331,9 @@ public class Options { options.sendCodecMeta = false; } break; + case "virtual_display": + options.virtualDisplayParams = value; + break; default: Ln.w("Unknown server option: " + key); break; diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 17b9ae4d..aeb90b4a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -2,10 +2,15 @@ package com.genymobile.scrcpy.wrappers; import com.genymobile.scrcpy.Command; import com.genymobile.scrcpy.DisplayInfo; +import com.genymobile.scrcpy.FakeContext; import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Size; +import android.graphics.SurfaceTexture; +import android.hardware.display.VirtualDisplay; +import android.os.Build; import android.view.Display; +import android.view.Surface; import java.lang.reflect.Field; import java.util.regex.Matcher; @@ -18,6 +23,55 @@ public final class DisplayManager { this.manager = manager; } + public VirtualDisplay createVirtualDisplay(String displayParams) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) throw new RuntimeException("requires android 10+"); + final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5; + final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6; + final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7; + final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8; + final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9; + final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10; + final int VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP = 1 << 11; + final int VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED = 1 << 12; + final int VIRTUAL_DISPLAY_FLAG_TOUCH_FEEDBACK_DISABLED = 1 << 13; + try { + if (displayParams == null || displayParams.isEmpty()) displayParams = "1920x1080dpi300"; + String resolution = displayParams.toLowerCase().split("dpi")[0]; + int width = Integer.parseInt(resolution.split("x")[0]); + int height = Integer.parseInt(resolution.split("x")[1]); + int dpi = Integer.parseInt(displayParams.toLowerCase().split("dpi")[1]); + //MediaProjectionManager mediaProjectionManager = (MediaProjectionManager)Class.forName("android.media.projection.MediaProjectionManager").getConstructor(Class.forName("android.content.Context")).newInstance(FakeContext.get()); + //Intent intent = mediaProjectionManager.createScreenCaptureIntent(); + //ServiceManager.getActivityManager().startActivityAsUserWithFeature(intent); + android.hardware.display.DisplayManager displayManager = (android.hardware.display.DisplayManager)Class.forName("android.hardware.display.DisplayManager").getConstructor(Class.forName("android.content.Context")).newInstance(FakeContext.get()); + SurfaceTexture surfaceTexture = new SurfaceTexture(false); + Surface surface = new Surface(surfaceTexture); + VirtualDisplay[] display = new VirtualDisplay[1]; + display[0] = displayManager.createVirtualDisplay("test1", width, height, dpi, surface, + android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC + | android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR + | android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE + | VIRTUAL_DISPLAY_FLAG_TRUSTED + | VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED, + new VirtualDisplay.Callback() { + @Override + public void onStopped() { + super.onStopped(); + surfaceTexture.release(); + surface.release(); + display[0].release(); + } + }, null); + // start launcher or settings activity + // TODO: replace shell commands with proper android framework api call + Command.exec("am", "start", "--user", "0", "--display", Integer.toString(display[0].getDisplay().getDisplayId()), "-a", "android.intent.action.MAIN", "-c", "android.intent.category.HOME", "-c", "android.intent.category.DEFAULT"); + //Command.exec("am", "start", "--activity-single-top", "--display", Integer.toString(display.getDisplay().getDisplayId()), "com.android.settings"); + return display[0]; + } catch (Exception e) { + throw new AssertionError(e); + } + } + // public to call it from unit tests public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) { Pattern regex = Pattern.compile(