blob: 37101b757e66ef22fcdb32cb11b43fbe9b73efa9 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view;
import static android.view.InsetsStateProto.DISPLAY_CUTOUT;
import static android.view.InsetsStateProto.DISPLAY_FRAME;
import static android.view.InsetsStateProto.SOURCES;
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.indexOf;
import static android.view.WindowInsets.Type.isVisibleInsetsType;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowInsets.Type.systemBars;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
import android.util.SparseIntArray;
import android.util.proto.ProtoOutputStream;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
import java.util.Objects;
import java.util.StringJoiner;
/**
* Holder for state of system windows that cause window insets for all other windows in the system.
* @hide
*/
public class InsetsState implements Parcelable {
/**
* Internal representation of inset source types. This is different from the public API in
* {@link WindowInsets.Type} as one type from the public API might indicate multiple windows
* at the same time.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "ITYPE", value = {
ITYPE_STATUS_BAR,
ITYPE_NAVIGATION_BAR,
ITYPE_CAPTION_BAR,
ITYPE_TOP_GESTURES,
ITYPE_BOTTOM_GESTURES,
ITYPE_LEFT_GESTURES,
ITYPE_RIGHT_GESTURES,
ITYPE_TOP_MANDATORY_GESTURES,
ITYPE_BOTTOM_MANDATORY_GESTURES,
ITYPE_LEFT_MANDATORY_GESTURES,
ITYPE_RIGHT_MANDATORY_GESTURES,
ITYPE_TOP_TAPPABLE_ELEMENT,
ITYPE_BOTTOM_TAPPABLE_ELEMENT,
ITYPE_LEFT_DISPLAY_CUTOUT,
ITYPE_TOP_DISPLAY_CUTOUT,
ITYPE_RIGHT_DISPLAY_CUTOUT,
ITYPE_BOTTOM_DISPLAY_CUTOUT,
ITYPE_IME,
ITYPE_CLIMATE_BAR,
ITYPE_EXTRA_NAVIGATION_BAR
})
public @interface InternalInsetsType {}
/**
* Special value to be used to by methods returning an {@link InternalInsetsType} to indicate
* that the objects/parameters aren't associated with an {@link InternalInsetsType}
*/
public static final int ITYPE_INVALID = -1;
static final int FIRST_TYPE = 0;
public static final int ITYPE_STATUS_BAR = FIRST_TYPE;
public static final int ITYPE_NAVIGATION_BAR = 1;
public static final int ITYPE_CAPTION_BAR = 2;
public static final int ITYPE_TOP_GESTURES = 3;
public static final int ITYPE_BOTTOM_GESTURES = 4;
public static final int ITYPE_LEFT_GESTURES = 5;
public static final int ITYPE_RIGHT_GESTURES = 6;
public static final int ITYPE_TOP_MANDATORY_GESTURES = 7;
public static final int ITYPE_BOTTOM_MANDATORY_GESTURES = 8;
public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
public static final int ITYPE_RIGHT_MANDATORY_GESTURES = 10;
public static final int ITYPE_LEFT_DISPLAY_CUTOUT = 11;
public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12;
public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13;
public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14;
public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
public static final int ITYPE_RIGHT_TAPPABLE_ELEMENT = 17;
public static final int ITYPE_BOTTOM_TAPPABLE_ELEMENT = 18;
/** Input method window. */
public static final int ITYPE_IME = 19;
/** Additional system decorations inset type. */
public static final int ITYPE_CLIMATE_BAR = 20;
public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21;
static final int LAST_TYPE = ITYPE_EXTRA_NAVIGATION_BAR;
public static final int SIZE = LAST_TYPE + 1;
// Derived types
/** A shelf is the same as the navigation bar. */
public static final int ITYPE_SHELF = ITYPE_NAVIGATION_BAR;
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = "IINSETS_SIDE", value = {
ISIDE_LEFT,
ISIDE_TOP,
ISIDE_RIGHT,
ISIDE_BOTTOM,
ISIDE_FLOATING,
ISIDE_UNKNOWN
})
public @interface InternalInsetsSide {}
static final int ISIDE_LEFT = 0;
static final int ISIDE_TOP = 1;
static final int ISIDE_RIGHT = 2;
static final int ISIDE_BOTTOM = 3;
static final int ISIDE_FLOATING = 4;
static final int ISIDE_UNKNOWN = 5;
private final InsetsSource[] mSources = new InsetsSource[SIZE];
/**
* The frame of the display these sources are relative to.
*/
private final Rect mDisplayFrame = new Rect();
/** The area cut from the display. */
private final DisplayCutout.ParcelableWrapper mDisplayCutout =
new DisplayCutout.ParcelableWrapper();
/** The rounded corners on the display */
private RoundedCorners mRoundedCorners = RoundedCorners.NO_ROUNDED_CORNERS;
/** The bounds of the Privacy Indicator */
private PrivacyIndicatorBounds mPrivacyIndicatorBounds =
new PrivacyIndicatorBounds();
public InsetsState() {
}
public InsetsState(InsetsState copy) {
set(copy);
}
public InsetsState(InsetsState copy, boolean copySources) {
set(copy, copySources);
}
/**
* Calculates {@link WindowInsets} based on the current source configuration.
*
* @param frame The frame to calculate the insets relative to.
* @param ignoringVisibilityState {@link InsetsState} used to calculate
* {@link WindowInsets#getInsetsIgnoringVisibility(int)} information, or pass
* {@code null} to use this state to calculate that information.
* @return The calculated insets.
*/
public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState,
boolean isScreenRound, boolean alwaysConsumeSystemBars,
int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags,
int windowType, @WindowConfiguration.WindowingMode int windowingMode,
@Nullable @InternalInsetsSide SparseIntArray typeSideMap) {
Insets[] typeInsetsMap = new Insets[Type.SIZE];
Insets[] typeMaxInsetsMap = new Insets[Type.SIZE];
boolean[] typeVisibilityMap = new boolean[SIZE];
final Rect relativeFrame = new Rect(frame);
final Rect relativeFrameMax = new Rect(frame);
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources[type];
if (source == null) {
int index = indexOf(toPublicType(type));
if (typeInsetsMap[index] == null) {
typeInsetsMap[index] = Insets.NONE;
}
continue;
}
processSource(source, relativeFrame, false /* ignoreVisibility */, typeInsetsMap,
typeSideMap, typeVisibilityMap);
// IME won't be reported in max insets as the size depends on the EditorInfo of the IME
// target.
if (source.getType() != ITYPE_IME) {
InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null
? ignoringVisibilityState.getSource(type)
: source;
if (ignoringVisibilitySource == null) {
continue;
}
processSource(ignoringVisibilitySource, relativeFrameMax,
true /* ignoreVisibility */, typeMaxInsetsMap, null /* typeSideMap */,
null /* typeVisibilityMap */);
}
}
final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST;
@InsetsType int compatInsetsTypes = systemBars() | displayCutout();
if (softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE) {
compatInsetsTypes |= ime();
}
if ((legacyWindowFlags & FLAG_FULLSCREEN) != 0) {
compatInsetsTypes &= ~statusBars();
}
if (clearCompatInsets(windowType, legacyWindowFlags, windowingMode)) {
compatInsetsTypes = 0;
}
return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
alwaysConsumeSystemBars, calculateRelativeCutout(frame),
calculateRelativeRoundedCorners(frame),
calculateRelativePrivacyIndicatorBounds(frame),
compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0);
}
private DisplayCutout calculateRelativeCutout(Rect frame) {
final DisplayCutout raw = mDisplayCutout.get();
if (mDisplayFrame.equals(frame)) {
return raw;
}
if (frame == null) {
return DisplayCutout.NO_CUTOUT;
}
final int insetLeft = frame.left - mDisplayFrame.left;
final int insetTop = frame.top - mDisplayFrame.top;
final int insetRight = mDisplayFrame.right - frame.right;
final int insetBottom = mDisplayFrame.bottom - frame.bottom;
if (insetLeft >= raw.getSafeInsetLeft()
&& insetTop >= raw.getSafeInsetTop()
&& insetRight >= raw.getSafeInsetRight()
&& insetBottom >= raw.getSafeInsetBottom()) {
return DisplayCutout.NO_CUTOUT;
}
return raw.inset(insetLeft, insetTop, insetRight, insetBottom);
}
private RoundedCorners calculateRelativeRoundedCorners(Rect frame) {
if (mDisplayFrame.equals(frame)) {
return mRoundedCorners;
}
if (frame == null) {
return RoundedCorners.NO_ROUNDED_CORNERS;
}
final int insetLeft = frame.left - mDisplayFrame.left;
final int insetTop = frame.top - mDisplayFrame.top;
final int insetRight = mDisplayFrame.right - frame.right;
final int insetBottom = mDisplayFrame.bottom - frame.bottom;
return mRoundedCorners.inset(insetLeft, insetTop, insetRight, insetBottom);
}
private PrivacyIndicatorBounds calculateRelativePrivacyIndicatorBounds(Rect frame) {
if (mDisplayFrame.equals(frame)) {
return mPrivacyIndicatorBounds;
}
if (frame == null) {
return null;
}
final int insetLeft = frame.left - mDisplayFrame.left;
final int insetTop = frame.top - mDisplayFrame.top;
final int insetRight = mDisplayFrame.right - frame.right;
final int insetBottom = mDisplayFrame.bottom - frame.bottom;
return mPrivacyIndicatorBounds.inset(insetLeft, insetTop, insetRight, insetBottom);
}
public Rect calculateInsets(Rect frame, @InsetsType int types, boolean ignoreVisibility) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources[type];
if (source == null) {
continue;
}
int publicType = InsetsState.toPublicType(type);
if ((publicType & types) == 0) {
continue;
}
insets = Insets.max(source.calculateInsets(frame, ignoreVisibility), insets);
}
return insets.toRect();
}
public Rect calculateVisibleInsets(Rect frame, @SoftInputModeFlags int softInputMode) {
Insets insets = Insets.NONE;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources[type];
if (source == null) {
continue;
}
// Ignore everything that's not a system bar or IME.
int publicType = InsetsState.toPublicType(type);
if (!isVisibleInsetsType(publicType, softInputMode)) {
continue;
}
insets = Insets.max(source.calculateVisibleInsets(frame), insets);
}
return insets.toRect();
}
/**
* Calculate which insets *cannot* be controlled, because the frame does not cover the
* respective side of the inset.
*
* If the frame of our window doesn't cover the entire inset, the control API makes very
* little sense, as we don't deal with negative insets.
*/
@InsetsType
public int calculateUncontrollableInsetsFromFrame(Rect frame) {
int blocked = 0;
for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
InsetsSource source = mSources[type];
if (source == null) {
continue;
}
if (!canControlSide(frame, getInsetSide(
source.calculateInsets(frame, true /* ignoreVisibility */)))) {
blocked |= toPublicType(type);
}
}
return blocked;
}
private boolean canControlSide(Rect frame, int side) {
switch (side) {
case ISIDE_LEFT:
case ISIDE_RIGHT:
return frame.left == mDisplayFrame.left && frame.right == mDisplayFrame.right;
case ISIDE_TOP:
case ISIDE_BOTTOM:
return frame.top == mDisplayFrame.top && frame.bottom == mDisplayFrame.bottom;
case ISIDE_FLOATING:
return true;
default:
return false;
}
}
private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
Insets[] typeInsetsMap, @Nullable @InternalInsetsSide SparseIntArray typeSideMap,
@Nullable boolean[] typeVisibilityMap) {
Insets insets = source.calculateInsets(relativeFrame, ignoreVisibility);
int type = toPublicType(source.getType());
processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
insets, type);
if (type == Type.MANDATORY_SYSTEM_GESTURES) {
// Mandatory system gestures are also system gestures.
// TODO: find a way to express this more generally. One option would be to define
// Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
// ability to set systemGestureInsets() independently from
// mandatorySystemGestureInsets() in the Builder.
processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
insets, Type.SYSTEM_GESTURES);
}
}
private void processSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap,
@InternalInsetsSide @Nullable SparseIntArray typeSideMap,
@Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
int index = indexOf(type);
Insets existing = typeInsetsMap[index];
if (existing == null) {
typeInsetsMap[index] = insets;
} else {
typeInsetsMap[index] = Insets.max(existing, insets);
}
if (typeVisibilityMap != null) {
typeVisibilityMap[index] = source.isVisible();
}
if (typeSideMap != null) {
@InternalInsetsSide int insetSide = getInsetSide(insets);
if (insetSide != ISIDE_UNKNOWN) {
typeSideMap.put(source.getType(), insetSide);
}
}
}
/**
* Retrieves the side for a certain {@code insets}. It is required that only one field l/t/r/b
* is set in order that this method returns a meaningful result.
*/
static @InternalInsetsSide int getInsetSide(Insets insets) {
if (Insets.NONE.equals(insets)) {
return ISIDE_FLOATING;
}
if (insets.left != 0) {
return ISIDE_LEFT;
}
if (insets.top != 0) {
return ISIDE_TOP;
}
if (insets.right != 0) {
return ISIDE_RIGHT;
}
if (insets.bottom != 0) {
return ISIDE_BOTTOM;
}
return ISIDE_UNKNOWN;
}
public InsetsSource getSource(@InternalInsetsType int type) {
InsetsSource source = mSources[type];
if (source != null) {
return source;
}
source = new InsetsSource(type);
mSources[type] = source;
return source;
}
public @Nullable InsetsSource peekSource(@InternalInsetsType int type) {
return mSources[type];
}
/**
* Returns the source visibility or the default visibility if the source doesn't exist. This is
* useful if when treating this object as a request.
*
* @param type The {@link InternalInsetsType} to query.
* @return {@code true} if the source is visible or the type is default visible and the source
* doesn't exist.
*/
public boolean getSourceOrDefaultVisibility(@InternalInsetsType int type) {
final InsetsSource source = mSources[type];
return source != null ? source.isVisible() : getDefaultVisibility(type);
}
public void setDisplayFrame(Rect frame) {
mDisplayFrame.set(frame);
}
public Rect getDisplayFrame() {
return mDisplayFrame;
}
public void setDisplayCutout(DisplayCutout cutout) {
mDisplayCutout.set(cutout);
}
public DisplayCutout getDisplayCutout() {
return mDisplayCutout.get();
}
public void setRoundedCorners(RoundedCorners roundedCorners) {
mRoundedCorners = roundedCorners;
}
public RoundedCorners getRoundedCorners() {
return mRoundedCorners;
}
public void setPrivacyIndicatorBounds(PrivacyIndicatorBounds bounds) {
mPrivacyIndicatorBounds = bounds;
}
public PrivacyIndicatorBounds getPrivacyIndicatorBounds() {
return mPrivacyIndicatorBounds;
}
/**
* Modifies the state of this class to exclude a certain type to make it ready for dispatching
* to the client.
*
* @param type The {@link InternalInsetsType} of the source to remove
* @return {@code true} if this InsetsState was modified; {@code false} otherwise.
*/
public boolean removeSource(@InternalInsetsType int type) {
if (mSources[type] == null) {
return false;
}
mSources[type] = null;
return true;
}
/**
* A shortcut for setting the visibility of the source.
*
* @param type The {@link InternalInsetsType} of the source to set the visibility
* @param visible {@code true} for visible
*/
public void setSourceVisible(@InternalInsetsType int type, boolean visible) {
InsetsSource source = mSources[type];
if (source != null) {
source.setVisible(visible);
}
}
/**
* Scales the frame and the visible frame (if there is one) of each source.
*
* @param scale the scale to be applied
*/
public void scale(float scale) {
mDisplayFrame.scale(scale);
mDisplayCutout.scale(scale);
mRoundedCorners = mRoundedCorners.scale(scale);
mPrivacyIndicatorBounds = mPrivacyIndicatorBounds.scale(scale);
for (int i = 0; i < SIZE; i++) {
final InsetsSource source = mSources[i];
if (source != null) {
source.getFrame().scale(scale);
final Rect visibleFrame = source.getVisibleFrame();
if (visibleFrame != null) {
visibleFrame.scale(scale);
}
}
}
}
public void set(InsetsState other) {
set(other, false /* copySources */);
}
public void set(InsetsState other, boolean copySources) {
mDisplayFrame.set(other.mDisplayFrame);
mDisplayCutout.set(other.mDisplayCutout);
mRoundedCorners = other.getRoundedCorners();
mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
if (copySources) {
for (int i = 0; i < SIZE; i++) {
InsetsSource source = other.mSources[i];
mSources[i] = source != null ? new InsetsSource(source) : null;
}
} else {
for (int i = 0; i < SIZE; i++) {
mSources[i] = other.mSources[i];
}
}
}
/**
* Sets the values from the other InsetsState. But for sources, only specific types of source
* would be set.
*
* @param other the other InsetsState.
* @param types the only types of sources would be set.
*/
public void set(InsetsState other, @InsetsType int types) {
mDisplayFrame.set(other.mDisplayFrame);
mDisplayCutout.set(other.mDisplayCutout);
mRoundedCorners = other.getRoundedCorners();
mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds();
final ArraySet<Integer> t = toInternalType(types);
for (int i = t.size() - 1; i >= 0; i--) {
final int type = t.valueAt(i);
mSources[type] = other.mSources[type];
}
}
public void addSource(InsetsSource source) {
mSources[source.getType()] = source;
}
public static boolean clearCompatInsets(int windowType, int windowFlags, int windowingMode) {
return (windowFlags & FLAG_LAYOUT_NO_LIMITS) != 0
&& windowType != TYPE_WALLPAPER && windowType != TYPE_SYSTEM_ERROR
&& !WindowConfiguration.inMultiWindowMode(windowingMode);
}
public static @InternalInsetsType ArraySet<Integer> toInternalType(@InsetsType int types) {
final ArraySet<Integer> result = new ArraySet<>();
if ((types & Type.STATUS_BARS) != 0) {
result.add(ITYPE_STATUS_BAR);
result.add(ITYPE_CLIMATE_BAR);
}
if ((types & Type.NAVIGATION_BARS) != 0) {
result.add(ITYPE_NAVIGATION_BAR);
result.add(ITYPE_EXTRA_NAVIGATION_BAR);
}
if ((types & Type.CAPTION_BAR) != 0) {
result.add(ITYPE_CAPTION_BAR);
}
if ((types & Type.SYSTEM_GESTURES) != 0) {
result.add(ITYPE_LEFT_GESTURES);
result.add(ITYPE_TOP_GESTURES);
result.add(ITYPE_RIGHT_GESTURES);
result.add(ITYPE_BOTTOM_GESTURES);
}
if ((types & Type.MANDATORY_SYSTEM_GESTURES) != 0) {
result.add(ITYPE_LEFT_MANDATORY_GESTURES);
result.add(ITYPE_TOP_MANDATORY_GESTURES);
result.add(ITYPE_RIGHT_MANDATORY_GESTURES);
result.add(ITYPE_BOTTOM_MANDATORY_GESTURES);
}
if ((types & Type.DISPLAY_CUTOUT) != 0) {
result.add(ITYPE_LEFT_DISPLAY_CUTOUT);
result.add(ITYPE_TOP_DISPLAY_CUTOUT);
result.add(ITYPE_RIGHT_DISPLAY_CUTOUT);
result.add(ITYPE_BOTTOM_DISPLAY_CUTOUT);
}
if ((types & Type.IME) != 0) {
result.add(ITYPE_IME);
}
return result;
}
/**
* Converting a internal type to the public type.
* @param type internal insets type, {@code InternalInsetsType}.
* @return public insets type, {@code Type.InsetsType}.
*/
public static @Type.InsetsType int toPublicType(@InternalInsetsType int type) {
switch (type) {
case ITYPE_STATUS_BAR:
case ITYPE_CLIMATE_BAR:
return Type.STATUS_BARS;
case ITYPE_NAVIGATION_BAR:
case ITYPE_EXTRA_NAVIGATION_BAR:
return Type.NAVIGATION_BARS;
case ITYPE_CAPTION_BAR:
return Type.CAPTION_BAR;
case ITYPE_IME:
return Type.IME;
case ITYPE_TOP_MANDATORY_GESTURES:
case ITYPE_BOTTOM_MANDATORY_GESTURES:
case ITYPE_LEFT_MANDATORY_GESTURES:
case ITYPE_RIGHT_MANDATORY_GESTURES:
return Type.MANDATORY_SYSTEM_GESTURES;
case ITYPE_TOP_GESTURES:
case ITYPE_BOTTOM_GESTURES:
case ITYPE_LEFT_GESTURES:
case ITYPE_RIGHT_GESTURES:
return Type.SYSTEM_GESTURES;
case ITYPE_LEFT_TAPPABLE_ELEMENT:
case ITYPE_TOP_TAPPABLE_ELEMENT:
case ITYPE_RIGHT_TAPPABLE_ELEMENT:
case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
return Type.TAPPABLE_ELEMENT;
case ITYPE_LEFT_DISPLAY_CUTOUT:
case ITYPE_TOP_DISPLAY_CUTOUT:
case ITYPE_RIGHT_DISPLAY_CUTOUT:
case ITYPE_BOTTOM_DISPLAY_CUTOUT:
return Type.DISPLAY_CUTOUT;
default:
throw new IllegalArgumentException("Unknown type: " + type);
}
}
public static boolean getDefaultVisibility(@InternalInsetsType int type) {
return type != ITYPE_IME;
}
public static boolean containsType(@InternalInsetsType int[] types,
@InternalInsetsType int type) {
if (types == null) {
return false;
}
for (int t : types) {
if (t == type) {
return true;
}
}
return false;
}
public void dump(String prefix, PrintWriter pw) {
final String newPrefix = prefix + " ";
pw.println(prefix + "InsetsState");
pw.println(newPrefix + "mDisplayFrame=" + mDisplayFrame);
pw.println(newPrefix + "mDisplayCutout=" + mDisplayCutout.get());
pw.println(newPrefix + "mRoundedCorners=" + mRoundedCorners);
pw.println(newPrefix + "mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds);
for (int i = 0; i < SIZE; i++) {
InsetsSource source = mSources[i];
if (source == null) continue;
source.dump(newPrefix + " ", pw);
}
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
InsetsSource source = mSources[ITYPE_IME];
if (source != null) {
source.dumpDebug(proto, SOURCES);
}
mDisplayFrame.dumpDebug(proto, DISPLAY_FRAME);
mDisplayCutout.get().dumpDebug(proto, DISPLAY_CUTOUT);
proto.end(token);
}
public static String typeToString(@InternalInsetsType int type) {
switch (type) {
case ITYPE_STATUS_BAR:
return "ITYPE_STATUS_BAR";
case ITYPE_NAVIGATION_BAR:
return "ITYPE_NAVIGATION_BAR";
case ITYPE_CAPTION_BAR:
return "ITYPE_CAPTION_BAR";
case ITYPE_TOP_GESTURES:
return "ITYPE_TOP_GESTURES";
case ITYPE_BOTTOM_GESTURES:
return "ITYPE_BOTTOM_GESTURES";
case ITYPE_LEFT_GESTURES:
return "ITYPE_LEFT_GESTURES";
case ITYPE_RIGHT_GESTURES:
return "ITYPE_RIGHT_GESTURES";
case ITYPE_TOP_MANDATORY_GESTURES:
return "ITYPE_TOP_MANDATORY_GESTURES";
case ITYPE_BOTTOM_MANDATORY_GESTURES:
return "ITYPE_BOTTOM_MANDATORY_GESTURES";
case ITYPE_LEFT_MANDATORY_GESTURES:
return "ITYPE_LEFT_MANDATORY_GESTURES";
case ITYPE_RIGHT_MANDATORY_GESTURES:
return "ITYPE_RIGHT_MANDATORY_GESTURES";
case ITYPE_LEFT_TAPPABLE_ELEMENT:
return "ITYPE_LEFT_TAPPABLE_ELEMENT";
case ITYPE_TOP_TAPPABLE_ELEMENT:
return "ITYPE_TOP_TAPPABLE_ELEMENT";
case ITYPE_RIGHT_TAPPABLE_ELEMENT:
return "ITYPE_RIGHT_TAPPABLE_ELEMENT";
case ITYPE_BOTTOM_TAPPABLE_ELEMENT:
return "ITYPE_BOTTOM_TAPPABLE_ELEMENT";
case ITYPE_LEFT_DISPLAY_CUTOUT:
return "ITYPE_LEFT_DISPLAY_CUTOUT";
case ITYPE_TOP_DISPLAY_CUTOUT:
return "ITYPE_TOP_DISPLAY_CUTOUT";
case ITYPE_RIGHT_DISPLAY_CUTOUT:
return "ITYPE_RIGHT_DISPLAY_CUTOUT";
case ITYPE_BOTTOM_DISPLAY_CUTOUT:
return "ITYPE_BOTTOM_DISPLAY_CUTOUT";
case ITYPE_IME:
return "ITYPE_IME";
case ITYPE_CLIMATE_BAR:
return "ITYPE_CLIMATE_BAR";
case ITYPE_EXTRA_NAVIGATION_BAR:
return "ITYPE_EXTRA_NAVIGATION_BAR";
default:
return "ITYPE_UNKNOWN_" + type;
}
}
@Override
public boolean equals(@Nullable Object o) {
return equals(o, false, false);
}
/**
* An equals method can exclude the caption insets. This is useful because we assemble the
* caption insets information on the client side, and when we communicate with server, it's
* excluded.
* @param excludingCaptionInsets {@code true} if we want to compare two InsetsState objects but
* ignore the caption insets source value.
* @param excludeInvisibleImeFrames If {@link #ITYPE_IME} frames should be ignored when IME is
* not visible.
* @return {@code true} if the two InsetsState objects are equal, {@code false} otherwise.
*/
@VisibleForTesting
public boolean equals(@Nullable Object o, boolean excludingCaptionInsets,
boolean excludeInvisibleImeFrames) {
if (this == o) { return true; }
if (o == null || getClass() != o.getClass()) { return false; }
InsetsState state = (InsetsState) o;
if (!mDisplayFrame.equals(state.mDisplayFrame)
|| !mDisplayCutout.equals(state.mDisplayCutout)
|| !mRoundedCorners.equals(state.mRoundedCorners)
|| !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) {
return false;
}
for (int i = 0; i < SIZE; i++) {
if (excludingCaptionInsets) {
if (i == ITYPE_CAPTION_BAR) continue;
}
InsetsSource source = mSources[i];
InsetsSource otherSource = state.mSources[i];
if (source == null && otherSource == null) {
continue;
}
if (source == null || otherSource == null) {
return false;
}
if (!otherSource.equals(source, excludeInvisibleImeFrames)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources),
mRoundedCorners, mPrivacyIndicatorBounds);
}
public InsetsState(Parcel in) {
readFromParcel(in);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
mDisplayFrame.writeToParcel(dest, flags);
mDisplayCutout.writeToParcel(dest, flags);
dest.writeTypedArray(mSources, 0 /* parcelableFlags */);
dest.writeTypedObject(mRoundedCorners, flags);
dest.writeTypedObject(mPrivacyIndicatorBounds, flags);
}
public static final @NonNull Creator<InsetsState> CREATOR = new Creator<InsetsState>() {
public InsetsState createFromParcel(Parcel in) {
return new InsetsState(in);
}
public InsetsState[] newArray(int size) {
return new InsetsState[size];
}
};
public void readFromParcel(Parcel in) {
mDisplayFrame.readFromParcel(in);
mDisplayCutout.readFromParcel(in);
in.readTypedArray(mSources, InsetsSource.CREATOR);
mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR);
mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR);
}
@Override
public String toString() {
StringJoiner joiner = new StringJoiner(", ");
for (int i = 0; i < SIZE; i++) {
InsetsSource source = mSources[i];
if (source != null) {
joiner.add(source.toString());
}
}
return "InsetsState: {"
+ "mDisplayFrame=" + mDisplayFrame
+ ", mDisplayCutout=" + mDisplayCutout
+ ", mRoundedCorners=" + mRoundedCorners
+ ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds
+ ", mSources= { " + joiner
+ " }";
}
public @NonNull String toSourceVisibilityString() {
StringJoiner joiner = new StringJoiner(", ");
for (int i = 0; i < SIZE; i++) {
InsetsSource source = mSources[i];
if (source != null) {
joiner.add(typeToString(i) + ": " + (source.isVisible() ? "visible" : "invisible"));
}
}
return joiner.toString();
}
}