diff --git a/server/src/main/aidl/android/view/IDisplayWindowListener.aidl b/server/src/main/aidl/android/view/IDisplayWindowListener.aidl new file mode 100644 index 00000000..2b331175 --- /dev/null +++ b/server/src/main/aidl/android/view/IDisplayWindowListener.aidl @@ -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 restricted, in List unrestricted); +} 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); + } + } }