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.

Refs #5455 comment <https://github.com/Genymobile/scrcpy/pull/5455#issuecomment-2481302084>
PR #5455 <https://github.com/Genymobile/scrcpy/pull/5455>

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 d72686c867
commit 39d51ff2cc
4 changed files with 170 additions and 95 deletions

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
import android.graphics.Rect;
import android.content.res.Configuration;
import java.util.List;
/**
* Interface to listen for changes to display window-containers.
*
* This differs from DisplayManager's DisplayListener in a couple ways:
* - onDisplayAdded is always called after the display is actually added to the WM hierarchy.
* This corresponds to the DisplayContent and not the raw Dislay from DisplayManager.
* - onDisplayConfigurationChanged is called for all configuration changes, not just changes
* to displayinfo (eg. windowing-mode).
*
*/
oneway interface IDisplayWindowListener {
/**
* Called when a new display is added to the WM hierarchy. The existing display ids are returned
* when this listener is registered with WM via {@link #registerDisplayWindowListener}.
*/
void onDisplayAdded(int displayId);
/**
* Called when a display's window-container configuration has changed.
*/
void onDisplayConfigurationChanged(int displayId, in Configuration newConfig);
/**
* Called when a display is removed from the hierarchy.
*/
void onDisplayRemoved(int displayId);
/**
* Called when fixed rotation is started on a display.
*/
void onFixedRotationStarted(int displayId, int newRotation);
/**
* Called when the previous fixed rotation on a display is finished.
*/
void onFixedRotationFinished(int displayId);
/**
* Called when the keep clear ares on a display have changed.
*/
void onKeepClearAreasChanged(int displayId, in List<Rect> restricted, in List<Rect> unrestricted);
}

View File

@ -6,13 +6,14 @@ import com.genymobile.scrcpy.device.DisplayInfo;
import com.genymobile.scrcpy.device.Size; import com.genymobile.scrcpy.device.Size;
import com.genymobile.scrcpy.util.Ln; import com.genymobile.scrcpy.util.Ln;
import com.genymobile.scrcpy.wrappers.DisplayManager; import com.genymobile.scrcpy.wrappers.DisplayManager;
import com.genymobile.scrcpy.wrappers.DisplayWindowListener;
import com.genymobile.scrcpy.wrappers.ServiceManager; import com.genymobile.scrcpy.wrappers.ServiceManager;
import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.HandlerThread; import android.os.HandlerThread;
import android.view.IDisplayFoldListener; import android.view.IDisplayWindowListener;
import android.view.IRotationWatcher;
public class DisplaySizeMonitor { public class DisplaySizeMonitor {
@ -20,15 +21,14 @@ public class DisplaySizeMonitor {
void onDisplaySizeChanged(); 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 DisplayManager.DisplayListenerHandle displayListenerHandle;
private HandlerThread handlerThread; 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 private IDisplayWindowListener displayWindowListener;
// 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 int displayId = Device.DISPLAY_ID_NONE; private int displayId = Device.DISPLAY_ID_NONE;
@ -44,31 +44,34 @@ public class DisplaySizeMonitor {
assert this.displayId == Device.DISPLAY_ID_NONE; assert this.displayId == Device.DISPLAY_ID_NONE;
this.displayId = displayId; this.displayId = displayId;
if (USE_DEFAULT_METHOD) {
handlerThread = new HandlerThread("DisplayListener"); handlerThread = new HandlerThread("DisplayListener");
handlerThread.start(); handlerThread.start();
Handler handler = new Handler(handlerThread.getLooper()); Handler handler = new Handler(handlerThread.getLooper());
if (Build.VERSION.SDK_INT == AndroidVersions.API_34_ANDROID_14) {
registerDisplayListenerFallbacks();
}
displayListenerHandle = ServiceManager.getDisplayManager().registerDisplayListener(eventDisplayId -> { displayListenerHandle = ServiceManager.getDisplayManager().registerDisplayListener(eventDisplayId -> {
if (Ln.isEnabled(Ln.Level.VERBOSE)) { if (Ln.isEnabled(Ln.Level.VERBOSE)) {
Ln.v("DisplaySizeMonitor: onDisplayChanged(" + eventDisplayId + ")"); 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 (eventDisplayId == displayId) { if (eventDisplayId == displayId) {
checkDisplaySizeChanged(); checkDisplaySizeChanged();
} }
}, handler); }, 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,10 +81,7 @@ public class DisplaySizeMonitor {
* It is ok to call this method even if {@link #start(int, Listener)} was not called. * It is ok to call this method even if {@link #start(int, Listener)} was not called.
*/ */
public void stopAndRelease() { public void stopAndRelease() {
if (Build.VERSION.SDK_INT == AndroidVersions.API_34_ANDROID_14) { if (USE_DEFAULT_METHOD) {
unregisterDisplayListenerFallbacks();
}
// displayListenerHandle may be null if registration failed // displayListenerHandle may be null if registration failed
if (displayListenerHandle != null) { if (displayListenerHandle != null) {
ServiceManager.getDisplayManager().unregisterDisplayListener(displayListenerHandle); ServiceManager.getDisplayManager().unregisterDisplayListener(displayListenerHandle);
@ -91,6 +91,9 @@ public class DisplaySizeMonitor {
if (handlerThread != null) { if (handlerThread != null) {
handlerThread.quitSafely(); handlerThread.quitSafely();
} }
} else if (displayWindowListener != null) {
ServiceManager.getWindowManager().unregisterDisplayWindowListener(displayWindowListener);
}
} }
private synchronized Size getSessionDisplaySize() { private synchronized Size getSessionDisplaySize() {
@ -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.annotation.TargetApi;
import android.os.IInterface; import android.os.IInterface;
import android.view.IDisplayFoldListener; import android.view.IDisplayFoldListener;
import android.view.IDisplayWindowListener;
import android.view.IRotationWatcher; import android.view.IRotationWatcher;
import java.lang.reflect.Method; import java.lang.reflect.Method;
@ -226,4 +227,23 @@ public final class WindowManager {
Ln.e("Could not unregister display fold listener", e); 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);
}
}
} }