diff --git a/server/src/main/java/com/genymobile/scrcpy/Device.java b/server/src/main/java/com/genymobile/scrcpy/Device.java index 2db9db6c..d4800244 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 @@ package com.genymobile.scrcpy; 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 com.genymobile.scrcpy.wrappers.WindowManager; 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,6 +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 DisplayChangeListener { + void onDisplayChanged(); + } + public interface RotationListener { void onRotationChanged(int rotation); } @@ -51,6 +58,7 @@ public final class Device { private Size deviceSize; private ScreenInfo screenInfo; + private DisplayChangeListener displayChangeListener; private RotationListener rotationListener; private FoldListener foldListener; private ClipboardListener clipboardListener; @@ -86,6 +94,27 @@ public final class Device { screenInfo = ScreenInfo.computeScreenInfo(displayInfo.getRotation(), deviceSize, crop, maxSize, lockVideoOrientation); layerStack = displayInfo.getLayerStack(); + HandlerThread displayListenerThread = new HandlerThread("DisplayListenerThread"); + displayListenerThread.start(); + + Handler displayListenerHandler = new Handler(displayListenerThread.getLooper()); + ServiceManager.getDisplayManager().registerDisplayListener(new DisplayManager.DisplayListener() { + @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(); + } + } + }, displayListenerHandler); + ServiceManager.getWindowManager().registerRotationWatcher(new IRotationWatcher.Stub() { @Override public void onRotationChanged(int rotation) { @@ -253,6 +282,10 @@ public final class Device { return ServiceManager.getPowerManager().isScreenOn(); } + public synchronized void setDisplayChangeListener(DisplayChangeListener displayChangeListener) { + this.displayChangeListener = displayChangeListener; + } + public synchronized void setRotationListener(RotationListener rotationListener) { this.rotationListener = rotationListener; } diff --git a/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java b/server/src/main/java/com/genymobile/scrcpy/ScreenCapture.java index e048354a..d4f25253 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.Build; 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.RotationListener, Device.FoldListener { private final Device device; private IBinder display; @@ -18,6 +18,7 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList @Override public void init() { + device.setDisplayChangeListener(this); device.setRotationListener(this); device.setFoldListener(this); } @@ -41,6 +42,7 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList @Override public void release() { + device.setDisplayChangeListener(null); device.setRotationListener(null); device.setFoldListener(null); if (display != null) { @@ -69,6 +71,11 @@ public class ScreenCapture extends SurfaceCapture implements Device.RotationList requestReset(); } + @Override + public void onDisplayChanged() { + requestReset(); + } + private static IBinder createDisplay() { // 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". 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 17b9ae4d..5f57a7cc 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,20 @@ import com.genymobile.scrcpy.DisplayInfo; import com.genymobile.scrcpy.Ln; import com.genymobile.scrcpy.Size; +import android.os.Build; +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 { + + // Constant copied from AOSP framework_base: core/java/android/hardware/display/DisplayManager.java + private 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 +101,47 @@ public final class DisplayManager { throw new AssertionError(e); } } + + public void registerDisplayListener(DisplayListener listener, Handler handler) { + try { + Class displayListenerClass = Class.forName("android.hardware.display.DisplayManager$DisplayListener"); + Object displayListenerProxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), + new Class[]{displayListenerClass}, + (proxy, method, args) -> { + if ("onDisplayChanged".equals(method.getName())) { + listener.onDisplayChanged((int) args[0]); + } + return null; + }); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + try { + manager.getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class, long.class) + .invoke(manager, displayListenerProxy, handler, EVENT_FLAG_DISPLAY_CHANGED); + return; + } catch (NoSuchMethodException e) { + // fall-through + } + } + + manager.getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class) + .invoke(manager, displayListenerProxy, handler); + } catch (Exception e) { + // Screen size won't be updated, not a fatal error + Ln.e("Could not register display listener", e); + } + } + + // Partially copied from AOSP framework_base: core/java/android/hardware/display/DisplayManager.java + public interface DisplayListener { + /** + * 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); + } }