scrcpy/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java
2023-09-10 01:14:00 +08:00

154 lines
7.2 KiB
Java

package com.genymobile.scrcpy.wrappers;
import com.genymobile.scrcpy.Command;
import com.genymobile.scrcpy.DisplayInfo;
import com.genymobile.scrcpy.FakeContext;
import com.genymobile.scrcpy.IO;
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.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class DisplayManager {
private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal
public DisplayManager(Object 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_TRUSTED = 1 << 10;
final int VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED = 1 << 12;
try {
if (displayParams == null || displayParams.isEmpty()) displayParams = "1920x1080dpi300";
String[] displayParamsArray = displayParams.split(",");
String resolution = displayParamsArray[0].toLowerCase().split("dpi")[0];
int width = Integer.parseInt(resolution.split("x")[0]);
int height = Integer.parseInt(resolution.split("x")[1]);
int dpi = Integer.parseInt(displayParamsArray[0].toLowerCase().split("dpi")[1]);
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("scrcpy", 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);
Ln.i(String.format("Display: %d", display[0].getDisplay().getDisplayId()));
// start launcher
// TODO: replace shell commands with proper android framework api call
String[] cmd;
if (displayParamsArray.length == 1) {
cmd = new String[]{"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"};
}
else {
ArrayList<String> command = new ArrayList<>(Arrays.asList("am", "start", "--display", Integer.toString(display[0].getDisplay().getDisplayId())));
command.addAll(Arrays.asList(Arrays.copyOfRange(displayParamsArray, 1, displayParamsArray.length)));
cmd = command.toArray(new String[0]);
}
Process process = Runtime.getRuntime().exec(cmd);
String output = IO.toString(process.getInputStream());
String error = IO.toString(process.getErrorStream());
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command " + Arrays.toString(cmd) + " returned with value " + exitCode);
}
Ln.i(String.format("stdout: %s\nstderr: %s", output.trim(), error.trim()));
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(
"^ mOverrideDisplayInfo=DisplayInfo\\{\".*?, displayId " + displayId + ".*?(, FLAG_.*)?, real ([0-9]+) x ([0-9]+).*?, "
+ "rotation ([0-9]+).*?, layerStack ([0-9]+)",
Pattern.MULTILINE);
Matcher m = regex.matcher(dumpsysDisplayOutput);
if (!m.find()) {
return null;
}
int flags = parseDisplayFlags(m.group(1));
int width = Integer.parseInt(m.group(2));
int height = Integer.parseInt(m.group(3));
int rotation = Integer.parseInt(m.group(4));
int layerStack = Integer.parseInt(m.group(5));
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags);
}
private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) {
try {
String dumpsysDisplayOutput = Command.execReadOutput("dumpsys", "display");
return parseDisplayInfo(dumpsysDisplayOutput, displayId);
} catch (Exception e) {
Ln.e("Could not get display info from \"dumpsys display\" output", e);
return null;
}
}
private static int parseDisplayFlags(String text) {
Pattern regex = Pattern.compile("FLAG_[A-Z_]+");
if (text == null) {
return 0;
}
int flags = 0;
Matcher m = regex.matcher(text);
while (m.find()) {
String flagString = m.group();
try {
Field filed = Display.class.getDeclaredField(flagString);
flags |= filed.getInt(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
// Silently ignore, some flags reported by "dumpsys display" are @TestApi
}
}
return flags;
}
public DisplayInfo getDisplayInfo(int displayId) {
try {
Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId);
if (displayInfo == null) {
// fallback when displayInfo is null
return getDisplayInfoFromDumpsysDisplay(displayId);
}
Class<?> cls = displayInfo.getClass();
// width and height already take the rotation into account
int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo);
int height = cls.getDeclaredField("logicalHeight").getInt(displayInfo);
int rotation = cls.getDeclaredField("rotation").getInt(displayInfo);
int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo);
int flags = cls.getDeclaredField("flags").getInt(displayInfo);
return new DisplayInfo(displayId, new Size(width, height), rotation, layerStack, flags);
} catch (Exception e) {
throw new AssertionError(e);
}
}
public int[] getDisplayIds() {
try {
return (int[]) manager.getClass().getMethod("getDisplayIds").invoke(manager);
} catch (Exception e) {
throw new AssertionError(e);
}
}
}