1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.window; 18 19 import android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.RequiresPermission; 22 import android.annotation.TestApi; 23 import android.graphics.Rect; 24 import android.graphics.RectF; 25 import android.os.IBinder; 26 import android.os.InputConfig; 27 import android.util.ArrayMap; 28 import android.util.Log; 29 import android.util.Pair; 30 import android.util.SparseArray; 31 import android.view.InputWindowHandle; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 import java.util.concurrent.CountDownLatch; 36 import java.util.function.Consumer; 37 38 /** 39 * Wrapper class to provide access to WindowInfosListener within tests. 40 * 41 * @hide 42 */ 43 @TestApi 44 public class WindowInfosListenerForTest { 45 46 /** 47 * Window properties passed to {@code @WindowInfosListenerForTest#onWindowInfosChanged}. 48 */ 49 public static class WindowInfo { 50 /** 51 * The window's token. 52 */ 53 @NonNull 54 public final IBinder windowToken; 55 56 /** 57 * The window's name. 58 */ 59 @NonNull 60 public final String name; 61 62 /** 63 * The display id the window is on. 64 */ 65 public final int displayId; 66 67 /** 68 * The window's position and size in display space. 69 */ 70 @NonNull 71 public final Rect bounds; 72 73 /** 74 * True if the window is a trusted overlay. 75 */ 76 public final boolean isTrustedOverlay; 77 78 /** 79 * True if the window is visible. 80 */ 81 public final boolean isVisible; 82 WindowInfo(@onNull IBinder windowToken, @NonNull String name, int displayId, @NonNull Rect bounds, int inputConfig)83 WindowInfo(@NonNull IBinder windowToken, @NonNull String name, int displayId, 84 @NonNull Rect bounds, int inputConfig) { 85 this.windowToken = windowToken; 86 this.name = name; 87 this.displayId = displayId; 88 this.bounds = bounds; 89 this.isTrustedOverlay = (inputConfig & InputConfig.TRUSTED_OVERLAY) != 0; 90 this.isVisible = (inputConfig & InputConfig.NOT_VISIBLE) == 0; 91 } 92 93 @Override toString()94 public String toString() { 95 return name + ", frame=" + bounds 96 + ", isVisible=" + isVisible 97 + ", isTrustedOverlay=" + isTrustedOverlay 98 + ", token=" + windowToken; 99 } 100 } 101 102 private static final String TAG = "WindowInfosListenerForTest"; 103 104 private ArrayMap<Consumer<List<WindowInfo>>, WindowInfosListener> mListeners; 105 WindowInfosListenerForTest()106 public WindowInfosListenerForTest() { 107 mListeners = new ArrayMap<>(); 108 } 109 110 /** 111 * Register a listener that is called when the system's list of visible windows has changes in 112 * position or visibility. 113 * 114 * @param consumer Consumer that is called with reverse Z ordered lists of WindowInfo instances 115 * where the first value is the topmost window. 116 */ 117 @RequiresPermission(Manifest.permission.ACCESS_SURFACE_FLINGER) addWindowInfosListener( @onNull Consumer<List<WindowInfo>> consumer)118 public void addWindowInfosListener( 119 @NonNull Consumer<List<WindowInfo>> consumer) { 120 var calledWithInitialState = new CountDownLatch(1); 121 var listener = new WindowInfosListener() { 122 @Override 123 public void onWindowInfosChanged(InputWindowHandle[] windowHandles, 124 DisplayInfo[] displayInfos) { 125 try { 126 calledWithInitialState.await(); 127 } catch (InterruptedException exception) { 128 Log.e(TAG, 129 "Exception thrown while waiting for listener to be called with " 130 + "initial state"); 131 } 132 consumer.accept(buildWindowInfos(windowHandles, displayInfos)); 133 } 134 }; 135 mListeners.put(consumer, listener); 136 Pair<InputWindowHandle[], WindowInfosListener.DisplayInfo[]> initialState = 137 listener.register(); 138 consumer.accept(buildWindowInfos(initialState.first, initialState.second)); 139 calledWithInitialState.countDown(); 140 } 141 142 /** 143 * Unregisters the listener. 144 */ removeWindowInfosListener(@onNull Consumer<List<WindowInfo>> consumer)145 public void removeWindowInfosListener(@NonNull Consumer<List<WindowInfo>> consumer) { 146 WindowInfosListener listener = mListeners.remove(consumer); 147 if (listener == null) { 148 return; 149 } 150 listener.unregister(); 151 } 152 buildWindowInfos( InputWindowHandle[] windowHandles, WindowInfosListener.DisplayInfo[] displayInfos)153 private static List<WindowInfo> buildWindowInfos( 154 InputWindowHandle[] windowHandles, WindowInfosListener.DisplayInfo[] displayInfos) { 155 var windowInfos = new ArrayList<WindowInfo>(windowHandles.length); 156 157 var displayInfoById = new SparseArray<WindowInfosListener.DisplayInfo>(displayInfos.length); 158 for (var displayInfo : displayInfos) { 159 displayInfoById.put(displayInfo.mDisplayId, displayInfo); 160 } 161 162 var tmp = new RectF(); 163 for (var handle : windowHandles) { 164 var bounds = new Rect(handle.frameLeft, handle.frameTop, handle.frameRight, 165 handle.frameBottom); 166 167 // Transform bounds from physical display coordinates to logical display coordinates. 168 var display = displayInfoById.get(handle.displayId); 169 if (display != null) { 170 tmp.set(bounds); 171 display.mTransform.mapRect(tmp); 172 tmp.round(bounds); 173 } 174 175 windowInfos.add(new WindowInfo(handle.getWindowToken(), handle.name, handle.displayId, 176 bounds, handle.inputConfig)); 177 } 178 return windowInfos; 179 } 180 } 181