diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index b51ad8d377..495e84e610 100644 --- a/server/src/main/java/com/genymobile/scrcpy/Device.java +++ b/server/src/main/java/com/genymobile/scrcpy/Device.java @@ -2,6 +2,7 @@ import com.genymobile.scrcpy.wrappers.ClipboardManager; import com.genymobile.scrcpy.wrappers.DisplayControl; +import com.genymobile.scrcpy.wrappers.DisplayManager; import com.genymobile.scrcpy.wrappers.InputManager; import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; @@ -10,6 +11,8 @@ import android.content.IOnPrimaryClipChangedListener; import android.graphics.Rect; import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; import android.os.SystemClock; import android.view.IDisplayFoldListener; @@ -33,8 +36,8 @@ 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 DisplayChangeListener { + void onDisplayChanged(); } public interface FoldListener { @@ -45,13 +48,13 @@ public interface ClipboardListener { void onClipboardTextChanged(String text); } - private final Size deviceSize; private final Rect crop; private int maxSize; private final int lockVideoOrientation; + private Size deviceSize; private ScreenInfo screenInfo; - private RotationListener rotationListener; + private DisplayChangeListener displayChangeListener; private FoldListener foldListener; private ClipboardListener clipboardListener; private final AtomicBoolean isSettingClipboard = new AtomicBoolean(); @@ -78,27 +81,44 @@ public Device(Options options) throws ConfigurationException { int displayInfoFlags = displayInfo.getFlags(); - deviceSize = displayInfo.getSize(); crop = options.getCrop(); maxSize = options.getMaxSize(); lockVideoOrientation = options.getLockVideoOrientation(); + deviceSize = displayInfo.getSize(); screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); layerStack = displayInfo.getLayerStack(); - ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { + HandlerThread displayListenerThread = new HandlerThread("DisplayListenerThread"); + displayListenerThread.start(); + + Handler displayListenerHandler = new Handler(displayListenerThread.getLooper()); + ServiceManager.getDisplayManager().registerDisplayListener(new DisplayManager.DisplayListener() { @Override - public void onRotationChanged(int rotation) { - synchronized (Device.this) { - screenInfo = screenInfo.withDeviceRotation(rotation); + public void onDisplayAdded(int displayId) { + // nothing to do + } - // notify - if (rotationListener != null) { - rotationListener.onRotationChanged(rotation); - } + @Override + public void onDisplayRemoved(int displayId) { + // nothing to do + } + + @Override + public void onDisplayChanged(int displayId) { + if (Device.this.displayId != displayId) { + return; + } + + DisplayInfo displayInfo = ServiceManager.getDisplayManager().getDisplayInfo(displayId); + deviceSize = displayInfo.getSize(); + screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); + + if (displayChangeListener != null) { + displayChangeListener.onDisplayChanged(); } } - }, displayId); + }, displayListenerHandler, DisplayManager.EVENT_FLAG_DISPLAY_CHANGED); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { ServiceManager.getWindowManager().registerDisplayFoldListener(new IDisplayFoldListener.Stub() { @@ -254,8 +274,8 @@ public static boolean isScreenOn() { return ServiceManager.getPowerManager().isScreenOn(); } - public synchronized void setRotationListener(RotationListener rotationListener) { - this.rotationListener = rotationListener; + public synchronized void setDisplayChangeListener(DisplayChangeListener displayChangeListener) { + this.displayChangeListener = displayChangeListener; } public synchronized void setFoldListener(FoldListener foldlistener) { diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java index e048354a93..f95d62d4c2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java @@ -7,7 +7,7 @@ import android.os.IBinder; import android.view.Surface; -public class ScreenCapture extends SurfaceCapture implements Device.RotationListener, Device.FoldListener { +public class ScreenCapture extends SurfaceCapture implements Device.DisplayChangeListener, Device.FoldListener { private final Device device; private IBinder display; @@ -18,7 +18,7 @@ public ScreenCapture(Device device) { @Override public void init() { - device.setRotationListener(this); + device.setDisplayChangeListener(this); device.setFoldListener(this); } @@ -41,7 +41,7 @@ public void start(Surface surface) { @Override public void release() { - device.setRotationListener(null); + device.setDisplayChangeListener(null); device.setFoldListener(null); if (display != null) { SurfaceControl.destroyDisplay(display); @@ -65,7 +65,7 @@ public void onFoldChanged(int displayId, boolean folded) { } @Override - public void onRotationChanged(int rotation) { + public void onDisplayChanged() { requestReset(); } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java index 17b9ae4ddb..e3c92bde29 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -5,13 +5,22 @@ import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Size; +import android.os.Handler; import android.view.Display; import java.lang.reflect.Field; +import java.lang.reflect.Proxy; import java.util.regex.Matcher; import java.util.regex.Pattern; public final class DisplayManager { + /** + * Event type for when a display is changed. + * + * @see #registerDisplayListener(DisplayListener, Handler, long) + */ + public static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2; + private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal public DisplayManager(Object manager) { @@ -94,4 +103,69 @@ public int[] getDisplayIds() { throw new AssertionError(e); } } + + public void registerDisplayListener(DisplayListener listener, Handler handler, long eventMask) { + try { + Class displayListenerClass = Class.forName("android.hardware.display.DisplayManager$DisplayListener"); + Object displayListenerProxy = Proxy.newProxyInstance( + ClassLoader.getSystemClassLoader(), + new Class[]{ displayListenerClass }, + (proxy, method, args) -> { + switch (method.getName()) { + case "onDisplayAdded": + listener.onDisplayAdded((int) args[0]); + break; + case "onDisplayRemoved": + listener.onDisplayRemoved((int) args[0]); + break; + case "onDisplayChanged": + listener.onDisplayChanged((int) args[0]); + break; + default: + throw new AssertionError("Unexpected method: " + method.getName()); + } + return null; + }); + try { + manager + .getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class, long.class) + .invoke(manager, displayListenerProxy, handler, eventMask); + } catch (NoSuchMethodException e) { + manager + .getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class) + .invoke(manager, displayListenerProxy, handler); + } + } catch (Exception e) { + // Rotation and screen size won't be updated, not a fatal error + Ln.w("Could not register display listener", e); + } + } + + public interface DisplayListener { + /** + * Called whenever a logical display has been added to the system. + * Use {@link DisplayManager#getDisplay} to get more information about + * the display. + * + * @param displayId The id of the logical display that was added. + */ + void onDisplayAdded(int displayId); + + /** + * Called whenever a logical display has been removed from the system. + * + * @param displayId The id of the logical display that was removed. + */ + void onDisplayRemoved(int displayId); + + /** + * Called whenever the properties of a logical {@link android.view.Display}, + * such as size and density, have changed. + * + * @param displayId The id of the logical display that changed. + */ + void onDisplayChanged(int displayId); + } } 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 a746be5cf6..d767d73654 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -95,22 +95,6 @@ public void thawRotation() { } } - public void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) { - try { - Class cls = manager.getClass(); - try { - // display parameter added since this commit: - // https://android.googlesource.com/platform/frameworks/base/+/35fa3c26adcb5f6577849fd0df5228b1f67cf2c6%5E%21/#F1 - cls.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId); - } catch (NoSuchMethodException e) { - // old version - cls.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher); - } - } catch (Exception e) { - Ln.e("Could not register rotation watcher", e); - } - } - @TargetApi(29) public void registerDisplayFoldListener(IDisplayFoldListener foldListener) { try {