Move screen-related features out of Device.java

Move the code related to screen size and rotation/fold to ScreenCapture.

For now, keep the ScreenInfo instance in the Device class to communicate
with the Controller, but it will be removed by further commits.
This commit is contained in:
Romain Vimont 2024-10-12 09:23:31 +02:00
parent 7b3dd595b4
commit 68476b6d28
5 changed files with 135 additions and 141 deletions

View File

@ -190,7 +190,8 @@ public final class Server {
options.getSendFrameMeta()); options.getSendFrameMeta());
SurfaceCapture surfaceCapture; SurfaceCapture surfaceCapture;
if (options.getVideoSource() == VideoSource.DISPLAY) { if (options.getVideoSource() == VideoSource.DISPLAY) {
surfaceCapture = new ScreenCapture(device); surfaceCapture = new ScreenCapture(device, options.getDisplayId(), options.getMaxSize(), options.getCrop(),
options.getLockVideoOrientation());
} else { } else {
surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(), surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(),
options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed()); options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed());

View File

@ -3,7 +3,6 @@ package com.genymobile.scrcpy.device;
import com.genymobile.scrcpy.AndroidVersions; import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.Options; import com.genymobile.scrcpy.Options;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.video.ScreenInfo; import com.genymobile.scrcpy.video.ScreenInfo;
import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.ClipboardManager;
import com.genymobile.scrcpy.wrappers.DisplayControl; import com.genymobile.scrcpy.wrappers.DisplayControl;
@ -17,14 +16,13 @@ import android.graphics.Rect;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.SystemClock; import android.os.SystemClock;
import android.view.IDisplayFoldListener;
import android.view.IRotationWatcher;
import android.view.InputDevice; import android.view.InputDevice;
import android.view.InputEvent; import android.view.InputEvent;
import android.view.KeyCharacterMap; import android.view.KeyCharacterMap;
import android.view.KeyEvent; import android.view.KeyEvent;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
public final class Device { public final class Device {
@ -38,26 +36,10 @@ public final class Device {
public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1; public static final int LOCK_VIDEO_ORIENTATION_UNLOCKED = -1;
public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2; public static final int LOCK_VIDEO_ORIENTATION_INITIAL = -2;
public interface RotationListener {
void onRotationChanged(int rotation);
}
public interface FoldListener {
void onFoldChanged(int displayId, boolean folded);
}
public interface ClipboardListener { public interface ClipboardListener {
void onClipboardTextChanged(String text); void onClipboardTextChanged(String text);
} }
private final Rect crop;
private int maxSize;
private final int lockVideoOrientation;
private Size deviceSize;
private ScreenInfo screenInfo;
private RotationListener rotationListener;
private FoldListener foldListener;
private ClipboardListener clipboardListener; private ClipboardListener clipboardListener;
private final AtomicBoolean isSettingClipboard = new AtomicBoolean(); private final AtomicBoolean isSettingClipboard = new AtomicBoolean();
@ -66,71 +48,12 @@ public final class Device {
*/ */
private final int displayId; private final int displayId;
/**
* The surface flinger layer stack associated with this logical display
*/
private final int layerStack;
private final boolean supportsInputEvents; private final boolean supportsInputEvents;
public Device(Options options) throws ConfigurationException { private final AtomicReference<ScreenInfo> screenInfo = new AtomicReference<>(); // set by the ScreenCapture instance
public Device(Options options) {
displayId = options.getDisplayId(); displayId = options.getDisplayId();
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) {
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
throw new ConfigurationException("Unknown display id: " + displayId);
}
int displayInfoFlags = displayInfo.getFlags();
deviceSize = displayInfo.getSize();
crop = options.getCrop();
maxSize = options.getMaxSize();
lockVideoOrientation = options.getLockVideoOrientation();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
layerStack = displayInfo.getLayerStack();
ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() {
@Override
public void onRotationChanged(int rotation) {
synchronized (Device.this) {
screenInfo = screenInfo.withDeviceRotation(rotation);
// notify
if (rotationListener != null) {
rotationListener.onRotationChanged(rotation);
}
}
}
}, displayId);
if (Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10) {
ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() {
@Override
public void onDisplayFoldChanged(int displayId, boolean folded) {
if (Device.this.displayId != displayId) {
// Ignore events related to other display ids
return;
}
synchronized (Device.this) {
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) {
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
return;
}
deviceSize = displayInfo.getSize();
screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
// notify
if (foldListener != null) {
foldListener.onFoldChanged(displayId, folded);
}
}
}
});
}
if (options.getControl() && options.getClipboardAutosync()) { if (options.getControl() && options.getClipboardAutosync()) {
// If control and autosync are enabled, synchronize Android clipboard to the computer automatically // If control and autosync are enabled, synchronize Android clipboard to the computer automatically
@ -158,38 +81,20 @@ public final class Device {
} }
} }
if ((displayInfoFlags & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
}
// main display or any display on Android >= 10 // main display or any display on Android >= 10
supportsInputEvents = displayId == 0 || Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10; supportsInputEvents = options.getDisplayId() == 0 || Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10;
if (!supportsInputEvents) { if (!supportsInputEvents) {
Ln.w("Input events are not supported for secondary displays before Android 10"); Ln.w("Input events are not supported for secondary displays before Android 10");
} }
} }
public int getDisplayId() {
return displayId;
}
public synchronized void setMaxSize(int newMaxSize) {
maxSize = newMaxSize;
screenInfo = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
}
public synchronized ScreenInfo getScreenInfo() {
return screenInfo;
}
public int getLayerStack() {
return layerStack;
}
public Point getPhysicalPoint(Position position) { public Point getPhysicalPoint(Position position) {
// it hides the field on purpose, to read it with a lock // it hides the field on purpose, to read it from the atomic once
@SuppressWarnings("checkstyle:HiddenField") @SuppressWarnings("checkstyle:HiddenField")
ScreenInfo screenInfo = getScreenInfo(); // read with synchronization ScreenInfo screenInfo = this.screenInfo.get();
if (screenInfo == null) {
return null;
}
// ignore the locked video orientation, the events will apply in coordinates considered in the physical device orientation // ignore the locked video orientation, the events will apply in coordinates considered in the physical device orientation
Size unlockedVideoSize = screenInfo.getUnlockedVideoSize(); Size unlockedVideoSize = screenInfo.getUnlockedVideoSize();
@ -223,6 +128,10 @@ public final class Device {
return supportsInputEvents; return supportsInputEvents;
} }
public void setScreenInfo(ScreenInfo screenInfo) {
this.screenInfo.set(screenInfo);
}
public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) { public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) {
if (!supportsInputEvents(displayId)) { if (!supportsInputEvents(displayId)) {
throw new AssertionError("Could not inject input event if !supportsInputEvents()"); throw new AssertionError("Could not inject input event if !supportsInputEvents()");
@ -263,14 +172,6 @@ public final class Device {
return ServiceManager.getPowerManager().isScreenOn(); return ServiceManager.getPowerManager().isScreenOn();
} }
public synchronized void setRotationListener(RotationListener rotationListener) {
this.rotationListener = rotationListener;
}
public synchronized void setFoldListener(FoldListener foldlistener) {
this.foldListener = foldlistener;
}
public synchronized void setClipboardListener(ClipboardListener clipboardListener) { public synchronized void setClipboardListener(ClipboardListener clipboardListener) {
this.clipboardListener = clipboardListener; this.clipboardListener = clipboardListener;
} }

View File

@ -1,9 +1,12 @@
package com.genymobile.scrcpy.video; package com.genymobile.scrcpy.video;
import com.genymobile.scrcpy.AndroidVersions; import com.genymobile.scrcpy.AndroidVersions;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.device.Device; import com.genymobile.scrcpy.device.Device;
import com.genymobile.scrcpy.device.DisplayInfo;
import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.util.LogUtils;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import com.genymobile.scrcpy.wrappers.SurfaceControl; import com.genymobile.scrcpy.wrappers.SurfaceControl;
@ -11,33 +14,104 @@ import android.graphics.Rect;
import android.hardware.display.VirtualDisplay; import android.hardware.display.VirtualDisplay;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.view.IDisplayFoldListener;
import android.view.IRotationWatcher;
import android.view.Surface; import android.view.Surface;
public class ScreenCapture extends SurfaceCapture implements Device.RotationListener, Device.FoldListener { public class ScreenCapture extends SurfaceCapture {
private final Device device; private final Device device;
private final int displayId;
private int maxSize;
private final Rect crop;
private final int lockVideoOrientation;
private int layerStack;
private Size deviceSize;
private ScreenInfo screenInfo;
private IBinder display; private IBinder display;
private VirtualDisplay virtualDisplay; private VirtualDisplay virtualDisplay;
public ScreenCapture(Device device) { private IRotationWatcher rotationWatcher;
private IDisplayFoldListener displayFoldListener;
public ScreenCapture(Device device, int displayId, int maxSize, Rect crop, int lockVideoOrientation) {
this.device = device; this.device = device;
this.displayId = displayId;
this.maxSize = maxSize;
this.crop = crop;
this.lockVideoOrientation = lockVideoOrientation;
} }
@Override @Override
public void init() { public void init() throws ConfigurationException {
device.setRotationListener(this); DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
device.setFoldListener(this); if (displayInfo == null) {
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
throw new ConfigurationException("Unknown display id: " + displayId);
}
deviceSize = displayInfo.getSize();
ScreenInfo si = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
setScreenInfo(si);
layerStack = displayInfo.getLayerStack();
if (displayId == 0) {
rotationWatcher = new IRotationWatcher.Stub() {
@Override
public void onRotationChanged(int rotation) {
synchronized (ScreenCapture.this) {
ScreenInfo si = screenInfo.withDeviceRotation(rotation);
setScreenInfo(si);
}
requestReset();
}
};
ServiceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId);
}
if (Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10) {
displayFoldListener = new IDisplayFoldListener.Stub() {
@Override
public void onDisplayFoldChanged(int displayId, boolean folded) {
if (ScreenCapture.this.displayId != displayId) {
// Ignore events related to other display ids
return;
}
synchronized (ScreenCapture.this) {
DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId);
if (displayInfo == null) {
Ln.e("Display " + displayId + " not found\n" + LogUtils.buildDisplayListMessage());
return;
}
deviceSize = displayInfo.getSize();
ScreenInfo si = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation);
setScreenInfo(si);
}
requestReset();
}
};
ServiceManager.getWindowManager().registerDisplayFoldListener(displayFoldListener);
}
if ((displayInfo.getFlags() & DisplayInfo.FLAG_SUPPORTS_PROTECTED_BUFFERS) == 0) {
Ln.w("Display doesn't have FLAG_SUPPORTS_PROTECTED_BUFFERS flag, mirroring can be restricted");
}
} }
@Override @Override
public void start(Surface surface) { public void start(Surface surface) {
ScreenInfo screenInfo = device.getScreenInfo();
Rect contentRect = screenInfo.getContentRect(); Rect contentRect = screenInfo.getContentRect();
// does not include the locked video orientation // does not include the locked video orientation
Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect();
int videoRotation = screenInfo.getVideoRotation(); int videoRotation = screenInfo.getVideoRotation();
int layerStack = device.getLayerStack();
if (display != null) { if (display != null) {
SurfaceControl.destroyDisplay(display); SurfaceControl.destroyDisplay(display);
@ -51,7 +125,7 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
try { try {
Rect videoRect = screenInfo.getVideoSize().toRect(); Rect videoRect = screenInfo.getVideoSize().toRect();
virtualDisplay = ServiceManager.getDisplayManager() virtualDisplay = ServiceManager.getDisplayManager()
.createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), device.getDisplayId(), surface); .createVirtualDisplay("scrcpy", videoRect.width(), videoRect.height(), displayId, surface);
Ln.d("Display: using DisplayManager API"); Ln.d("Display: using DisplayManager API");
} catch (Exception displayManagerException) { } catch (Exception displayManagerException) {
try { try {
@ -68,8 +142,12 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
@Override @Override
public void release() { public void release() {
device.setRotationListener(null); if (rotationWatcher != null) {
device.setFoldListener(null); ServiceManager.getWindowManager().unregisterRotationWatcher(rotationWatcher);
}
if (Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10) {
ServiceManager.getWindowManager().unregisterDisplayFoldListener(displayFoldListener);
}
if (display != null) { if (display != null) {
SurfaceControl.destroyDisplay(display); SurfaceControl.destroyDisplay(display);
display = null; display = null;
@ -81,26 +159,18 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
} }
@Override @Override
public Size getSize() { public synchronized Size getSize() {
return device.getScreenInfo().getVideoSize(); return screenInfo.getVideoSize();
} }
@Override @Override
public boolean setMaxSize(int maxSize) { public synchronized boolean setMaxSize(int newMaxSize) {
device.setMaxSize(maxSize); maxSize = newMaxSize;
ScreenInfo si = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation);
setScreenInfo(si);
return true; return true;
} }
@Override
public void onFoldChanged(int displayId, boolean folded) {
requestReset();
}
@Override
public void onRotationChanged(int rotation) {
requestReset();
}
private static IBinder createDisplay() throws Exception { private static IBinder createDisplay() throws Exception {
// Since Android 12 (preview), secure displays could not be created with shell permissions anymore. // Since Android 12 (preview), secure displays could not be created with shell permissions anymore.
// On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S". // On Android 12 preview, SDK_INT is still R (not S), but CODENAME is "S".
@ -119,4 +189,9 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList
SurfaceControl.closeTransaction(); SurfaceControl.closeTransaction();
} }
} }
private void setScreenInfo(ScreenInfo si) {
screenInfo = si;
device.setScreenInfo(si);
}
} }

View File

@ -1,5 +1,6 @@
package com.genymobile.scrcpy.video; package com.genymobile.scrcpy.video;
import com.genymobile.scrcpy.device.ConfigurationException;
import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.device.Size;
import android.view.Surface; import android.view.Surface;
@ -34,7 +35,7 @@ public abstract class SurfaceCapture {
/** /**
* Called once before the capture starts. * Called once before the capture starts.
*/ */
public abstract void init() throws IOException; public abstract void init() throws ConfigurationException, IOException;
/** /**
* Called after the capture ends (if and only if {@link #init()} has been called). * Called after the capture ends (if and only if {@link #init()} has been called).

View File

@ -200,13 +200,29 @@ public final class WindowManager {
} }
} }
public void unregisterRotationWatcher(IRotationWatcher rotationWatcher) {
try {
manager.getClass().getMethod("removeRotationWatcher", IRotationWatcher.class).invoke(manager, rotationWatcher);
} catch (Exception e) {
Ln.e("Could not unregister rotation watcher", e);
}
}
@TargetApi(29) @TargetApi(29)
public void registerDisplayFoldListener(IDisplayFoldListener foldListener) { public void registerDisplayFoldListener(IDisplayFoldListener foldListener) {
try { try {
Class<?> cls = manager.getClass(); manager.getClass().getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener);
cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener);
} catch (Exception e) { } catch (Exception e) {
Ln.e("Could not register display fold listener", e); Ln.e("Could not register display fold listener", e);
} }
} }
@TargetApi(29)
public void unregisterDisplayFoldListener(IDisplayFoldListener foldListener) {
try {
manager.getClass().getMethod("unregisterDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener);
} catch (Exception e) {
Ln.e("Could not unregister display fold listener", e);
}
}
} }