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