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