Loading core/api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -58287,7 +58287,9 @@ package android.view.inspector { } public final class WindowInspector { method @FlaggedApi("android.view.flags.root_view_changed_listener") public static void addGlobalWindowViewsListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.view.View>>); method @NonNull public static java.util.List<android.view.View> getGlobalWindowViews(); method @FlaggedApi("android.view.flags.root_view_changed_listener") public static void removeGlobalWindowViewsListener(@NonNull java.util.function.Consumer<java.util.List<android.view.View>>); } } core/java/android/view/ListenerWrapper.java 0 → 100644 +56 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.annotation.NonNull; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * A utilty class to bundle a {@link Consumer} and an {@link Executor} * @param <T> the type of value to be reported. * @hide */ public class ListenerWrapper<T> { @NonNull private final Consumer<T> mConsumer; @NonNull private final Executor mExecutor; public ListenerWrapper(@NonNull Executor executor, @NonNull Consumer<T> consumer) { mExecutor = Objects.requireNonNull(executor); mConsumer = Objects.requireNonNull(consumer); } /** * Relays the new value to the {@link Consumer} using the {@link Executor} */ public void accept(@NonNull T value) { mExecutor.execute(() -> mConsumer.accept(value)); } /** * Returns {@code true} if the consumer matches the one provided in the constructor, * {@code false} otherwise. */ public boolean isConsumerSame(@NonNull Consumer<T> consumer) { return mConsumer.equals(consumer); } } core/java/android/view/WindowManagerGlobal.java +33 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.view.inputmethod.InputMethodManager; import android.view.translation.ListenerGroup; import android.window.ITrustedPresentationListener; import android.window.InputTransferToken; import android.window.TrustedPresentationThresholds; Loading @@ -58,6 +59,7 @@ import java.io.FileOutputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; Loading Loading @@ -147,6 +149,12 @@ public final class WindowManagerGlobal { @UnsupportedAppUsage private final ArrayList<View> mViews = new ArrayList<View>(); /** * The {@link ListenerGroup} that is associated to {@link #mViews}. * @hide */ @GuardedBy("mLock") private final ListenerGroup<List<View>> mWindowViewsListenerGroup = new ListenerGroup<>(); @UnsupportedAppUsage private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); @UnsupportedAppUsage Loading Loading @@ -319,6 +327,29 @@ public final class WindowManagerGlobal { } } /** * Adds a listener that will be notified whenever {@link #getWindowViews()} changes. The * current value is provided immediately. If it was registered previously then this is ano op. */ public void addWindowViewsListener(@NonNull Executor executor, @NonNull Consumer<List<View>> consumer) { synchronized (mLock) { mWindowViewsListenerGroup.addListener(executor, consumer); mWindowViewsListenerGroup.accept(getWindowViews()); } } /** * Removes a listener that was registered in * {@link #addWindowViewsListener(Executor, Consumer)}. If it was not registered previously, * then this is a no op. */ public void removeWindowViewsListener(@NonNull Consumer<List<View>> consumer) { synchronized (mLock) { mWindowViewsListenerGroup.removeListener(consumer); } } public View getWindowView(IBinder windowToken) { synchronized (mLock) { final int numViews = mViews.size(); Loading Loading @@ -454,6 +485,7 @@ public final class WindowManagerGlobal { // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView, userId); mWindowViewsListenerGroup.accept(getWindowViews()); } catch (RuntimeException e) { Log.e(TAG, "Couldn't add view: " + view, e); final int viewIndex = (index >= 0) ? index : (mViews.size() - 1); Loading Loading @@ -575,6 +607,7 @@ public final class WindowManagerGlobal { mDyingViews.remove(view); } allViewsRemoved = mRoots.isEmpty(); mWindowViewsListenerGroup.accept(getWindowViews()); } // If we don't have any views anymore in our process, we no longer need the Loading core/java/android/view/flags/view_flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -159,3 +159,11 @@ flag { bug: "364653005" is_fixed_read_only: true } flag { name: "root_view_changed_listener" namespace: "windowing_sdk" description: "Implement listener pattern for WindowInspector#getGlobalWindowViews." bug: "394397033" is_fixed_read_only: false } core/java/android/view/inspector/WindowInspector.java +24 −0 Original line number Diff line number Diff line Loading @@ -16,11 +16,14 @@ package android.view.inspector; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.view.View; import android.view.WindowManagerGlobal; import java.util.List; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * Provides access to window inspection information. Loading @@ -37,4 +40,25 @@ public final class WindowInspector { public static List<View> getGlobalWindowViews() { return WindowManagerGlobal.getInstance().getWindowViews(); } /** * Adds a listener that is notified whenever the list of global window views changes. If a * {@link Consumer} is already registered this method is a no op. * @see #getGlobalWindowViews() */ @FlaggedApi(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) public static void addGlobalWindowViewsListener(@NonNull Executor executor, @NonNull Consumer<List<View>> consumer) { WindowManagerGlobal.getInstance().addWindowViewsListener(executor, consumer); } /** * Removes a listener from getting notifications of global window views changes. If the * {@link Consumer} is not registered this method is a no op. * @see #addGlobalWindowViewsListener(Executor, Consumer) */ @FlaggedApi(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) public static void removeGlobalWindowViewsListener(@NonNull Consumer<List<View>> consumer) { WindowManagerGlobal.getInstance().removeWindowViewsListener(consumer); } } Loading
core/api/current.txt +2 −0 Original line number Diff line number Diff line Loading @@ -58287,7 +58287,9 @@ package android.view.inspector { } public final class WindowInspector { method @FlaggedApi("android.view.flags.root_view_changed_listener") public static void addGlobalWindowViewsListener(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.List<android.view.View>>); method @NonNull public static java.util.List<android.view.View> getGlobalWindowViews(); method @FlaggedApi("android.view.flags.root_view_changed_listener") public static void removeGlobalWindowViewsListener(@NonNull java.util.function.Consumer<java.util.List<android.view.View>>); } }
core/java/android/view/ListenerWrapper.java 0 → 100644 +56 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.annotation.NonNull; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * A utilty class to bundle a {@link Consumer} and an {@link Executor} * @param <T> the type of value to be reported. * @hide */ public class ListenerWrapper<T> { @NonNull private final Consumer<T> mConsumer; @NonNull private final Executor mExecutor; public ListenerWrapper(@NonNull Executor executor, @NonNull Consumer<T> consumer) { mExecutor = Objects.requireNonNull(executor); mConsumer = Objects.requireNonNull(consumer); } /** * Relays the new value to the {@link Consumer} using the {@link Executor} */ public void accept(@NonNull T value) { mExecutor.execute(() -> mConsumer.accept(value)); } /** * Returns {@code true} if the consumer matches the one provided in the constructor, * {@code false} otherwise. */ public boolean isConsumerSame(@NonNull Consumer<T> consumer) { return mConsumer.equals(consumer); } }
core/java/android/view/WindowManagerGlobal.java +33 −0 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import android.util.Log; import android.util.Pair; import android.util.SparseArray; import android.view.inputmethod.InputMethodManager; import android.view.translation.ListenerGroup; import android.window.ITrustedPresentationListener; import android.window.InputTransferToken; import android.window.TrustedPresentationThresholds; Loading @@ -58,6 +59,7 @@ import java.io.FileOutputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.WeakHashMap; import java.util.concurrent.Executor; import java.util.function.Consumer; Loading Loading @@ -147,6 +149,12 @@ public final class WindowManagerGlobal { @UnsupportedAppUsage private final ArrayList<View> mViews = new ArrayList<View>(); /** * The {@link ListenerGroup} that is associated to {@link #mViews}. * @hide */ @GuardedBy("mLock") private final ListenerGroup<List<View>> mWindowViewsListenerGroup = new ListenerGroup<>(); @UnsupportedAppUsage private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>(); @UnsupportedAppUsage Loading Loading @@ -319,6 +327,29 @@ public final class WindowManagerGlobal { } } /** * Adds a listener that will be notified whenever {@link #getWindowViews()} changes. The * current value is provided immediately. If it was registered previously then this is ano op. */ public void addWindowViewsListener(@NonNull Executor executor, @NonNull Consumer<List<View>> consumer) { synchronized (mLock) { mWindowViewsListenerGroup.addListener(executor, consumer); mWindowViewsListenerGroup.accept(getWindowViews()); } } /** * Removes a listener that was registered in * {@link #addWindowViewsListener(Executor, Consumer)}. If it was not registered previously, * then this is a no op. */ public void removeWindowViewsListener(@NonNull Consumer<List<View>> consumer) { synchronized (mLock) { mWindowViewsListenerGroup.removeListener(consumer); } } public View getWindowView(IBinder windowToken) { synchronized (mLock) { final int numViews = mViews.size(); Loading Loading @@ -454,6 +485,7 @@ public final class WindowManagerGlobal { // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView, userId); mWindowViewsListenerGroup.accept(getWindowViews()); } catch (RuntimeException e) { Log.e(TAG, "Couldn't add view: " + view, e); final int viewIndex = (index >= 0) ? index : (mViews.size() - 1); Loading Loading @@ -575,6 +607,7 @@ public final class WindowManagerGlobal { mDyingViews.remove(view); } allViewsRemoved = mRoots.isEmpty(); mWindowViewsListenerGroup.accept(getWindowViews()); } // If we don't have any views anymore in our process, we no longer need the Loading
core/java/android/view/flags/view_flags.aconfig +8 −0 Original line number Diff line number Diff line Loading @@ -159,3 +159,11 @@ flag { bug: "364653005" is_fixed_read_only: true } flag { name: "root_view_changed_listener" namespace: "windowing_sdk" description: "Implement listener pattern for WindowInspector#getGlobalWindowViews." bug: "394397033" is_fixed_read_only: false }
core/java/android/view/inspector/WindowInspector.java +24 −0 Original line number Diff line number Diff line Loading @@ -16,11 +16,14 @@ package android.view.inspector; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.view.View; import android.view.WindowManagerGlobal; import java.util.List; import java.util.concurrent.Executor; import java.util.function.Consumer; /** * Provides access to window inspection information. Loading @@ -37,4 +40,25 @@ public final class WindowInspector { public static List<View> getGlobalWindowViews() { return WindowManagerGlobal.getInstance().getWindowViews(); } /** * Adds a listener that is notified whenever the list of global window views changes. If a * {@link Consumer} is already registered this method is a no op. * @see #getGlobalWindowViews() */ @FlaggedApi(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) public static void addGlobalWindowViewsListener(@NonNull Executor executor, @NonNull Consumer<List<View>> consumer) { WindowManagerGlobal.getInstance().addWindowViewsListener(executor, consumer); } /** * Removes a listener from getting notifications of global window views changes. If the * {@link Consumer} is not registered this method is a no op. * @see #addGlobalWindowViewsListener(Executor, Consumer) */ @FlaggedApi(android.view.flags.Flags.FLAG_ROOT_VIEW_CHANGED_LISTENER) public static void removeGlobalWindowViewsListener(@NonNull Consumer<List<View>> consumer) { WindowManagerGlobal.getInstance().removeWindowViewsListener(consumer); } }