1 /**
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11  * express or implied. See the License for the specific language governing permissions and
12  * limitations under the License.
13  */
14 
15 package android.accessibilityservice.cts.utils;
16 
17 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.findWindowByTitleAndDisplay;
18 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
19 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 
22 import android.app.Activity;
23 import android.app.UiAutomation;
24 import android.content.Context;
25 import android.graphics.PixelFormat;
26 import android.graphics.Rect;
27 import android.hardware.display.DisplayManager;
28 import android.hardware.display.VirtualDisplay;
29 import android.media.ImageReader;
30 import android.os.Looper;
31 import android.os.SystemClock;
32 import android.util.DisplayMetrics;
33 import android.view.Display;
34 import android.view.InputDevice;
35 import android.view.MotionEvent;
36 import android.view.WindowInsets;
37 
38 import com.android.compatibility.common.util.TestUtils;
39 
40 /**
41  * Utilities needed when interacting with the display
42  */
43 public class DisplayUtils {
44     private static final int DISPLAY_ADDED_TIMEOUT_MS = 5000;
45     // Tolerance that allows for rounding differences in how various parts of
46     // Android calculate on-screen bounds given non-integer screen scaling or
47     // dp/pixel density.
48     private static final int BOUNDS_IN_SCREEN_TOLERANCE_PX = 1;
49 
getStatusBarHeight(Activity activity)50     public static int getStatusBarHeight(Activity activity) {
51         return activity.getWindow().getDecorView().getRootWindowInsets()
52                 .getInsets(WindowInsets.Type.statusBars()).top;
53     }
54 
getNavBarHeight(Activity activity)55     public static int getNavBarHeight(Activity activity) {
56         return activity.getWindow().getDecorView().getRootWindowInsets()
57                 .getInsets(WindowInsets.Type.statusBars()).bottom;
58     }
59 
60     /**
61      * Checks if the bounds origin match the provided point, to a tolerance of
62      * {@link #BOUNDS_IN_SCREEN_TOLERANCE_PX} pixels.
63      */
fuzzyBoundsInScreenSameOrigin(int[] origin, Rect bounds)64     public static boolean fuzzyBoundsInScreenSameOrigin(int[] origin, Rect bounds) {
65         return Math.abs((origin[0]) - bounds.left) <= BOUNDS_IN_SCREEN_TOLERANCE_PX
66                 && Math.abs((origin[1]) - bounds.top) <= BOUNDS_IN_SCREEN_TOLERANCE_PX;
67     }
68 
69     /**
70      * Checks if the bounds origins match each other, to a tolerance of
71      * {@link #BOUNDS_IN_SCREEN_TOLERANCE_PX} pixels.
72      */
fuzzyBoundsInScreenSameOrigin(Rect boundsA, Rect boundsB)73     public static boolean fuzzyBoundsInScreenSameOrigin(Rect boundsA, Rect boundsB) {
74         return Math.abs((boundsA.left) - boundsB.left) <= BOUNDS_IN_SCREEN_TOLERANCE_PX
75                 && Math.abs((boundsA.top) - boundsB.top) <= BOUNDS_IN_SCREEN_TOLERANCE_PX;
76     }
77 
78     /**
79      * Checks if a larger rect contains another, to a tolerance of
80      * {@link #BOUNDS_IN_SCREEN_TOLERANCE_PX} pixels.
81      */
fuzzyBoundsInScreenContains(Rect larger, Rect smaller)82     public static boolean fuzzyBoundsInScreenContains(Rect larger, Rect smaller) {
83         final Rect largerExpanded = new Rect(larger);
84         largerExpanded.inset(-BOUNDS_IN_SCREEN_TOLERANCE_PX, -BOUNDS_IN_SCREEN_TOLERANCE_PX);
85         return largerExpanded.contains(smaller);
86     }
87 
88     public static class VirtualDisplaySession implements AutoCloseable {
89         private VirtualDisplay mVirtualDisplay;
90         private ImageReader mReader;
91 
createDisplay(Context context, int width, int height, int density, boolean isPrivate)92         public Display createDisplay(Context context, int width, int height, int density,
93                 boolean isPrivate) {
94             if (mReader != null) {
95                 throw new IllegalStateException(
96                         "Only one display can be created during this session.");
97             }
98             mReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888,
99                     1 /* maxImages */);
100             int flags = isPrivate ? 0
101                     :(VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_PUBLIC);
102             mVirtualDisplay = context.getSystemService(DisplayManager.class).createVirtualDisplay(
103                     "A11yDisplay", width, height, density, mReader.getSurface(), flags);
104             return mVirtualDisplay.getDisplay();
105         }
106 
107         @Override
close()108         public void close() {
109             if (mVirtualDisplay != null) {
110                 mVirtualDisplay.release();
111             }
112             if (mReader != null) {
113                 mReader.close();
114             }
115         }
116 
117         /**
118          * Creates a virtual display having same size with default display and waits until it's
119          * in display list. The density of the virtual display is based on
120          * {@link DisplayMetrics#xdpi} so that the threshold of gesture detection is same as
121          * the default display's.
122          *
123          * @param context
124          * @param isPrivate if this display is a private display.
125          * @return virtual display.
126          *
127          * @throws IllegalStateException if called from main thread.
128          */
createDisplayWithDefaultDisplayMetricsAndWait(Context context, boolean isPrivate)129         public Display createDisplayWithDefaultDisplayMetricsAndWait(Context context,
130                 boolean isPrivate) {
131             if (Looper.myLooper() == Looper.getMainLooper()) {
132                 throw new IllegalStateException("Should not call from main thread");
133             }
134 
135             final Object waitObject = new Object();
136             final DisplayManager.DisplayListener listener = new DisplayManager.DisplayListener() {
137                 @Override
138                 public void onDisplayAdded(int i) {
139                     synchronized (waitObject) {
140                         waitObject.notifyAll();
141                     }
142                 }
143 
144                 @Override
145                 public void onDisplayRemoved(int i) {
146                 }
147 
148                 @Override
149                 public void onDisplayChanged(int i) {
150                 }
151             };
152             final DisplayManager displayManager = (DisplayManager) context.getSystemService(
153                     Context.DISPLAY_SERVICE);
154             displayManager.registerDisplayListener(listener, null);
155 
156             final DisplayMetrics metrics = new DisplayMetrics();
157             displayManager.getDisplay(DEFAULT_DISPLAY).getRealMetrics(metrics);
158             final Display display = createDisplay(context, metrics.widthPixels,
159                     metrics.heightPixels, (int) metrics.xdpi, isPrivate);
160 
161             try {
162                 TestUtils.waitOn(waitObject,
163                         () -> displayManager.getDisplay(display.getDisplayId()) != null,
164                         DISPLAY_ADDED_TIMEOUT_MS,
165                         String.format("wait for virtual display %d adding", display.getDisplayId()));
166             } finally {
167                 displayManager.unregisterDisplayListener(listener);
168             }
169             return display;
170         }
171     }
172 
touchDisplay(UiAutomation uiAutomation, int displayId, CharSequence activityTitle)173     public static void touchDisplay(UiAutomation uiAutomation, int displayId,
174             CharSequence activityTitle) {
175         final Rect areaOfActivityWindowOnDisplay = new Rect();
176         findWindowByTitleAndDisplay(uiAutomation, activityTitle, displayId)
177                 .getBoundsInScreen(areaOfActivityWindowOnDisplay);
178 
179         final int xOnScreen =
180                 areaOfActivityWindowOnDisplay.centerX();
181         final int yOnScreen =
182                 areaOfActivityWindowOnDisplay.centerY();
183         final long downEventTime = SystemClock.uptimeMillis();
184         final MotionEvent downEvent = MotionEvent.obtain(downEventTime,
185                 downEventTime, MotionEvent.ACTION_DOWN, xOnScreen, yOnScreen, 0);
186         downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
187         downEvent.setDisplayId(displayId);
188         uiAutomation.injectInputEvent(downEvent, true);
189 
190         final long upEventTime = downEventTime + 10;
191         final MotionEvent upEvent = MotionEvent.obtain(downEventTime, upEventTime,
192                 MotionEvent.ACTION_UP, xOnScreen, yOnScreen, 0);
193         upEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
194         upEvent.setDisplayId(displayId);
195         uiAutomation.injectInputEvent(upEvent, true);
196     }
197 }
198