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:
parent
d72686c867
commit
39d51ff2cc
@ -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);
|
||||||
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user