diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 777faa7fe5aa1..085d79727975d 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3923,6 +3923,18 @@ package android.window { method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction); } + public class WindowInfosListenerForTest { + ctor public WindowInfosListenerForTest(); + method @RequiresPermission(android.Manifest.permission.ACCESS_SURFACE_FLINGER) public void addWindowInfosListener(@NonNull java.util.function.Consumer>); + method public void removeWindowInfosListener(@NonNull java.util.function.Consumer>); + } + + public static class WindowInfosListenerForTest.WindowInfo { + field @NonNull public final android.graphics.Rect bounds; + field @NonNull public final String name; + field @NonNull public final android.os.IBinder windowToken; + } + public class WindowOrganizer { ctor public WindowOrganizer(); method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback); diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java index 406f446156a94..24a0355dd10eb 100644 --- a/core/java/android/view/InputWindowHandle.java +++ b/core/java/android/view/InputWindowHandle.java @@ -244,6 +244,10 @@ public final class InputWindowHandle { window = iwindow; } + public @Nullable IBinder getWindowToken() { + return windowToken; + } + public IWindow getWindow() { if (window != null) { return window; diff --git a/core/java/android/window/WindowInfosListener.java b/core/java/android/window/WindowInfosListener.java index 8db5a5eb0e471..42bb674d9d857 100644 --- a/core/java/android/window/WindowInfosListener.java +++ b/core/java/android/window/WindowInfosListener.java @@ -16,6 +16,8 @@ package android.window; +import android.Manifest; +import android.annotation.RequiresPermission; import android.graphics.Matrix; import android.util.Pair; import android.util.Size; @@ -49,10 +51,13 @@ public abstract class WindowInfosListener { /** * Register the WindowInfosListener. * + * Registering WindowInfosListeners should only be done within system_server and shell. + * * @return The cached values for InputWindowHandles and DisplayInfos. This is the last updated * value that was sent from SurfaceFlinger to this particular process. If there was nothing * registered previously, then the data can be empty. */ + @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) public Pair register() { return nativeRegister(mNativeListener); } @@ -65,8 +70,12 @@ public abstract class WindowInfosListener { } private static native long nativeCreate(WindowInfosListener thiz); + + @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) private static native Pair nativeRegister(long ptr); + private static native void nativeUnregister(long ptr); + private static native long nativeGetFinalizer(); /** diff --git a/core/java/android/window/WindowInfosListenerForTest.java b/core/java/android/window/WindowInfosListenerForTest.java new file mode 100644 index 0000000000000..429156fb980a3 --- /dev/null +++ b/core/java/android/window/WindowInfosListenerForTest.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2023 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.window; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.annotation.TestApi; +import android.graphics.Rect; +import android.os.IBinder; +import android.os.InputConfig; +import android.util.ArrayMap; +import android.util.Log; +import android.util.Pair; +import android.view.InputWindowHandle; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +/** + * Wrapper class to provide access to WindowInfosListener within tests. + * + * @hide + */ +@TestApi +public class WindowInfosListenerForTest { + + /** + * Window properties passed to {@code @WindowInfosListenerForTest#onWindowInfosChanged}. + */ + public static class WindowInfo { + /** + * The window's token. + */ + @NonNull + public final IBinder windowToken; + + /** + * The window's name. + */ + @NonNull + public final String name; + + /** + * The window's position and size in display space. + */ + @NonNull + public final Rect bounds; + + WindowInfo(@NonNull IBinder windowToken, @NonNull String name, @NonNull Rect bounds) { + this.windowToken = windowToken; + this.name = name; + this.bounds = bounds; + } + } + + private static final String TAG = "WindowInfosListenerForTest"; + + private ArrayMap>, WindowInfosListener> mListeners; + + public WindowInfosListenerForTest() { + mListeners = new ArrayMap<>(); + } + + /** + * Register a listener that is called when the system's list of visible windows has changes in + * position or visibility. + * + * @param consumer Consumer that is called with reverse Z ordered lists of WindowInfo instances + * where the first value is the topmost window. + */ + @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) + public void addWindowInfosListener( + @NonNull Consumer> consumer) { + var calledWithInitialState = new CountDownLatch(1); + var listener = new WindowInfosListener() { + @Override + public void onWindowInfosChanged(InputWindowHandle[] windowHandles, + DisplayInfo[] displayInfos) { + try { + calledWithInitialState.await(); + } catch (InterruptedException exception) { + Log.e(TAG, + "Exception thrown while waiting for listener to be called with " + + "initial state"); + } + consumer.accept(buildWindowInfos(windowHandles)); + } + }; + mListeners.put(consumer, listener); + Pair initialState = + listener.register(); + consumer.accept(buildWindowInfos(initialState.first)); + calledWithInitialState.countDown(); + } + + /** + * Unregisters the listener. + */ + public void removeWindowInfosListener(@NonNull Consumer> consumer) { + WindowInfosListener listener = mListeners.remove(consumer); + if (listener == null) { + return; + } + listener.unregister(); + } + + private static List buildWindowInfos(InputWindowHandle[] windowHandles) { + var windowInfos = new ArrayList(windowHandles.length); + for (var handle : windowHandles) { + if ((handle.inputConfig & InputConfig.NOT_VISIBLE) != 0) { + continue; + } + var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight, + handle.frameBottom); + windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, bounds)); + } + return windowInfos; + } +}