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 c356344ae9..45f56ba846 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/ScreenCapture.java @@ -6,15 +6,16 @@ import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.LogUtils; +import com.genymobile.scrcpy.wrappers.DisplayManager; import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.SurfaceControl; import android.graphics.Rect; import android.hardware.display.VirtualDisplay; import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; -import android.view.IDisplayFoldListener; -import android.view.IRotationWatcher; import android.view.Surface; public class ScreenCapture extends SurfaceCapture { @@ -31,8 +32,8 @@ public class ScreenCapture extends SurfaceCapture { private IBinder display; private VirtualDisplay virtualDisplay; - private IRotationWatcher rotationWatcher; - private IDisplayFoldListener displayFoldListener; + private DisplayManager.DisplayListenerHandle displayListenerHandle; + private HandlerThread handlerThread; public ScreenCapture(VirtualDisplayListener vdListener, int displayId, int maxSize, Rect crop, int lockVideoOrientation) { this.vdListener = vdListener; @@ -44,30 +45,14 @@ public ScreenCapture(VirtualDisplayListener vdListener, int displayId, int maxSi @Override public void init() { - if (displayId == 0) { - rotationWatcher = new IRotationWatcher.Stub() { - @Override - public void onRotationChanged(int rotation) { - 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; - } - - requestReset(); - } - }; - ServiceManager.getWindowManager().registerDisplayFoldListener(displayFoldListener); - } + handlerThread = new HandlerThread("DisplayListener"); + handlerThread.start(); + Handler handler = new Handler(handlerThread.getLooper()); + displayListenerHandle = ServiceManager.getDisplayManager().registerDisplayListener(displayId -> { + if (this.displayId == displayId) { + requestReset(); + } + }, handler); } @Override @@ -137,12 +122,8 @@ public void start(Surface surface) { @Override public void release() { - if (rotationWatcher != null) { - ServiceManager.getWindowManager().unregisterRotationWatcher(rotationWatcher); - } - if (Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10) { - ServiceManager.getWindowManager().unregisterDisplayFoldListener(displayFoldListener); - } + handlerThread.quitSafely(); + ServiceManager.getDisplayManager().unregisterDisplayListener(displayListenerHandle); if (display != null) { SurfaceControl.destroyDisplay(display); display = null; 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 c8c405bb4b..8f5086d827 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayManager.java @@ -9,17 +9,40 @@ import android.annotation.SuppressLint; import android.content.Context; import android.hardware.display.VirtualDisplay; +import android.os.Handler; import android.view.Display; import android.view.Surface; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.lang.reflect.Proxy; import java.util.regex.Matcher; import java.util.regex.Pattern; @SuppressLint("PrivateApi,DiscouragedPrivateApi") public final class DisplayManager { + + // android.hardware.display.DisplayManager.EVENT_FLAG_DISPLAY_CHANGED + public static final long EVENT_FLAG_DISPLAY_CHANGED = 1L << 2; + + 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); + } + + public static final class DisplayListenerHandle { + private final Object displayListenerProxy; + private DisplayListenerHandle(Object displayListenerProxy) { + this.displayListenerProxy = displayListenerProxy; + } + } + private final Object manager; // instance of hidden class android.hardware.display.DisplayManagerGlobal private Method createVirtualDisplayMethod; @@ -137,4 +160,50 @@ public VirtualDisplay createNewVirtualDisplay(String name, int width, int height android.hardware.display.DisplayManager dm = ctor.newInstance(FakeContext.get()); return dm.createVirtualDisplay(name, width, height, dpi, surface, flags); } + + public DisplayListenerHandle 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; + }); + try { + manager.getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class, long.class, String.class) + .invoke(manager, displayListenerProxy, handler, EVENT_FLAG_DISPLAY_CHANGED, FakeContext.PACKAGE_NAME); + } catch (NoSuchMethodException e) { + try { + manager.getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class, long.class) + .invoke(manager, displayListenerProxy, handler, EVENT_FLAG_DISPLAY_CHANGED); + } catch (NoSuchMethodException e2) { + manager.getClass() + .getMethod("registerDisplayListener", displayListenerClass, Handler.class) + .invoke(manager, displayListenerProxy, handler); + } + } + + return new DisplayListenerHandle(displayListenerProxy); + } catch (Exception e) { + // Rotation and screen size won't be updated, not a fatal error + Ln.e("Could not register display listener", e); + } + + return null; + } + + public void unregisterDisplayListener(DisplayListenerHandle listener) { + try { + Class displayListenerClass = Class.forName("android.hardware.display.DisplayManager$DisplayListener"); + manager.getClass().getMethod("unregisterDisplayListener", displayListenerClass).invoke(manager, listener.displayListenerProxy); + } catch (Exception e) { + Ln.e("Could not unregister display listener", e); + } + } }