From 108abf486a292dd84734edcb1ac3e15f3b161542 Mon Sep 17 00:00:00 2001 From: Anric Date: Sun, 17 Nov 2024 21:43:32 +0800 Subject: [PATCH] Use DisplayWindowListener for Android 14 On Android 14, DisplayListener may be broken (it never sends events). This is fixed in recent Android 14 upgrades, but we can't really detect it directly. As a workaround, a RotationWatcher and DisplayFoldListener were registered as a fallback, until a first "display changed" event was triggered. To simplify, on Android 14, register a DisplayWindowListener (introduced in Android 11) to listen to configuration changes instead. Co-authored-by: Romain Vimont Signed-off-by: Romain Vimont --- .../scrcpy/video/DisplaySizeMonitor.java | 140 ++++++------------ .../wrappers/DisplayWindowListener.java | 39 +++++ .../scrcpy/wrappers/WindowManager.java | 20 +++ 3 files changed, 104 insertions(+), 95 deletions(-) create mode 100644 server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java diff --git a/server/src/main/java/com/genymobile/scrcpy/video/DisplaySizeMonitor.java b/server/src/main/java/com/genymobile/scrcpy/video/DisplaySizeMonitor.java index df8be323..ff863aa8 100644 --- a/server/src/main/java/com/genymobile/scrcpy/video/DisplaySizeMonitor.java +++ b/server/src/main/java/com/genymobile/scrcpy/video/DisplaySizeMonitor.java @@ -6,13 +6,14 @@ import com.genymobile.scrcpy.device.DisplayInfo; import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.wrappers.DisplayManager; +import com.genymobile.scrcpy.wrappers.DisplayWindowListener; import com.genymobile.scrcpy.wrappers.ServiceManager; +import android.content.res.Configuration; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; -import android.view.IDisplayFoldListener; -import android.view.IRotationWatcher; +import android.view.IDisplayWindowListener; public class DisplaySizeMonitor { @@ -20,15 +21,14 @@ public class DisplaySizeMonitor { void onDisplaySizeChanged(); } + // On Android 14, DisplayListener may be broken (it never sends events). This is fixed in recent Android 14 upgrades, but we can't really + // detect it directly, so register a DisplayWindowListener (introduced in Android 11) to listen to configuration changes instead. + private static final boolean USE_DEFAULT_METHOD = Build.VERSION.SDK_INT != AndroidVersions.API_34_ANDROID_14; + private DisplayManager.DisplayListenerHandle displayListenerHandle; private HandlerThread handlerThread; - // On Android 14, DisplayListener may be broken (it never sends events). This is fixed in recent Android 14 upgrades, but we can't really - // detect it directly, so register a RotationWatcher and a DisplayFoldListener as a fallback, until we receive the first event from - // DisplayListener (which proves that it works). - private boolean displayListenerWorks; // only accessed from the display listener thread - private IRotationWatcher rotationWatcher; - private IDisplayFoldListener displayFoldListener; + private IDisplayWindowListener displayWindowListener; private int displayId = Device.DISPLAY_ID_NONE; @@ -44,31 +44,34 @@ public class DisplaySizeMonitor { assert this.displayId == Device.DISPLAY_ID_NONE; this.displayId = displayId; - handlerThread = new HandlerThread("DisplayListener"); - handlerThread.start(); - Handler handler = new Handler(handlerThread.getLooper()); - - if (Build.VERSION.SDK_INT == AndroidVersions.API_34_ANDROID_14) { - registerDisplayListenerFallbacks(); - } - - displayListenerHandle = ServiceManager.getDisplayManager().registerDisplayListener(eventDisplayId -> { - if (Ln.isEnabled(Ln.Level.VERBOSE)) { - Ln.v("DisplaySizeMonitor: onDisplayChanged(" + eventDisplayId + ")"); - } - - if (Build.VERSION.SDK_INT == AndroidVersions.API_34_ANDROID_14) { - if (!displayListenerWorks) { - // On the first display listener event, we know it works, we can unregister the fallbacks - displayListenerWorks = true; - unregisterDisplayListenerFallbacks(); + if (USE_DEFAULT_METHOD) { + handlerThread = new HandlerThread("DisplayListener"); + handlerThread.start(); + Handler handler = new Handler(handlerThread.getLooper()); + displayListenerHandle = ServiceManager.getDisplayManager().registerDisplayListener(eventDisplayId -> { + if (Ln.isEnabled(Ln.Level.VERBOSE)) { + Ln.v("DisplaySizeMonitor: onDisplayChanged(" + eventDisplayId + ")"); } - } - if (eventDisplayId == displayId) { - checkDisplaySizeChanged(); - } - }, handler); + if (eventDisplayId == displayId) { + checkDisplaySizeChanged(); + } + }, handler); + } else { + displayWindowListener = new DisplayWindowListener() { + @Override + public void onDisplayConfigurationChanged(int eventDisplayId, Configuration newConfig) { + if (Ln.isEnabled(Ln.Level.VERBOSE)) { + Ln.v("DisplaySizeMonitor: onDisplayConfigurationChanged(" + eventDisplayId + ")"); + } + + if (eventDisplayId == displayId) { + checkDisplaySizeChanged(); + } + } + }; + ServiceManager.getWindowManager().registerDisplayWindowListener(displayWindowListener); + } } /** @@ -78,18 +81,18 @@ public class DisplaySizeMonitor { * It is ok to call this method even if {@link #start(int, Listener)} was not called. */ public void stopAndRelease() { - if (Build.VERSION.SDK_INT == AndroidVersions.API_34_ANDROID_14) { - unregisterDisplayListenerFallbacks(); - } + if (USE_DEFAULT_METHOD) { + // displayListenerHandle may be null if registration failed + if (displayListenerHandle != null) { + ServiceManager.getDisplayManager().unregisterDisplayListener(displayListenerHandle); + displayListenerHandle = null; + } - // displayListenerHandle may be null if registration failed - if (displayListenerHandle != null) { - ServiceManager.getDisplayManager().unregisterDisplayListener(displayListenerHandle); - displayListenerHandle = null; - } - - if (handlerThread != null) { - handlerThread.quitSafely(); + if (handlerThread != null) { + handlerThread.quitSafely(); + } + } else if (displayWindowListener != null) { + ServiceManager.getWindowManager().unregisterDisplayWindowListener(displayWindowListener); } } @@ -133,57 +136,4 @@ public class DisplaySizeMonitor { } } } - - private void registerDisplayListenerFallbacks() { - rotationWatcher = new IRotationWatcher.Stub() { - @Override - public void onRotationChanged(int rotation) { - if (Ln.isEnabled(Ln.Level.VERBOSE)) { - Ln.v("DisplaySizeMonitor: onRotationChanged(" + rotation + ")"); - } - - checkDisplaySizeChanged(); - } - }; - ServiceManager.getWindowManager().registerRotationWatcher(rotationWatcher, displayId); - - // Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10 (but implied by == API_34_ANDROID 14) - displayFoldListener = new IDisplayFoldListener.Stub() { - - private boolean first = true; - - @Override - public void onDisplayFoldChanged(int displayId, boolean folded) { - if (first) { - // An event is posted on registration to signal the initial state. Ignore it to avoid restarting encoding. - first = false; - return; - } - - if (Ln.isEnabled(Ln.Level.VERBOSE)) { - Ln.v("DisplaySizeMonitor: onDisplayFoldChanged(" + displayId + ", " + folded + ")"); - } - - if (DisplaySizeMonitor.this.displayId != displayId) { - // Ignore events related to other display ids - return; - } - - checkDisplaySizeChanged(); - } - }; - ServiceManager.getWindowManager().registerDisplayFoldListener(displayFoldListener); - } - - private synchronized void unregisterDisplayListenerFallbacks() { - if (rotationWatcher != null) { - ServiceManager.getWindowManager().unregisterRotationWatcher(rotationWatcher); - rotationWatcher = null; - } - if (displayFoldListener != null) { - // Build.VERSION.SDK_INT >= AndroidVersions.API_29_ANDROID_10 (but implied by == API_34_ANDROID 14) - ServiceManager.getWindowManager().unregisterDisplayFoldListener(displayFoldListener); - displayFoldListener = null; - } - } } diff --git a/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java new file mode 100644 index 00000000..f2ecb158 --- /dev/null +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/DisplayWindowListener.java @@ -0,0 +1,39 @@ +package com.genymobile.scrcpy.wrappers; + +import android.content.res.Configuration; +import android.graphics.Rect; +import android.view.IDisplayWindowListener; + +import java.util.List; + +public class DisplayWindowListener extends IDisplayWindowListener.Stub { + @Override + public void onDisplayAdded(int displayId) { + // empty default implementation + } + + @Override + public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) { + // empty default implementation + } + + @Override + public void onDisplayRemoved(int displayId) { + // empty default implementation + } + + @Override + public void onFixedRotationStarted(int displayId, int newRotation) { + // empty default implementation + } + + @Override + public void onFixedRotationFinished(int displayId) { + // empty default implementation + } + + @Override + public void onKeepClearAreasChanged(int displayId, List restricted, List unrestricted) { + // empty default implementation + } +} 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 ee36139a..86dd83f2 100644 --- a/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java +++ b/server/src/main/java/com/genymobile/scrcpy/wrappers/WindowManager.java @@ -6,6 +6,7 @@ import com.genymobile.scrcpy.util.Ln; import android.annotation.TargetApi; import android.os.IInterface; import android.view.IDisplayFoldListener; +import android.view.IDisplayWindowListener; import android.view.IRotationWatcher; import java.lang.reflect.Method; @@ -226,4 +227,23 @@ public final class WindowManager { Ln.e("Could not unregister display fold listener", e); } } + + @TargetApi(AndroidVersions.API_30_ANDROID_11) + public int[] registerDisplayWindowListener(IDisplayWindowListener listener) { + try { + return (int[]) manager.getClass().getMethod("registerDisplayWindowListener", IDisplayWindowListener.class).invoke(manager, listener); + } catch (Exception e) { + Ln.e("Could not register display window listener", e); + } + return null; + } + + @TargetApi(AndroidVersions.API_30_ANDROID_11) + public void unregisterDisplayWindowListener(IDisplayWindowListener listener) { + try { + manager.getClass().getMethod("unregisterDisplayWindowListener", IDisplayWindowListener.class).invoke(manager, listener); + } catch (Exception e) { + Ln.e("Could not unregister display window listener", e); + } + } }