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.WindowInsets.Type.captionBar;
24 import static android.view.WindowInsets.Type.ime;
25 
26 import android.annotation.IntDef;
27 import android.annotation.IntRange;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.graphics.Insets;
31 import android.graphics.Rect;
32 import android.os.Parcel;
33 import android.os.Parcelable;
34 import android.util.proto.ProtoOutputStream;
35 import android.view.WindowInsets.Type.InsetsType;
36 
37 import java.io.PrintWriter;
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.util.Objects;
41 import java.util.StringJoiner;
42 
43 /**
44  * Represents the state of a single entity generating insets for clients.
45  * @hide
46  */
47 public class InsetsSource implements Parcelable {
48 
49     /** The insets source ID of IME */
50     public static final int ID_IME = createId(null, 0, ime());
51     /** The insets source ID of the IME caption bar ("fake" IME navigation bar). */
52     static final int ID_IME_CAPTION_BAR =
53             InsetsSource.createId(null /* owner */, 1 /* index */, captionBar());
54 
55     /**
56      * Controls whether this source suppresses the scrim. If the scrim is ignored, the system won't
57      * draw a semi-transparent scrim behind the system bar area even when the bar contrast is
58      * enforced.
59      *
60      * @see android.R.styleable#Window_enforceStatusBarContrast
61      * @see android.R.styleable#Window_enforceNavigationBarContrast
62      */
63     public static final int FLAG_SUPPRESS_SCRIM = 1;
64 
65     /**
66      * Controls whether the insets frame will be used to move {@link RoundedCorner} inward with the
67      * insets frame size when calculating the rounded corner insets to other windows.
68      *
69      * For example, task bar will draw fake rounded corners above itself, so we need to move the
70      * rounded corner up by the task bar insets size to make other windows see a rounded corner
71      * above the task bar.
72      */
73     public static final int FLAG_INSETS_ROUNDED_CORNER = 1 << 1;
74 
75     /**
76      * Controls whether the insets provided by this source should be forcibly consumed.
77      */
78     public static final int FLAG_FORCE_CONSUMING = 1 << 2;
79 
80     @Retention(RetentionPolicy.SOURCE)
81     @IntDef(flag = true, prefix = "FLAG_", value = {
82             FLAG_SUPPRESS_SCRIM,
83             FLAG_INSETS_ROUNDED_CORNER,
84             FLAG_FORCE_CONSUMING,
85     })
86     public @interface Flags {}
87 
88     private @Flags int mFlags;
89 
90     /**
91      * An unique integer to identify this source across processes.
92      */
93     private final int mId;
94 
95     private final @InsetsType int mType;
96 
97     /** Frame of the source in screen coordinate space */
98     private final Rect mFrame;
99     private @Nullable Rect mVisibleFrame;
100 
101     private boolean mVisible;
102 
103     private final Rect mTmpFrame = new Rect();
104 
InsetsSource(int id, @InsetsType int type)105     public InsetsSource(int id, @InsetsType int type) {
106         mId = id;
107         mType = type;
108         mFrame = new Rect();
109         mVisible = (WindowInsets.Type.defaultVisible() & type) != 0;
110     }
111 
InsetsSource(InsetsSource other)112     public InsetsSource(InsetsSource other) {
113         mId = other.mId;
114         mType = other.mType;
115         mFrame = new Rect(other.mFrame);
116         mVisible = other.mVisible;
117         mVisibleFrame = other.mVisibleFrame != null
118                 ? new Rect(other.mVisibleFrame)
119                 : null;
120         mFlags = other.mFlags;
121     }
122 
set(InsetsSource other)123     public void set(InsetsSource other) {
124         mFrame.set(other.mFrame);
125         mVisible = other.mVisible;
126         mVisibleFrame = other.mVisibleFrame != null
127                 ? new Rect(other.mVisibleFrame)
128                 : null;
129         mFlags = other.mFlags;
130     }
131 
setFrame(int left, int top, int right, int bottom)132     public InsetsSource setFrame(int left, int top, int right, int bottom) {
133         mFrame.set(left, top, right, bottom);
134         return this;
135     }
136 
setFrame(Rect frame)137     public InsetsSource setFrame(Rect frame) {
138         mFrame.set(frame);
139         return this;
140     }
141 
setVisibleFrame(@ullable Rect visibleFrame)142     public InsetsSource setVisibleFrame(@Nullable Rect visibleFrame) {
143         mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : null;
144         return this;
145     }
146 
setVisible(boolean visible)147     public InsetsSource setVisible(boolean visible) {
148         mVisible = visible;
149         return this;
150     }
151 
setFlags(@lags int flags)152     public InsetsSource setFlags(@Flags int flags) {
153         mFlags = flags;
154         return this;
155     }
156 
setFlags(@lags int flags, @Flags int mask)157     public InsetsSource setFlags(@Flags int flags, @Flags int mask) {
158         mFlags = (mFlags & ~mask) | (flags & mask);
159         return this;
160     }
161 
getId()162     public int getId() {
163         return mId;
164     }
165 
getType()166     public @InsetsType int getType() {
167         return mType;
168     }
169 
getFrame()170     public Rect getFrame() {
171         return mFrame;
172     }
173 
getVisibleFrame()174     public @Nullable Rect getVisibleFrame() {
175         return mVisibleFrame;
176     }
177 
isVisible()178     public boolean isVisible() {
179         return mVisible;
180     }
181 
getFlags()182     public @Flags int getFlags() {
183         return mFlags;
184     }
185 
hasFlags(int flags)186     public boolean hasFlags(int flags) {
187         return (mFlags & flags) == flags;
188     }
189 
isUserControllable()190     boolean isUserControllable() {
191         // If mVisibleFrame is null, it will be the same area as mFrame.
192         return mVisibleFrame == null || !mVisibleFrame.isEmpty();
193     }
194 
195     /**
196      * Calculates the insets this source will cause to a client window.
197      *
198      * @param relativeFrame The frame to calculate the insets relative to.
199      * @param ignoreVisibility If true, always reports back insets even if source isn't visible.
200      * @return The resulting insets. The contract is that only one side will be occupied by a
201      *         source.
202      */
calculateInsets(Rect relativeFrame, boolean ignoreVisibility)203     public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
204         return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
205     }
206 
207     /**
208      * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
209      */
calculateVisibleInsets(Rect relativeFrame)210     public Insets calculateVisibleInsets(Rect relativeFrame) {
211         return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
212                 false /* ignoreVisibility */);
213     }
214 
calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility)215     private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) {
216         if (!ignoreVisibility && !mVisible) {
217             return Insets.NONE;
218         }
219         // During drag-move and drag-resizing, the caption insets position may not get updated
220         // before the app frame get updated. To layout the app content correctly during drag events,
221         // we always return the insets with the corresponding height covering the top.
222         // However, with the "fake" IME navigation bar treated as a caption bar, we return the
223         // insets with the corresponding height the bottom.
224         if (getType() == WindowInsets.Type.captionBar()) {
225             return getId() == ID_IME_CAPTION_BAR
226                     ? Insets.of(0, 0, 0, frame.height())
227                     : Insets.of(0, frame.height(), 0, 0);
228         }
229         // Checks for whether there is shared edge with insets for 0-width/height window.
230         final boolean hasIntersection = relativeFrame.isEmpty()
231                 ? getIntersection(frame, relativeFrame, mTmpFrame)
232                 : mTmpFrame.setIntersect(frame, relativeFrame);
233         if (!hasIntersection) {
234             return Insets.NONE;
235         }
236 
237         // TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout.
238         // However, we should let the policy decide from the server.
239         if (getType() == WindowInsets.Type.ime()) {
240             return Insets.of(0, 0, 0, mTmpFrame.height());
241         }
242 
243         // Intersecting at top/bottom
244         if (mTmpFrame.width() == relativeFrame.width()) {
245             if (mTmpFrame.top == relativeFrame.top) {
246                 return Insets.of(0, mTmpFrame.height(), 0, 0);
247             } else if (mTmpFrame.bottom == relativeFrame.bottom) {
248                 return Insets.of(0, 0, 0, mTmpFrame.height());
249             }
250             // TODO: remove when insets are shell-customizable.
251             // This is a hack that says "if this is a top-inset (eg statusbar), always apply it
252             // to the top". It is used when adjusting primary split for IME.
253             if (mTmpFrame.top == 0) {
254                 return Insets.of(0, mTmpFrame.height(), 0, 0);
255             }
256         }
257         // Intersecting at left/right
258         else if (mTmpFrame.height() == relativeFrame.height()) {
259             if (mTmpFrame.left == relativeFrame.left) {
260                 return Insets.of(mTmpFrame.width(), 0, 0, 0);
261             } else if (mTmpFrame.right == relativeFrame.right) {
262                 return Insets.of(0, 0, mTmpFrame.width(), 0);
263             }
264         }
265         return Insets.NONE;
266     }
267 
268     /**
269      * Outputs the intersection of two rectangles. The shared edges will also be counted in the
270      * intersection.
271      *
272      * @param a The first rectangle being intersected with.
273      * @param b The second rectangle being intersected with.
274      * @param out The rectangle which represents the intersection.
275      * @return {@code true} if there is any intersection.
276      */
getIntersection(@onNull Rect a, @NonNull Rect b, @NonNull Rect out)277     private static boolean getIntersection(@NonNull Rect a, @NonNull Rect b, @NonNull Rect out) {
278         if (a.left <= b.right && b.left <= a.right && a.top <= b.bottom && b.top <= a.bottom) {
279             out.left = Math.max(a.left, b.left);
280             out.top = Math.max(a.top, b.top);
281             out.right = Math.min(a.right, b.right);
282             out.bottom = Math.min(a.bottom, b.bottom);
283             return true;
284         }
285         out.setEmpty();
286         return false;
287     }
288 
289     /**
290      * Creates an identifier of an {@link InsetsSource}.
291      *
292      * @param owner An object owned by the owner. Only the owner can modify its own sources.
293      * @param index An owner may have multiple sources with the same type. For example, the system
294      *              server might have multiple display cutout sources. This is used to identify
295      *              which one is which. The value must be in a range of [0, 2047].
296      * @param type The {@link InsetsType type} of the source.
297      * @return a unique integer as the identifier.
298      */
createId(Object owner, @IntRange(from = 0, to = 2047) int index, @InsetsType int type)299     public static int createId(Object owner, @IntRange(from = 0, to = 2047) int index,
300             @InsetsType int type) {
301         if (index < 0 || index >= 2048) {
302             throw new IllegalArgumentException();
303         }
304         // owner takes top 16 bits;
305         // index takes 11 bits since the 6th bit;
306         // type takes bottom 5 bits.
307         return ((System.identityHashCode(owner) % (1 << 16)) << 16)
308                 + (index << 5)
309                 + WindowInsets.Type.indexOf(type);
310     }
311 
312     /**
313      * Gets the index from the ID.
314      *
315      * @see #createId(Object, int, int)
316      */
getIndex(int id)317     public static int getIndex(int id) {
318         //   start: ????????????????***********?????
319         // & 65535: 0000000000000000***********?????
320         //    >> 5: 000000000000000000000***********
321         return (id & 65535) >> 5;
322     }
323 
324     /**
325      * Gets the {@link InsetsType} from the ID.
326      *
327      * @see #createId(Object, int, int)
328      * @see WindowInsets.Type#indexOf(int)
329      */
getType(int id)330     public static int getType(int id) {
331         // start: ???????????????????????????*****
332         //  & 31: 000000000000000000000000000*****
333         //  1 <<: See WindowInsets.Type#indexOf
334         return 1 << (id & 31);
335     }
336 
flagsToString(@lags int flags)337     public static String flagsToString(@Flags int flags) {
338         final StringJoiner joiner = new StringJoiner(" ");
339         if ((flags & FLAG_SUPPRESS_SCRIM) != 0) {
340             joiner.add("SUPPRESS_SCRIM");
341         }
342         if ((flags & FLAG_INSETS_ROUNDED_CORNER) != 0) {
343             joiner.add("INSETS_ROUNDED_CORNER");
344         }
345         if ((flags & FLAG_FORCE_CONSUMING) != 0) {
346             joiner.add("FORCE_CONSUMING");
347         }
348         return joiner.toString();
349     }
350 
351     /**
352      * Export the state of {@link InsetsSource} into a protocol buffer output stream.
353      *
354      * @param proto   Stream to write the state to
355      * @param fieldId FieldId of InsetsSource as defined in the parent message
356      */
dumpDebug(ProtoOutputStream proto, long fieldId)357     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
358         final long token = proto.start(fieldId);
359         proto.write(TYPE, WindowInsets.Type.toString(mType));
360         mFrame.dumpDebug(proto, FRAME);
361         if (mVisibleFrame != null) {
362             mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME);
363         }
364         proto.write(VISIBLE, mVisible);
365         proto.end(token);
366     }
367 
dump(String prefix, PrintWriter pw)368     public void dump(String prefix, PrintWriter pw) {
369         pw.print(prefix);
370         pw.print("InsetsSource id="); pw.print(Integer.toHexString(mId));
371         pw.print(" type="); pw.print(WindowInsets.Type.toString(mType));
372         pw.print(" frame="); pw.print(mFrame.toShortString());
373         if (mVisibleFrame != null) {
374             pw.print(" visibleFrame="); pw.print(mVisibleFrame.toShortString());
375         }
376         pw.print(" visible="); pw.print(mVisible);
377         pw.print(" flags="); pw.print(flagsToString(mFlags));
378         pw.println();
379     }
380 
381     @Override
equals(@ullable Object o)382     public boolean equals(@Nullable Object o) {
383         return equals(o, false);
384     }
385 
386     /**
387      * @param excludeInvisibleImeFrames If {@link WindowInsets.Type#ime()} frames should be ignored
388      *                                  when IME is not visible.
389      */
equals(@ullable Object o, boolean excludeInvisibleImeFrames)390     public boolean equals(@Nullable Object o, boolean excludeInvisibleImeFrames) {
391         if (this == o) return true;
392         if (o == null || getClass() != o.getClass()) return false;
393 
394         InsetsSource that = (InsetsSource) o;
395 
396         if (mId != that.mId) return false;
397         if (mType != that.mType) return false;
398         if (mVisible != that.mVisible) return false;
399         if (mFlags != that.mFlags) return false;
400         if (excludeInvisibleImeFrames && !mVisible && mType == WindowInsets.Type.ime()) return true;
401         if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
402         return mFrame.equals(that.mFrame);
403     }
404 
405     @Override
hashCode()406     public int hashCode() {
407         return Objects.hash(mId, mType, mFrame, mVisibleFrame, mVisible, mFlags);
408     }
409 
InsetsSource(Parcel in)410     public InsetsSource(Parcel in) {
411         mId = in.readInt();
412         mType = in.readInt();
413         mFrame = Rect.CREATOR.createFromParcel(in);
414         if (in.readInt() != 0) {
415             mVisibleFrame = Rect.CREATOR.createFromParcel(in);
416         } else {
417             mVisibleFrame = null;
418         }
419         mVisible = in.readBoolean();
420         mFlags = in.readInt();
421     }
422 
423     @Override
describeContents()424     public int describeContents() {
425         return 0;
426     }
427 
428     @Override
writeToParcel(Parcel dest, int flags)429     public void writeToParcel(Parcel dest, int flags) {
430         dest.writeInt(mId);
431         dest.writeInt(mType);
432         mFrame.writeToParcel(dest, 0);
433         if (mVisibleFrame != null) {
434             dest.writeInt(1);
435             mVisibleFrame.writeToParcel(dest, 0);
436         } else {
437             dest.writeInt(0);
438         }
439         dest.writeBoolean(mVisible);
440         dest.writeInt(mFlags);
441     }
442 
443     @Override
toString()444     public String toString() {
445         return "InsetsSource: {" + Integer.toHexString(mId)
446                 + " mType=" + WindowInsets.Type.toString(mType)
447                 + " mFrame=" + mFrame.toShortString()
448                 + " mVisible=" + mVisible
449                 + " mFlags=[" + flagsToString(mFlags) + "]"
450                 + "}";
451     }
452 
453     public static final @NonNull Creator<InsetsSource> CREATOR = new Creator<>() {
454 
455         public InsetsSource createFromParcel(Parcel in) {
456             return new InsetsSource(in);
457         }
458 
459         public InsetsSource[] newArray(int size) {
460             return new InsetsSource[size];
461         }
462     };
463 }
464