1 /*
2  * Copyright (C) 2017 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 com.android.cts.mockime;
18 
19 import android.graphics.Point;
20 import android.graphics.Rect;
21 import android.os.Bundle;
22 import android.view.Display;
23 import android.view.View;
24 import android.view.WindowInsets;
25 
26 import androidx.annotation.NonNull;
27 import androidx.annotation.Nullable;
28 
29 /**
30  * A collection of layout-related information when
31  * {@link View.OnLayoutChangeListener#onLayoutChange(View, int, int, int, int, int, int, int, int)}
32  * is called back for the input view (the view returned from {@link MockIme#onCreateInputView()}).
33  */
34 public final class ImeLayoutInfo {
35 
36     private static final String NEW_LAYOUT_KEY = "newLayout";
37     private static final String OLD_LAYOUT_KEY = "oldLayout";
38     private static final String VIEW_ORIGIN_ON_SCREEN_KEY = "viewOriginOnScreen";
39     private static final String DISPLAY_SIZE_KEY = "displaySize";
40     private static final String SYSTEM_WINDOW_INSET_KEY = "systemWindowInset";
41     private static final String STABLE_INSET_KEY = "stableInset";
42 
43     @NonNull
44     private final Rect mNewLayout;
45     @NonNull
46     private final Rect mOldLayout;
47     @Nullable
48     private Point mViewOriginOnScreen;
49     @Nullable
50     private Point mDisplaySize;
51     @Nullable
52     private Rect mSystemWindowInset;
53     @Nullable
54     private Rect mStableInset;
55 
56     /**
57      * Returns the bounding box of the {@link View} passed to
58      * {@link android.inputmethodservice.InputMethodService#onCreateInputView()} in screen
59      * coordinates.
60      *
61      * <p>Currently this method assumes that no {@link View} in the hierarchy uses
62      * transformations such as {@link View#setRotation(float)}.</p>
63      *
64      * @return Region in screen coordinates.
65      */
66     @Nullable
getInputViewBoundsInScreen()67     public Rect getInputViewBoundsInScreen() {
68         return new Rect(
69                 mViewOriginOnScreen.x, mViewOriginOnScreen.y,
70                 mViewOriginOnScreen.x + mNewLayout.width(),
71                 mViewOriginOnScreen.y + mNewLayout.height());
72     }
73 
74     /**
75      * Returns the screen area in screen coordinates that does not overlap with the system
76      * window inset, which represents the area of a full-screen window that is partially or
77      * fully obscured by the status bar, navigation bar, IME or other system windows.
78      *
79      * <p>May return {@code null} when this information is not yet ready.</p>
80      *
81      * @return Region in screen coordinates. {@code null} when it is not available
82      *
83      * @see WindowInsets#hasSystemWindowInsets()
84      * @see WindowInsets#getSystemWindowInsetBottom()
85      * @see WindowInsets#getSystemWindowInsetLeft()
86      * @see WindowInsets#getSystemWindowInsetRight()
87      * @see WindowInsets#getSystemWindowInsetTop()
88      */
89     @Nullable
getScreenRectWithoutSystemWindowInset()90     public Rect getScreenRectWithoutSystemWindowInset() {
91         if (mDisplaySize == null) {
92             return null;
93         }
94         if (mSystemWindowInset == null) {
95             return new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
96         }
97         return new Rect(mSystemWindowInset.left, mSystemWindowInset.top,
98                 mDisplaySize.x - mSystemWindowInset.right,
99                 mDisplaySize.y - mSystemWindowInset.bottom);
100     }
101 
102     /**
103      * Returns the screen area in screen coordinates that does not overlap with the stable
104      * inset, which represents the area of a full-screen window that <b>may</b> be partially or
105      * fully obscured by the system UI elements.
106      *
107      * <p>May return {@code null} when this information is not yet ready.</p>
108      *
109      * @return Region in screen coordinates. {@code null} when it is not available
110      *
111      * @see WindowInsets#hasStableInsets()
112      * @see WindowInsets#getStableInsetBottom()
113      * @see WindowInsets#getStableInsetLeft()
114      * @see WindowInsets#getStableInsetRight()
115      * @see WindowInsets#getStableInsetTop()
116      */
117     @Nullable
getScreenRectWithoutStableInset()118     public Rect getScreenRectWithoutStableInset() {
119         if (mDisplaySize == null) {
120             return null;
121         }
122         if (mStableInset == null) {
123             return new Rect(0, 0, mDisplaySize.x, mDisplaySize.y);
124         }
125         return new Rect(mStableInset.left, mStableInset.top,
126                 mDisplaySize.x - mStableInset.right,
127                 mDisplaySize.y - mStableInset.bottom);
128     }
129 
ImeLayoutInfo(@onNull Rect newLayout, @NonNull Rect oldLayout, @NonNull Point viewOriginOnScreen, @Nullable Point displaySize, @Nullable Rect systemWindowInset, @Nullable Rect stableInset)130     ImeLayoutInfo(@NonNull Rect newLayout, @NonNull Rect oldLayout,
131             @NonNull Point viewOriginOnScreen, @Nullable Point displaySize,
132             @Nullable Rect systemWindowInset, @Nullable Rect stableInset) {
133         mNewLayout = new Rect(newLayout);
134         mOldLayout = new Rect(oldLayout);
135         mViewOriginOnScreen = new Point(viewOriginOnScreen);
136         mDisplaySize = new Point(displaySize);
137         mSystemWindowInset = systemWindowInset;
138         mStableInset = stableInset;
139     }
140 
writeToBundle(@onNull Bundle bundle)141     void writeToBundle(@NonNull Bundle bundle) {
142         bundle.putParcelable(NEW_LAYOUT_KEY, mNewLayout);
143         bundle.putParcelable(OLD_LAYOUT_KEY, mOldLayout);
144         bundle.putParcelable(VIEW_ORIGIN_ON_SCREEN_KEY, mViewOriginOnScreen);
145         bundle.putParcelable(DISPLAY_SIZE_KEY, mDisplaySize);
146         bundle.putParcelable(SYSTEM_WINDOW_INSET_KEY, mSystemWindowInset);
147         bundle.putParcelable(STABLE_INSET_KEY, mStableInset);
148     }
149 
readFromBundle(@onNull Bundle bundle)150     static ImeLayoutInfo readFromBundle(@NonNull Bundle bundle) {
151         final Rect newLayout = bundle.getParcelable(NEW_LAYOUT_KEY);
152         final Rect oldLayout = bundle.getParcelable(OLD_LAYOUT_KEY);
153         final Point viewOrigin = bundle.getParcelable(VIEW_ORIGIN_ON_SCREEN_KEY);
154         final Point displaySize = bundle.getParcelable(DISPLAY_SIZE_KEY);
155         final Rect systemWindowInset = bundle.getParcelable(SYSTEM_WINDOW_INSET_KEY);
156         final Rect stableInset = bundle.getParcelable(STABLE_INSET_KEY);
157 
158         return new ImeLayoutInfo(newLayout, oldLayout, viewOrigin, displaySize, systemWindowInset,
159                 stableInset);
160     }
161 
fromLayoutListenerCallback(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom)162     static ImeLayoutInfo fromLayoutListenerCallback(View v, int left, int top, int right,
163             int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
164         final Rect newLayout = new Rect(left, top, right, bottom);
165         final Rect oldLayout = new Rect(oldLeft, oldTop, oldRight, oldBottom);
166         final int[] viewOriginArray = new int[2];
167         v.getLocationOnScreen(viewOriginArray);
168         final Point viewOrigin = new Point(viewOriginArray[0], viewOriginArray[1]);
169         final Display display = v.getDisplay();
170         final Point displaySize;
171         if (display != null) {
172             displaySize = new Point();
173             display.getRealSize(displaySize);
174         } else {
175             displaySize = null;
176         }
177         final WindowInsets windowInsets = v.getRootWindowInsets();
178         final Rect systemWindowInset;
179         if (windowInsets != null && windowInsets.hasSystemWindowInsets()) {
180             systemWindowInset = new Rect(
181                     windowInsets.getSystemWindowInsetLeft(), windowInsets.getSystemWindowInsetTop(),
182                     windowInsets.getSystemWindowInsetRight(),
183                     windowInsets.getSystemWindowInsetBottom());
184         } else {
185             systemWindowInset = null;
186         }
187         final Rect stableInset;
188         if (windowInsets != null && windowInsets.hasStableInsets()) {
189             stableInset = new Rect(
190                     windowInsets.getStableInsetLeft(), windowInsets.getStableInsetTop(),
191                     windowInsets.getStableInsetRight(), windowInsets.getStableInsetBottom());
192         } else {
193             stableInset = null;
194         }
195         return new ImeLayoutInfo(newLayout, oldLayout, viewOrigin, displaySize, systemWindowInset,
196                 stableInset);
197     }
198 }
199