• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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