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