• 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 
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