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