1 /* 2 * Copyright (C) 2018 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.view; 18 19 import static android.view.InsetsState.ITYPE_CAPTION_BAR; 20 import static android.view.InsetsState.ITYPE_IME; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.graphics.Insets; 25 import android.graphics.Rect; 26 import android.os.Parcel; 27 import android.os.Parcelable; 28 import android.view.InsetsState.InternalInsetsType; 29 30 import java.io.PrintWriter; 31 import java.util.Objects; 32 33 /** 34 * Represents the state of a single window generating insets for clients. 35 * @hide 36 */ 37 public class InsetsSource implements Parcelable { 38 39 private final @InternalInsetsType int mType; 40 41 /** Frame of the source in screen coordinate space */ 42 private final Rect mFrame; 43 private @Nullable Rect mVisibleFrame; 44 private boolean mVisible; 45 46 private final Rect mTmpFrame = new Rect(); 47 InsetsSource(@nternalInsetsType int type)48 public InsetsSource(@InternalInsetsType int type) { 49 mType = type; 50 mFrame = new Rect(); 51 mVisible = InsetsState.getDefaultVisibility(type); 52 } 53 InsetsSource(InsetsSource other)54 public InsetsSource(InsetsSource other) { 55 mType = other.mType; 56 mFrame = new Rect(other.mFrame); 57 mVisible = other.mVisible; 58 mVisibleFrame = other.mVisibleFrame != null 59 ? new Rect(other.mVisibleFrame) 60 : null; 61 } 62 setFrame(int left, int top, int right, int bottom)63 public void setFrame(int left, int top, int right, int bottom) { 64 mFrame.set(left, top, right, bottom); 65 } 66 setFrame(Rect frame)67 public void setFrame(Rect frame) { 68 mFrame.set(frame); 69 } 70 setVisibleFrame(@ullable Rect visibleFrame)71 public void setVisibleFrame(@Nullable Rect visibleFrame) { 72 mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame; 73 } 74 setVisible(boolean visible)75 public void setVisible(boolean visible) { 76 mVisible = visible; 77 } 78 getType()79 public @InternalInsetsType int getType() { 80 return mType; 81 } 82 getFrame()83 public Rect getFrame() { 84 return mFrame; 85 } 86 getVisibleFrame()87 public @Nullable Rect getVisibleFrame() { 88 return mVisibleFrame; 89 } 90 isVisible()91 public boolean isVisible() { 92 return mVisible; 93 } 94 isUserControllable()95 boolean isUserControllable() { 96 // If mVisibleFrame is null, it will be the same area as mFrame. 97 return mVisibleFrame == null || !mVisibleFrame.isEmpty(); 98 } 99 100 /** 101 * Calculates the insets this source will cause to a client window. 102 * 103 * @param relativeFrame The frame to calculate the insets relative to. 104 * @param ignoreVisibility If true, always reports back insets even if source isn't visible. 105 * @return The resulting insets. The contract is that only one side will be occupied by a 106 * source. 107 */ calculateInsets(Rect relativeFrame, boolean ignoreVisibility)108 public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) { 109 return calculateInsets(relativeFrame, mFrame, ignoreVisibility); 110 } 111 112 /** 113 * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets. 114 */ calculateVisibleInsets(Rect relativeFrame)115 public Insets calculateVisibleInsets(Rect relativeFrame) { 116 return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame, 117 false /* ignoreVisibility */); 118 } 119 calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility)120 private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) { 121 if (!ignoreVisibility && !mVisible) { 122 return Insets.NONE; 123 } 124 // During drag-move and drag-resizing, the caption insets position may not get updated 125 // before the app frame get updated. To layout the app content correctly during drag events, 126 // we always return the insets with the corresponding height covering the top. 127 if (getType() == ITYPE_CAPTION_BAR) { 128 return Insets.of(0, frame.height(), 0, 0); 129 } 130 if (!getIntersection(frame, relativeFrame, mTmpFrame)) { 131 return Insets.NONE; 132 } 133 134 // TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout. 135 // However, we should let the policy decide from the server. 136 if (getType() == ITYPE_IME) { 137 return Insets.of(0, 0, 0, mTmpFrame.height()); 138 } 139 140 // Intersecting at top/bottom 141 if (mTmpFrame.width() == relativeFrame.width()) { 142 if (mTmpFrame.top == relativeFrame.top) { 143 return Insets.of(0, mTmpFrame.height(), 0, 0); 144 } else if (mTmpFrame.bottom == relativeFrame.bottom) { 145 return Insets.of(0, 0, 0, mTmpFrame.height()); 146 } 147 // TODO: remove when insets are shell-customizable. 148 // This is a hack that says "if this is a top-inset (eg statusbar), always apply it 149 // to the top". It is used when adjusting primary split for IME. 150 if (mTmpFrame.top == 0) { 151 return Insets.of(0, mTmpFrame.height(), 0, 0); 152 } 153 } 154 // Intersecting at left/right 155 else if (mTmpFrame.height() == relativeFrame.height()) { 156 if (mTmpFrame.left == relativeFrame.left) { 157 return Insets.of(mTmpFrame.width(), 0, 0, 0); 158 } else if (mTmpFrame.right == relativeFrame.right) { 159 return Insets.of(0, 0, mTmpFrame.width(), 0); 160 } 161 } 162 return Insets.NONE; 163 } 164 165 /** 166 * Outputs the intersection of two rectangles. The shared edges will also be counted in the 167 * intersection. 168 * 169 * @param a The first rectangle being intersected with. 170 * @param b The second rectangle being intersected with. 171 * @param out The rectangle which represents the intersection. 172 * @return {@code true} if there is any intersection. 173 */ getIntersection(@onNull Rect a, @NonNull Rect b, @NonNull Rect out)174 private static boolean getIntersection(@NonNull Rect a, @NonNull Rect b, @NonNull Rect out) { 175 if (a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom) { 176 out.left = Math.max(a.left, b.left); 177 out.top = Math.max(a.top, b.top); 178 out.right = Math.min(a.right, b.right); 179 out.bottom = Math.min(a.bottom, b.bottom); 180 return true; 181 } 182 out.setEmpty(); 183 return false; 184 } 185 dump(String prefix, PrintWriter pw)186 public void dump(String prefix, PrintWriter pw) { 187 pw.print(prefix); 188 pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType)); 189 pw.print(" frame="); pw.print(mFrame.toShortString()); 190 if (mVisibleFrame != null) { 191 pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString()); 192 } 193 pw.print(" visible="); pw.print(mVisible); 194 pw.println(); 195 } 196 197 @Override equals(Object o)198 public boolean equals(Object o) { 199 return equals(o, false); 200 } 201 202 /** 203 * @param excludeInvisibleImeFrames If {@link InsetsState#ITYPE_IME} frames should be ignored 204 * when IME is not visible. 205 */ equals(Object o, boolean excludeInvisibleImeFrames)206 public boolean equals(Object o, boolean excludeInvisibleImeFrames) { 207 if (this == o) return true; 208 if (o == null || getClass() != o.getClass()) return false; 209 210 InsetsSource that = (InsetsSource) o; 211 212 if (mType != that.mType) return false; 213 if (mVisible != that.mVisible) return false; 214 if (excludeInvisibleImeFrames && !mVisible && mType == ITYPE_IME) return true; 215 if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false; 216 return mFrame.equals(that.mFrame); 217 } 218 219 @Override hashCode()220 public int hashCode() { 221 int result = mType; 222 result = 31 * result + mFrame.hashCode(); 223 result = 31 * result + (mVisibleFrame != null ? mVisibleFrame.hashCode() : 0); 224 result = 31 * result + (mVisible ? 1 : 0); 225 return result; 226 } 227 InsetsSource(Parcel in)228 public InsetsSource(Parcel in) { 229 mType = in.readInt(); 230 if (in.readInt() != 0) { 231 mFrame = Rect.CREATOR.createFromParcel(in); 232 } else { 233 mFrame = null; 234 } 235 if (in.readInt() != 0) { 236 mVisibleFrame = Rect.CREATOR.createFromParcel(in); 237 } else { 238 mVisibleFrame = null; 239 } 240 mVisible = in.readBoolean(); 241 } 242 243 @Override describeContents()244 public int describeContents() { 245 return 0; 246 } 247 248 @Override writeToParcel(Parcel dest, int flags)249 public void writeToParcel(Parcel dest, int flags) { 250 dest.writeInt(mType); 251 if (mFrame != null) { 252 dest.writeInt(1); 253 mFrame.writeToParcel(dest, 0); 254 } else { 255 dest.writeInt(0); 256 } 257 if (mVisibleFrame != null) { 258 dest.writeInt(1); 259 mVisibleFrame.writeToParcel(dest, 0); 260 } else { 261 dest.writeInt(0); 262 } 263 dest.writeBoolean(mVisible); 264 } 265 266 @Override toString()267 public String toString() { 268 return "InsetsSource: {" 269 + "mType=" + InsetsState.typeToString(mType) 270 + ", mFrame=" + mFrame.toShortString() 271 + ", mVisible=" + mVisible 272 + "}"; 273 } 274 275 public static final @android.annotation.NonNull Creator<InsetsSource> CREATOR = new Creator<InsetsSource>() { 276 277 public InsetsSource createFromParcel(Parcel in) { 278 return new InsetsSource(in); 279 } 280 281 public InsetsSource[] newArray(int size) { 282 return new InsetsSource[size]; 283 } 284 }; 285 } 286