WindowInsets.java revision f7b7426d81c5365473d00362cf158aa5ae35cee3
1/*
2 * Copyright (C) 2014 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
18package android.view;
19
20import android.annotation.Nullable;
21import android.graphics.Rect;
22
23import com.android.internal.util.Preconditions;
24
25import java.util.Objects;
26
27/**
28 * Describes a set of insets for window content.
29 *
30 * <p>WindowInsets are immutable and may be expanded to include more inset types in the future.
31 * To adjust insets, use one of the supplied clone methods to obtain a new WindowInsets instance
32 * with the adjusted properties.</p>
33 *
34 * <p>Note: Before {@link android.os.Build.VERSION_CODES#P P}, WindowInsets instances were only
35 * immutable during a single layout pass (i.e. would return the same values between
36 * {@link View#onApplyWindowInsets} and {@link View#onLayout}, but could return other values
37 * otherwise). Starting with {@link android.os.Build.VERSION_CODES#P P}, WindowInsets are
38 * always immutable and implement equality.
39 *
40 * @see View.OnApplyWindowInsetsListener
41 * @see View#onApplyWindowInsets(WindowInsets)
42 */
43public final class WindowInsets {
44
45    private Rect mSystemWindowInsets;
46    private Rect mWindowDecorInsets;
47    private Rect mStableInsets;
48    private Rect mTempRect;
49    private boolean mIsRound;
50    private DisplayCutout mDisplayCutout;
51
52    /**
53     * In multi-window we force show the navigation bar. Because we don't want that the surface size
54     * changes in this mode, we instead have a flag whether the navigation bar size should always
55     * be consumed, so the app is treated like there is no virtual navigation bar at all.
56     */
57    private boolean mAlwaysConsumeNavBar;
58
59    private boolean mSystemWindowInsetsConsumed = false;
60    private boolean mWindowDecorInsetsConsumed = false;
61    private boolean mStableInsetsConsumed = false;
62    private boolean mDisplayCutoutConsumed = false;
63
64    private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
65
66    /**
67     * Since new insets may be added in the future that existing apps couldn't
68     * know about, this fully empty constant shouldn't be made available to apps
69     * since it would allow them to inadvertently consume unknown insets by returning it.
70     * @hide
71     */
72    public static final WindowInsets CONSUMED;
73
74    static {
75        CONSUMED = new WindowInsets(null, null, null, false, false, null);
76    }
77
78    /** @hide */
79    public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets,
80            boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
81        mSystemWindowInsetsConsumed = systemWindowInsets == null;
82        mSystemWindowInsets = mSystemWindowInsetsConsumed
83                ? EMPTY_RECT : new Rect(systemWindowInsets);
84
85        mWindowDecorInsetsConsumed = windowDecorInsets == null;
86        mWindowDecorInsets = mWindowDecorInsetsConsumed ? EMPTY_RECT : new Rect(windowDecorInsets);
87
88        mStableInsetsConsumed = stableInsets == null;
89        mStableInsets = mStableInsetsConsumed ? EMPTY_RECT : new Rect(stableInsets);
90
91        mIsRound = isRound;
92        mAlwaysConsumeNavBar = alwaysConsumeNavBar;
93
94        mDisplayCutoutConsumed = displayCutout == null;
95        mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
96                ? null : displayCutout;
97    }
98
99    /**
100     * Construct a new WindowInsets, copying all values from a source WindowInsets.
101     *
102     * @param src Source to copy insets from
103     */
104    public WindowInsets(WindowInsets src) {
105        mSystemWindowInsets = src.mSystemWindowInsets;
106        mWindowDecorInsets = src.mWindowDecorInsets;
107        mStableInsets = src.mStableInsets;
108        mSystemWindowInsetsConsumed = src.mSystemWindowInsetsConsumed;
109        mWindowDecorInsetsConsumed = src.mWindowDecorInsetsConsumed;
110        mStableInsetsConsumed = src.mStableInsetsConsumed;
111        mIsRound = src.mIsRound;
112        mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
113        mDisplayCutout = src.mDisplayCutout;
114        mDisplayCutoutConsumed = src.mDisplayCutoutConsumed;
115    }
116
117    /** @hide */
118    public WindowInsets(Rect systemWindowInsets) {
119        this(systemWindowInsets, null, null, false, false, null);
120    }
121
122    /**
123     * Used to provide a safe copy of the system window insets to pass through
124     * to the existing fitSystemWindows method and other similar internals.
125     * @hide
126     */
127    public Rect getSystemWindowInsets() {
128        if (mTempRect == null) {
129            mTempRect = new Rect();
130        }
131        if (mSystemWindowInsets != null) {
132            mTempRect.set(mSystemWindowInsets);
133        } else {
134            // If there were no system window insets, this is just empty.
135            mTempRect.setEmpty();
136        }
137        return mTempRect;
138    }
139
140    /**
141     * Returns the left system window inset in pixels.
142     *
143     * <p>The system window inset represents the area of a full-screen window that is
144     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
145     * </p>
146     *
147     * @return The left system window inset
148     */
149    public int getSystemWindowInsetLeft() {
150        return mSystemWindowInsets.left;
151    }
152
153    /**
154     * Returns the top system window inset in pixels.
155     *
156     * <p>The system window inset represents the area of a full-screen window that is
157     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
158     * </p>
159     *
160     * @return The top system window inset
161     */
162    public int getSystemWindowInsetTop() {
163        return mSystemWindowInsets.top;
164    }
165
166    /**
167     * Returns the right system window inset in pixels.
168     *
169     * <p>The system window inset represents the area of a full-screen window that is
170     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
171     * </p>
172     *
173     * @return The right system window inset
174     */
175    public int getSystemWindowInsetRight() {
176        return mSystemWindowInsets.right;
177    }
178
179    /**
180     * Returns the bottom system window inset in pixels.
181     *
182     * <p>The system window inset represents the area of a full-screen window that is
183     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
184     * </p>
185     *
186     * @return The bottom system window inset
187     */
188    public int getSystemWindowInsetBottom() {
189        return mSystemWindowInsets.bottom;
190    }
191
192    /**
193     * Returns the left window decor inset in pixels.
194     *
195     * <p>The window decor inset represents the area of the window content area that is
196     * partially or fully obscured by decorations within the window provided by the framework.
197     * This can include action bars, title bars, toolbars, etc.</p>
198     *
199     * @return The left window decor inset
200     * @hide pending API
201     */
202    public int getWindowDecorInsetLeft() {
203        return mWindowDecorInsets.left;
204    }
205
206    /**
207     * Returns the top window decor inset in pixels.
208     *
209     * <p>The window decor inset represents the area of the window content area that is
210     * partially or fully obscured by decorations within the window provided by the framework.
211     * This can include action bars, title bars, toolbars, etc.</p>
212     *
213     * @return The top window decor inset
214     * @hide pending API
215     */
216    public int getWindowDecorInsetTop() {
217        return mWindowDecorInsets.top;
218    }
219
220    /**
221     * Returns the right window decor inset in pixels.
222     *
223     * <p>The window decor inset represents the area of the window content area that is
224     * partially or fully obscured by decorations within the window provided by the framework.
225     * This can include action bars, title bars, toolbars, etc.</p>
226     *
227     * @return The right window decor inset
228     * @hide pending API
229     */
230    public int getWindowDecorInsetRight() {
231        return mWindowDecorInsets.right;
232    }
233
234    /**
235     * Returns the bottom window decor inset in pixels.
236     *
237     * <p>The window decor inset represents the area of the window content area that is
238     * partially or fully obscured by decorations within the window provided by the framework.
239     * This can include action bars, title bars, toolbars, etc.</p>
240     *
241     * @return The bottom window decor inset
242     * @hide pending API
243     */
244    public int getWindowDecorInsetBottom() {
245        return mWindowDecorInsets.bottom;
246    }
247
248    /**
249     * Returns true if this WindowInsets has nonzero system window insets.
250     *
251     * <p>The system window inset represents the area of a full-screen window that is
252     * partially or fully obscured by the status bar, navigation bar, IME or other system windows.
253     * </p>
254     *
255     * @return true if any of the system window inset values are nonzero
256     */
257    public boolean hasSystemWindowInsets() {
258        return mSystemWindowInsets.left != 0 || mSystemWindowInsets.top != 0 ||
259                mSystemWindowInsets.right != 0 || mSystemWindowInsets.bottom != 0;
260    }
261
262    /**
263     * Returns true if this WindowInsets has nonzero window decor insets.
264     *
265     * <p>The window decor inset represents the area of the window content area that is
266     * partially or fully obscured by decorations within the window provided by the framework.
267     * This can include action bars, title bars, toolbars, etc.</p>
268     *
269     * @return true if any of the window decor inset values are nonzero
270     * @hide pending API
271     */
272    public boolean hasWindowDecorInsets() {
273        return mWindowDecorInsets.left != 0 || mWindowDecorInsets.top != 0 ||
274                mWindowDecorInsets.right != 0 || mWindowDecorInsets.bottom != 0;
275    }
276
277    /**
278     * Returns true if this WindowInsets has any nonzero insets.
279     *
280     * @return true if any inset values are nonzero
281     */
282    public boolean hasInsets() {
283        return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets()
284                || mDisplayCutout != null;
285    }
286
287    /**
288     * Returns the display cutout if there is one.
289     *
290     * @return the display cutout or null if there is none
291     * @see DisplayCutout
292     */
293    @Nullable
294    public DisplayCutout getDisplayCutout() {
295        return mDisplayCutout;
296    }
297
298    /**
299     * Returns a copy of this WindowInsets with the cutout fully consumed.
300     *
301     * @return A modified copy of this WindowInsets
302     */
303    public WindowInsets consumeDisplayCutout() {
304        final WindowInsets result = new WindowInsets(this);
305        result.mDisplayCutout = null;
306        result.mDisplayCutoutConsumed = true;
307        return result;
308    }
309
310
311    /**
312     * Check if these insets have been fully consumed.
313     *
314     * <p>Insets are considered "consumed" if the applicable <code>consume*</code> methods
315     * have been called such that all insets have been set to zero. This affects propagation of
316     * insets through the view hierarchy; insets that have not been fully consumed will continue
317     * to propagate down to child views.</p>
318     *
319     * <p>The result of this method is equivalent to the return value of
320     * {@link View#fitSystemWindows(android.graphics.Rect)}.</p>
321     *
322     * @return true if the insets have been fully consumed.
323     */
324    public boolean isConsumed() {
325        return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
326                && mDisplayCutoutConsumed;
327    }
328
329    /**
330     * Returns true if the associated window has a round shape.
331     *
332     * <p>A round window's left, top, right and bottom edges reach all the way to the
333     * associated edges of the window but the corners may not be visible. Views responding
334     * to round insets should take care to not lay out critical elements within the corners
335     * where they may not be accessible.</p>
336     *
337     * @return True if the window is round
338     */
339    public boolean isRound() {
340        return mIsRound;
341    }
342
343    /**
344     * Returns a copy of this WindowInsets with the system window insets fully consumed.
345     *
346     * @return A modified copy of this WindowInsets
347     */
348    public WindowInsets consumeSystemWindowInsets() {
349        final WindowInsets result = new WindowInsets(this);
350        result.mSystemWindowInsets = EMPTY_RECT;
351        result.mSystemWindowInsetsConsumed = true;
352        return result;
353    }
354
355    /**
356     * Returns a copy of this WindowInsets with selected system window insets fully consumed.
357     *
358     * @param left true to consume the left system window inset
359     * @param top true to consume the top system window inset
360     * @param right true to consume the right system window inset
361     * @param bottom true to consume the bottom system window inset
362     * @return A modified copy of this WindowInsets
363     * @hide pending API
364     */
365    public WindowInsets consumeSystemWindowInsets(boolean left, boolean top,
366            boolean right, boolean bottom) {
367        if (left || top || right || bottom) {
368            final WindowInsets result = new WindowInsets(this);
369            result.mSystemWindowInsets = new Rect(
370                    left ? 0 : mSystemWindowInsets.left,
371                    top ? 0 : mSystemWindowInsets.top,
372                    right ? 0 : mSystemWindowInsets.right,
373                    bottom ? 0 : mSystemWindowInsets.bottom);
374            return result;
375        }
376        return this;
377    }
378
379    /**
380     * Returns a copy of this WindowInsets with selected system window insets replaced
381     * with new values.
382     *
383     * @param left New left inset in pixels
384     * @param top New top inset in pixels
385     * @param right New right inset in pixels
386     * @param bottom New bottom inset in pixels
387     * @return A modified copy of this WindowInsets
388     */
389    public WindowInsets replaceSystemWindowInsets(int left, int top,
390            int right, int bottom) {
391        final WindowInsets result = new WindowInsets(this);
392        result.mSystemWindowInsets = new Rect(left, top, right, bottom);
393        return result;
394    }
395
396    /**
397     * Returns a copy of this WindowInsets with selected system window insets replaced
398     * with new values.
399     *
400     * @param systemWindowInsets New system window insets. Each field is the inset in pixels
401     *                           for that edge
402     * @return A modified copy of this WindowInsets
403     */
404    public WindowInsets replaceSystemWindowInsets(Rect systemWindowInsets) {
405        final WindowInsets result = new WindowInsets(this);
406        result.mSystemWindowInsets = new Rect(systemWindowInsets);
407        return result;
408    }
409
410    /**
411     * @hide
412     */
413    public WindowInsets consumeWindowDecorInsets() {
414        final WindowInsets result = new WindowInsets(this);
415        result.mWindowDecorInsets.set(0, 0, 0, 0);
416        result.mWindowDecorInsetsConsumed = true;
417        return result;
418    }
419
420    /**
421     * @hide
422     */
423    public WindowInsets consumeWindowDecorInsets(boolean left, boolean top,
424            boolean right, boolean bottom) {
425        if (left || top || right || bottom) {
426            final WindowInsets result = new WindowInsets(this);
427            result.mWindowDecorInsets = new Rect(left ? 0 : mWindowDecorInsets.left,
428                    top ? 0 : mWindowDecorInsets.top,
429                    right ? 0 : mWindowDecorInsets.right,
430                    bottom ? 0 : mWindowDecorInsets.bottom);
431            return result;
432        }
433        return this;
434    }
435
436    /**
437     * @hide
438     */
439    public WindowInsets replaceWindowDecorInsets(int left, int top, int right, int bottom) {
440        final WindowInsets result = new WindowInsets(this);
441        result.mWindowDecorInsets = new Rect(left, top, right, bottom);
442        return result;
443    }
444
445    /**
446     * Returns the top stable inset in pixels.
447     *
448     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
449     * partially or fully obscured by the system UI elements.  This value does not change
450     * based on the visibility state of those elements; for example, if the status bar is
451     * normally shown, but temporarily hidden, the stable inset will still provide the inset
452     * associated with the status bar being shown.</p>
453     *
454     * @return The top stable inset
455     */
456    public int getStableInsetTop() {
457        return mStableInsets.top;
458    }
459
460    /**
461     * Returns the left stable inset in pixels.
462     *
463     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
464     * partially or fully obscured by the system UI elements.  This value does not change
465     * based on the visibility state of those elements; for example, if the status bar is
466     * normally shown, but temporarily hidden, the stable inset will still provide the inset
467     * associated with the status bar being shown.</p>
468     *
469     * @return The left stable inset
470     */
471    public int getStableInsetLeft() {
472        return mStableInsets.left;
473    }
474
475    /**
476     * Returns the right stable inset in pixels.
477     *
478     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
479     * partially or fully obscured by the system UI elements.  This value does not change
480     * based on the visibility state of those elements; for example, if the status bar is
481     * normally shown, but temporarily hidden, the stable inset will still provide the inset
482     * associated with the status bar being shown.</p>
483     *
484     * @return The right stable inset
485     */
486    public int getStableInsetRight() {
487        return mStableInsets.right;
488    }
489
490    /**
491     * Returns the bottom stable inset in pixels.
492     *
493     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
494     * partially or fully obscured by the system UI elements.  This value does not change
495     * based on the visibility state of those elements; for example, if the status bar is
496     * normally shown, but temporarily hidden, the stable inset will still provide the inset
497     * associated with the status bar being shown.</p>
498     *
499     * @return The bottom stable inset
500     */
501    public int getStableInsetBottom() {
502        return mStableInsets.bottom;
503    }
504
505    /**
506     * Returns true if this WindowInsets has nonzero stable insets.
507     *
508     * <p>The stable inset represents the area of a full-screen window that <b>may</b> be
509     * partially or fully obscured by the system UI elements.  This value does not change
510     * based on the visibility state of those elements; for example, if the status bar is
511     * normally shown, but temporarily hidden, the stable inset will still provide the inset
512     * associated with the status bar being shown.</p>
513     *
514     * @return true if any of the stable inset values are nonzero
515     */
516    public boolean hasStableInsets() {
517        return mStableInsets.top != 0 || mStableInsets.left != 0 || mStableInsets.right != 0
518                || mStableInsets.bottom != 0;
519    }
520
521    /**
522     * Returns a copy of this WindowInsets with the stable insets fully consumed.
523     *
524     * @return A modified copy of this WindowInsets
525     */
526    public WindowInsets consumeStableInsets() {
527        final WindowInsets result = new WindowInsets(this);
528        result.mStableInsets = EMPTY_RECT;
529        result.mStableInsetsConsumed = true;
530        return result;
531    }
532
533    /**
534     * @hide
535     */
536    public boolean shouldAlwaysConsumeNavBar() {
537        return mAlwaysConsumeNavBar;
538    }
539
540    @Override
541    public String toString() {
542        return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
543                + " windowDecorInsets=" + mWindowDecorInsets
544                + " stableInsets=" + mStableInsets
545                + (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "")
546                + (isRound() ? " round" : "")
547                + "}";
548    }
549
550    /**
551     * Returns a copy of this instance inset in the given directions.
552     *
553     * @see #inset(int, int, int, int)
554     * @hide
555     */
556    public WindowInsets inset(Rect r) {
557        return inset(r.left, r.top, r.right, r.bottom);
558    }
559
560    /**
561     * Returns a copy of this instance inset in the given directions.
562     *
563     * This is intended for dispatching insets to areas of the window that are smaller than the
564     * current area.
565     *
566     * <p>Example:
567     * <pre>
568     * childView.dispatchApplyWindowInsets(insets.inset(
569     *         childMarginLeft, childMarginTop, childMarginBottom, childMarginRight));
570     * </pre>
571     *
572     * @param left the amount of insets to remove from the left. Must be non-negative.
573     * @param top the amount of insets to remove from the top. Must be non-negative.
574     * @param right the amount of insets to remove from the right. Must be non-negative.
575     * @param bottom the amount of insets to remove from the bottom. Must be non-negative.
576     *
577     * @return the inset insets
578     *
579     * @hide pending API
580     */
581    public WindowInsets inset(int left, int top, int right, int bottom) {
582        Preconditions.checkArgumentNonnegative(left);
583        Preconditions.checkArgumentNonnegative(top);
584        Preconditions.checkArgumentNonnegative(right);
585        Preconditions.checkArgumentNonnegative(bottom);
586
587        WindowInsets result = new WindowInsets(this);
588        if (!result.mSystemWindowInsetsConsumed) {
589            result.mSystemWindowInsets =
590                    insetInsets(result.mSystemWindowInsets, left, top, right, bottom);
591        }
592        if (!result.mWindowDecorInsetsConsumed) {
593            result.mWindowDecorInsets =
594                    insetInsets(result.mWindowDecorInsets, left, top, right, bottom);
595        }
596        if (!result.mStableInsetsConsumed) {
597            result.mStableInsets = insetInsets(result.mStableInsets, left, top, right, bottom);
598        }
599        if (mDisplayCutout != null) {
600            result.mDisplayCutout = result.mDisplayCutout.inset(left, top, right, bottom);
601            if (result.mDisplayCutout.isEmpty()) {
602                result.mDisplayCutout = null;
603            }
604        }
605        return result;
606    }
607
608    @Override
609    public boolean equals(Object o) {
610        if (this == o) return true;
611        if (o == null || !(o instanceof WindowInsets)) return false;
612        WindowInsets that = (WindowInsets) o;
613        return mIsRound == that.mIsRound
614                && mAlwaysConsumeNavBar == that.mAlwaysConsumeNavBar
615                && mSystemWindowInsetsConsumed == that.mSystemWindowInsetsConsumed
616                && mWindowDecorInsetsConsumed == that.mWindowDecorInsetsConsumed
617                && mStableInsetsConsumed == that.mStableInsetsConsumed
618                && mDisplayCutoutConsumed == that.mDisplayCutoutConsumed
619                && Objects.equals(mSystemWindowInsets, that.mSystemWindowInsets)
620                && Objects.equals(mWindowDecorInsets, that.mWindowDecorInsets)
621                && Objects.equals(mStableInsets, that.mStableInsets)
622                && Objects.equals(mDisplayCutout, that.mDisplayCutout);
623    }
624
625    @Override
626    public int hashCode() {
627        return Objects.hash(mSystemWindowInsets, mWindowDecorInsets, mStableInsets, mIsRound,
628                mDisplayCutout, mAlwaysConsumeNavBar, mSystemWindowInsetsConsumed,
629                mWindowDecorInsetsConsumed, mStableInsetsConsumed, mDisplayCutoutConsumed);
630    }
631
632    private static Rect insetInsets(Rect insets, int left, int top, int right, int bottom) {
633        int newLeft = Math.max(0, insets.left - left);
634        int newTop = Math.max(0, insets.top - top);
635        int newRight = Math.max(0, insets.right - right);
636        int newBottom = Math.max(0, insets.bottom - bottom);
637        if (newLeft == left && newTop == top && newRight == right && newBottom == bottom) {
638            return insets;
639        }
640        return new Rect(newLeft, newTop, newRight, newBottom);
641    }
642
643    /**
644     * @return whether system window insets have been consumed.
645     */
646    boolean isSystemWindowInsetsConsumed() {
647        return mSystemWindowInsetsConsumed;
648    }
649}
650