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 <rom@rom1v.com>
Signed-off-by: Romain Vimont <rom@rom1v.com>
This commit is contained in:
Anric 2024-11-17 21:43:32 +08:00 committed by Romain Vimont
parent 2d8c9e9fb5
commit 108abf486a
3 changed files with 104 additions and 95 deletions

View File

@ -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;
}
}
}

View File

@ -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<Rect> restricted, List<Rect> unrestricted) {
// empty default implementation
}
}

View File

@ -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);
}
}
}