test virtual display

This commit is contained in:
JerryXiao 2023-09-08 17:31:28 +08:00
parent 7b1ac46ab0
commit bd67110a20
Signed by: Jerry
GPG key ID: 22618F758B5BE2E5
10 changed files with 114 additions and 2 deletions

View file

@ -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;

View file

@ -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,
};

View file

@ -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;

View file

@ -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 = {

View file

@ -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

View file

@ -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 {

View file

@ -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());

View file

@ -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;

View file

@ -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;

View file

@ -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(