diff --git a/server/src/main/java/com/genymobile/scrcpy/Server.java b/server/src/main/java/com/genymobile/scrcpy/Server.java index 555cf97a..9802e0f5 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Server.java +++ b/server/src/main/java/com/genymobile/scrcpy/Server.java @@ -190,7 +190,8 @@ public final class Server { options.getSendFrameMeta()); SurfaceCapture surfaceCapture; if (options.getVideoSource() == VideoSource.DISPLAY) { - surfaceCapture = new ScreenCapture(device); + surfaceCapture = new ScreenCapture(device, options.getDisplayId(), options.getMaxSize(), options.getCrop(), + options.getLockVideoOrientation()); } else { surfaceCapture = new CameraCapture(options.getCameraId(), options.getCameraFacing(), options.getCameraSize(), options.getMaxSize(), options.getCameraAspectRatio(), options.getCameraFps(), options.getCameraHighSpeed()); diff --git a/server/src/main/java/com/genymobile/scrcpy/device/Device.java b/server/src/main/java/com/genymobile/scrcpy/device/Device.java index 1f375942..63e33988 100644 --- a/server/src/main/java/com/genymobile/scrcpy/device/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/device/Device.java @@ -3,7 +3,6 @@ package com.genymobile.scrcpy.device; import com.genymobile.scrcpy.AndroidVersions; import com.genymobile.scrcpy.Options; import com.genymobile.scrcpy.util.Ln; -import com.genymobile.scrcpy.util.LogUtils; import com.genymobile.scrcpy.video.ScreenInfo; import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.DisplayControl; @@ -17,14 +16,13 @@ import android.graphics.Rect; import android.os.Build; import android.os.IBinder; import android.os.SystemClock; -import android.view.IDisplayFoldListener; -import android.view.IRotationWatcher; import android.view.InputDevice; import android.view.InputEvent; import android.view.KeyCharacterMap; import android.view.KeyEvent; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; 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_INITIAL = -2; - public interface RotationListener { - void onRotationChanged(int rotation); - } - - public interface FoldListener { - void onFoldChanged(int displayId, boolean folded); - } - public interface ClipboardListener { 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 final AtomicBoolean isSettingClipboard = new AtomicBoolean(); @@ -66,71 +48,12 @@ public final class Device { */ private final int displayId; - /** - * The surface flinger layer stack associated with this logical display - */ - private final int layerStack; - private final boolean supportsInputEvents; - public Device(Options options) throws ConfigurationException { + private final AtomicReference screenInfo = new AtomicReference<>(); // set by the ScreenCapture instance + + public Device(Options options) { 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 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 - 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) { 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) { - // it hides the field on purpose, to read it with a lock + // it hides the field on purpose, to read it with atomic access @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 Size unlockedVideoSize = screenInfo.getUnlockedVideoSize(); @@ -223,6 +128,10 @@ public final class Device { return supportsInputEvents; } + public void setScreenInfo(ScreenInfo screenInfo) { + this.screenInfo.set(screenInfo); + } + public static boolean injectEvent(InputEvent inputEvent, int displayId, int injectMode) { if (!supportsInputEvents(displayId)) { throw new AssertionError("Could not inject input event if !supportsInputEvents()"); @@ -263,14 +172,6 @@ public final class Device { 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) { this.clipboardListener = clipboardListener; } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java index e6357410..af9a9283 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java @@ -1,9 +1,12 @@ package com.genymobile.scrcpy.video; import com.genymobile.scrcpy.AndroidVersions; +import com.genymobile.scrcpy.device.ConfigurationException; import com.genymobile.scrcpy.device.Device; +import com.genymobile.scrcpy.device.DisplayInfo; import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.util.Ln; +import com.genymobile.scrcpy.util.LogUtils; import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; @@ -11,33 +14,104 @@ import android.graphics.Rect; import android.hardware.display.VirtualDisplay; import android.os.Build; import android.os.IBinder; +import android.view.IDisplayFoldListener; +import android.view.IRotationWatcher; 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 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 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.displayId = displayId; + this.maxSize = maxSize; + this.crop = crop; + this.lockVideoOrientation = lockVideoOrientation; } @Override - public void init() { - device.setRotationListener(this); - device.setFoldListener(this); + public void init() throws ConfigurationException { + 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); + } + + 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 public void start(Surface surface) { - ScreenInfo screenInfo = device.getScreenInfo(); Rect contentRect = screenInfo.getContentRect(); // does not include the locked video orientation Rect unlockedVideoRect = screenInfo.getUnlockedVideoSize().toRect(); int videoRotation = screenInfo.getVideoRotation(); - int layerStack = device.getLayerStack(); if (display != null) { SurfaceControl.destroyDisplay(display); @@ -51,7 +125,7 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList try { Rect videoRect = screenInfo.getVideoSize().toRect(); 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"); } catch (Exception displayManagerException) { try { @@ -68,8 +142,12 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList @Override public void release() { - device.setRotationListener(null); - device.setFoldListener(null); + if (rotationWatcher != null) { + ServiceManager.getWindowManager().unregisterRotationWatcher(rotationWatcher); + } + if (Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10) { + ServiceManager.getWindowManager().unregisterDisplayFoldListener(displayFoldListener); + } if (display != null) { SurfaceControl.destroyDisplay(display); display = null; @@ -81,26 +159,18 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList } @Override - public Size getSize() { - return device.getScreenInfo().getVideoSize(); + public synchronized Size getSize() { + return screenInfo.getVideoSize(); } @Override - public boolean setMaxSize(int maxSize) { - device.setMaxSize(maxSize); + public synchronized boolean setMaxSize(int newMaxSize) { + maxSize = newMaxSize; + ScreenInfo si = ScreenInfo.computeScreenInfo(screenInfo.getReverseVideoRotation(), deviceSize, crop, newMaxSize, lockVideoOrientation); + setScreenInfo(si); return true; } - @Override - public void onFoldChanged(int displayId, boolean folded) { - requestReset(); - } - - @Override - public void onRotationChanged(int rotation) { - requestReset(); - } - private static IBinder createDisplay() throws Exception { // 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". @@ -119,4 +189,9 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList SurfaceControl.closeTransaction(); } } + + private void setScreenInfo(ScreenInfo si) { + screenInfo = si; + device.setScreenInfo(si); + } } diff --git a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java index 3118ddc8..fe679beb 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/SurfaceCapture.java @@ -1,5 +1,6 @@ package com.genymobile.scrcpy.video; +import com.genymobile.scrcpy.device.ConfigurationException; import com.genymobile.scrcpy.device.Size; import android.view.Surface; @@ -34,7 +35,7 @@ public abstract class SurfaceCapture { /** * 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). diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java index 5894b836..ee36139a 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -201,13 +201,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(AndroidVersions.API_29_ANDROID_10) public void registerDisplayFoldListener(IDisplayFoldListener foldListener) { try { - Class cls = manager.getClass(); - cls.getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener); + manager.getClass().getMethod("registerDisplayFoldListener", IDisplayFoldListener.class).invoke(manager, foldListener); } catch (Exception e) { Ln.e("Could not register display fold listener", e); } } + + @TargetApi(AndroidVersions.API_29_ANDROID_10) + 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); + } + } }