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 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 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 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 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 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 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 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