test virtual display
This commit is contained in:
parent
7b1ac46ab0
commit
bd67110a20
10 changed files with 114 additions and 2 deletions
|
@ -80,6 +80,8 @@ enum {
|
||||||
OPT_KILL_ADB_ON_CLOSE,
|
OPT_KILL_ADB_ON_CLOSE,
|
||||||
OPT_TIME_LIMIT,
|
OPT_TIME_LIMIT,
|
||||||
OPT_ROOT,
|
OPT_ROOT,
|
||||||
|
OPT_SU_QUIRK,
|
||||||
|
OPT_VIRTUAL_DISPLAY,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_option {
|
struct sc_option {
|
||||||
|
@ -541,6 +543,19 @@ static const struct sc_option options[] = {
|
||||||
.longopt = "root",
|
.longopt = "root",
|
||||||
.text = "Launch the server as root (disabled by default).",
|
.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',
|
.shortopt = 's',
|
||||||
.longopt = "serial",
|
.longopt = "serial",
|
||||||
|
@ -1986,6 +2001,12 @@ parse_args_with_getopt(struct scrcpy_cli_args *args, int argc, char *argv[],
|
||||||
case OPT_ROOT:
|
case OPT_ROOT:
|
||||||
opts->root = true;
|
opts->root = true;
|
||||||
break;
|
break;
|
||||||
|
case OPT_SU_QUIRK:
|
||||||
|
opts->su_quirk = true;
|
||||||
|
break;
|
||||||
|
case OPT_VIRTUAL_DISPLAY:
|
||||||
|
opts->virtual_display_params = optarg;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
// getopt prints the error message on stderr
|
// getopt prints the error message on stderr
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -83,4 +83,6 @@ const struct scrcpy_options scrcpy_options_default = {
|
||||||
.list_displays = false,
|
.list_displays = false,
|
||||||
.kill_adb_on_close = false,
|
.kill_adb_on_close = false,
|
||||||
.root = false,
|
.root = false,
|
||||||
|
.su_quirk = false,
|
||||||
|
.virtual_display_params = NULL,
|
||||||
};
|
};
|
||||||
|
|
|
@ -183,6 +183,8 @@ struct scrcpy_options {
|
||||||
bool list_displays;
|
bool list_displays;
|
||||||
bool kill_adb_on_close;
|
bool kill_adb_on_close;
|
||||||
bool root;
|
bool root;
|
||||||
|
bool su_quirk;
|
||||||
|
const char *virtual_display_params;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern const struct scrcpy_options scrcpy_options_default;
|
extern const struct scrcpy_options scrcpy_options_default;
|
||||||
|
|
|
@ -381,6 +381,8 @@ scrcpy(struct scrcpy_options *options) {
|
||||||
.list_displays = options->list_displays,
|
.list_displays = options->list_displays,
|
||||||
.kill_adb_on_close = options->kill_adb_on_close,
|
.kill_adb_on_close = options->kill_adb_on_close,
|
||||||
.root = options->root,
|
.root = options->root,
|
||||||
|
.su_quirk = options->su_quirk,
|
||||||
|
.virtual_display_params = options->virtual_display_params,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct sc_server_callbacks cbs = {
|
static const struct sc_server_callbacks cbs = {
|
||||||
|
|
|
@ -198,7 +198,8 @@ execute_server(struct sc_server *server,
|
||||||
if (params->root) {
|
if (params->root) {
|
||||||
cmd[count++] = "su";
|
cmd[count++] = "su";
|
||||||
cmd[count++] = "1000"; // AID_SYSTEM, AID_GRAPHICS is also supported for FLAG_SECURE but lacks other perms
|
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;
|
cmd[count++] = "CLASSPATH=" SC_DEVICE_SERVER_PATH;
|
||||||
|
@ -323,6 +324,10 @@ execute_server(struct sc_server *server,
|
||||||
if (params->list_displays) {
|
if (params->list_displays) {
|
||||||
ADD_PARAM("list_displays=true");
|
ADD_PARAM("list_displays=true");
|
||||||
}
|
}
|
||||||
|
if (params->virtual_display_params) {
|
||||||
|
ADD_PARAM("virtual_display=%s", params->virtual_display_params);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#undef ADD_PARAM
|
#undef ADD_PARAM
|
||||||
|
|
||||||
|
|
|
@ -60,6 +60,8 @@ struct sc_server_params {
|
||||||
bool list_displays;
|
bool list_displays;
|
||||||
bool kill_adb_on_close;
|
bool kill_adb_on_close;
|
||||||
bool root;
|
bool root;
|
||||||
|
bool su_quirk;
|
||||||
|
const char *virtual_display_params;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct sc_server {
|
struct sc_server {
|
||||||
|
|
|
@ -68,7 +68,10 @@ public final class Device {
|
||||||
private final boolean supportsInputEvents;
|
private final boolean supportsInputEvents;
|
||||||
|
|
||||||
public Device(Options options) throws ConfigurationException {
|
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);
|
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
|
||||||
if (displayInfo == null) {
|
if (displayInfo == null) {
|
||||||
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
|
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
|
||||||
|
|
|
@ -3,10 +3,13 @@ package com.genymobile.scrcpy;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.content.AttributionSource;
|
import android.content.AttributionSource;
|
||||||
import android.content.MutableContextWrapper;
|
import android.content.MutableContextWrapper;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
public final class FakeContext extends MutableContextWrapper {
|
public final class FakeContext extends MutableContextWrapper {
|
||||||
|
|
||||||
public static final String PACKAGE_NAME = Os.getuid() == 1000 ? "android" : "com.android.shell";
|
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);
|
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
|
@Override
|
||||||
public String getPackageName() {
|
public String getPackageName() {
|
||||||
return PACKAGE_NAME;
|
return PACKAGE_NAME;
|
||||||
|
|
|
@ -44,6 +44,7 @@ public class Options {
|
||||||
private boolean sendFrameMeta = true; // send PTS so that the client may record properly
|
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 sendDummyByte = true; // write a byte on start to detect connection issues
|
||||||
private boolean sendCodecMeta = true; // write the codec metadata before the stream
|
private boolean sendCodecMeta = true; // write the codec metadata before the stream
|
||||||
|
private String virtualDisplayParams = null;
|
||||||
|
|
||||||
public Ln.Level getLogLevel() {
|
public Ln.Level getLogLevel() {
|
||||||
return logLevel;
|
return logLevel;
|
||||||
|
@ -176,6 +177,9 @@ public class Options {
|
||||||
public boolean getSendCodecMeta() {
|
public boolean getSendCodecMeta() {
|
||||||
return sendCodecMeta;
|
return sendCodecMeta;
|
||||||
}
|
}
|
||||||
|
public String getVirtualDisplayParams() {
|
||||||
|
return virtualDisplayParams;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("MethodLength")
|
@SuppressWarnings("MethodLength")
|
||||||
public static Options parse(String... args) {
|
public static Options parse(String... args) {
|
||||||
|
@ -327,6 +331,9 @@ public class Options {
|
||||||
options.sendCodecMeta = false;
|
options.sendCodecMeta = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "virtual_display":
|
||||||
|
options.virtualDisplayParams = value;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Ln.w("Unknown server option: " + key);
|
Ln.w("Unknown server option: " + key);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -2,10 +2,15 @@ package com.genymobile.scrcpy.wrappers;
|
||||||
|
|
||||||
import com.genymobile.scrcpy.Command;
|
import com.genymobile.scrcpy.Command;
|
||||||
import com.genymobile.scrcpy.DisplayInfo;
|
import com.genymobile.scrcpy.DisplayInfo;
|
||||||
|
import com.genymobile.scrcpy.FakeContext;
|
||||||
import com.genymobile.scrcpy.Ln;
|
import com.genymobile.scrcpy.Ln;
|
||||||
import com.genymobile.scrcpy.Size;
|
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.Display;
|
||||||
|
import android.view.Surface;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
|
@ -18,6 +23,55 @@ public final class DisplayManager {
|
||||||
this.manager = manager;
|
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 to call it from unit tests
|
||||||
public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) {
|
public static DisplayInfo parseDisplayInfo(String dumpsysDisplayOutput, int displayId) {
|
||||||
Pattern regex = Pattern.compile(
|
Pattern regex = Pattern.compile(
|
||||||
|
|
Loading…
Reference in a new issue