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:
parent
2d8c9e9fb5
commit
108abf486a
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user