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