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