From 8e762afcd724085b963e7bcceba98217df244778 Mon Sep 17 00:00:00 2001 From: Nate Myren Date: Wed, 21 Apr 2021 12:05:28 -0700 Subject: [PATCH] Add PrivacyIndicator APIs to WindowInsets Add an API to WindowInsets that allows apps to see the maximum bounds of the system privacy indicator Fixes: 185932128 Test: atest PrivacyIndicatorBoundsTests Change-Id: I01d9e36ccbbea15752eccadfbce4c54bdd35ed5a --- core/api/current.txt | 2 + core/java/android/view/IWindowManager.aidl | 7 + core/java/android/view/InsetsController.java | 1 + core/java/android/view/InsetsState.java | 43 ++- .../android/view/PrivacyIndicatorBounds.java | 251 ++++++++++++++++++ core/java/android/view/WindowInsets.java | 69 ++++- .../src/android/view/InsetsStateTest.java | 45 ++++ .../src/android/view/WindowInsetsTest.java | 2 +- .../com/android/server/wm/DisplayContent.java | 48 +++- .../com/android/server/wm/DisplayFrames.java | 15 +- .../server/wm/WindowManagerService.java | 14 + .../server/wm/DisplayPolicyLayoutTests.java | 3 +- .../android/server/wm/DisplayPolicyTests.java | 3 +- .../server/wm/WallpaperControllerTests.java | 3 +- 14 files changed, 479 insertions(+), 27 deletions(-) create mode 100644 core/java/android/view/PrivacyIndicatorBounds.java diff --git a/core/api/current.txt b/core/api/current.txt index 03ce368fe70e2..205fd82fbb4ff 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -50260,6 +50260,7 @@ package android.view { method @NonNull public android.graphics.Insets getInsets(int); method @NonNull public android.graphics.Insets getInsetsIgnoringVisibility(int); method @Deprecated @NonNull public android.graphics.Insets getMandatorySystemGestureInsets(); + method @Nullable public android.graphics.Rect getPrivacyIndicatorBounds(); method @Nullable public android.view.RoundedCorner getRoundedCorner(int); method @Deprecated public int getStableInsetBottom(); method @Deprecated public int getStableInsetLeft(); @@ -50294,6 +50295,7 @@ package android.view { method @NonNull public android.view.WindowInsets.Builder setInsets(int, @NonNull android.graphics.Insets); method @NonNull public android.view.WindowInsets.Builder setInsetsIgnoringVisibility(int, @NonNull android.graphics.Insets) throws java.lang.IllegalArgumentException; method @Deprecated @NonNull public android.view.WindowInsets.Builder setMandatorySystemGestureInsets(@NonNull android.graphics.Insets); + method @NonNull public android.view.WindowInsets.Builder setPrivacyIndicatorBounds(@Nullable android.graphics.Rect); method @NonNull public android.view.WindowInsets.Builder setRoundedCorner(int, @Nullable android.view.RoundedCorner); method @Deprecated @NonNull public android.view.WindowInsets.Builder setStableInsets(@NonNull android.graphics.Insets); method @Deprecated @NonNull public android.view.WindowInsets.Builder setSystemGestureInsets(@NonNull android.graphics.Insets); diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index cd8248934be7f..802163617b3b9 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -384,6 +384,13 @@ interface IWindowManager */ oneway void setRecentsVisibility(boolean visible); + /** + * Called by System UI to indicate the maximum bounds of the system Privacy Indicator, for the + * current orientation, whether the indicator is showing or not. Should be an array of length + * 4, with the bounds for ROTATION_0, 90, 180, and 270, in that order. + */ + oneway void updateStaticPrivacyIndicatorBounds(int displayId, in Rect[] staticBounds); + /** * Called by System UI to enable or disable haptic feedback on the navigation bar buttons. */ diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index de7cc18531a9a..d17b42edb815f 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -690,6 +690,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mState.setDisplayFrame(newState.getDisplayFrame()); mState.setDisplayCutout(newState.getDisplayCutout()); mState.setRoundedCorners(newState.getRoundedCorners()); + mState.setPrivacyIndicatorBounds(newState.getPrivacyIndicatorBounds()); @InsetsType int disabledUserAnimationTypes = 0; @InsetsType int[] cancelledUserAnimationTypes = {0}; for (@InternalInsetsType int type = 0; type < InsetsState.SIZE; type++) { diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index f487c6c619739..37101b757e66e 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -171,6 +171,10 @@ public class InsetsState implements Parcelable { /** 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() { } @@ -243,8 +247,9 @@ public class InsetsState implements Parcelable { return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound, alwaysConsumeSystemBars, calculateRelativeCutout(frame), - calculateRelativeRoundedCorners(frame), compatInsetsTypes, - (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); + calculateRelativeRoundedCorners(frame), + calculateRelativePrivacyIndicatorBounds(frame), + compatInsetsTypes, (legacySystemUiFlags & SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0); } private DisplayCutout calculateRelativeCutout(Rect frame) { @@ -282,6 +287,20 @@ public class InsetsState implements Parcelable { 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++) { @@ -471,6 +490,14 @@ public class InsetsState implements Parcelable { 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. @@ -508,6 +535,7 @@ public class InsetsState implements Parcelable { 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) { @@ -528,6 +556,7 @@ public class InsetsState implements Parcelable { 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]; @@ -551,6 +580,7 @@ public class InsetsState implements Parcelable { mDisplayFrame.set(other.mDisplayFrame); mDisplayCutout.set(other.mDisplayCutout); mRoundedCorners = other.getRoundedCorners(); + mPrivacyIndicatorBounds = other.getPrivacyIndicatorBounds(); final ArraySet t = toInternalType(types); for (int i = t.size() - 1; i >= 0; i--) { final int type = t.valueAt(i); @@ -670,6 +700,7 @@ public class InsetsState implements Parcelable { 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; @@ -764,7 +795,8 @@ public class InsetsState implements Parcelable { if (!mDisplayFrame.equals(state.mDisplayFrame) || !mDisplayCutout.equals(state.mDisplayCutout) - || !mRoundedCorners.equals(state.mRoundedCorners)) { + || !mRoundedCorners.equals(state.mRoundedCorners) + || !mPrivacyIndicatorBounds.equals(state.mPrivacyIndicatorBounds)) { return false; } for (int i = 0; i < SIZE; i++) { @@ -789,7 +821,7 @@ public class InsetsState implements Parcelable { @Override public int hashCode() { return Objects.hash(mDisplayFrame, mDisplayCutout, Arrays.hashCode(mSources), - mRoundedCorners); + mRoundedCorners, mPrivacyIndicatorBounds); } public InsetsState(Parcel in) { @@ -807,6 +839,7 @@ public class InsetsState implements Parcelable { mDisplayCutout.writeToParcel(dest, flags); dest.writeTypedArray(mSources, 0 /* parcelableFlags */); dest.writeTypedObject(mRoundedCorners, flags); + dest.writeTypedObject(mPrivacyIndicatorBounds, flags); } public static final @NonNull Creator CREATOR = new Creator() { @@ -825,6 +858,7 @@ public class InsetsState implements Parcelable { mDisplayCutout.readFromParcel(in); in.readTypedArray(mSources, InsetsSource.CREATOR); mRoundedCorners = in.readTypedObject(RoundedCorners.CREATOR); + mPrivacyIndicatorBounds = in.readTypedObject(PrivacyIndicatorBounds.CREATOR); } @Override @@ -840,6 +874,7 @@ public class InsetsState implements Parcelable { + "mDisplayFrame=" + mDisplayFrame + ", mDisplayCutout=" + mDisplayCutout + ", mRoundedCorners=" + mRoundedCorners + + ", mPrivacyIndicatorBounds=" + mPrivacyIndicatorBounds + ", mSources= { " + joiner + " }"; } diff --git a/core/java/android/view/PrivacyIndicatorBounds.java b/core/java/android/view/PrivacyIndicatorBounds.java new file mode 100644 index 0000000000000..4cf5eda387efd --- /dev/null +++ b/core/java/android/view/PrivacyIndicatorBounds.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2021 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.Surface.ROTATION_0; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.os.Parcelable; + +import com.android.internal.util.ArrayUtils; +import com.android.internal.util.DataClass; + +/** + * A class which manages the bounds of the privacy indicator + * + * @hide + */ +@DataClass( + genEqualsHashCode = true, + genConstructor = false, + genAidl = false, + genGetters = false +) +public class PrivacyIndicatorBounds implements Parcelable { + + private final @NonNull Rect[] mStaticBounds; + private final int mRotation; + + public PrivacyIndicatorBounds() { + mStaticBounds = new Rect[4]; + mRotation = ROTATION_0; + } + + public PrivacyIndicatorBounds(@NonNull Rect[] staticBounds, @Surface.Rotation int rotation) { + mStaticBounds = staticBounds; + mRotation = rotation; + } + + /** + * Return a PrivacyIndicatorBounds with updated static bounds + */ + public PrivacyIndicatorBounds updateStaticBounds(@NonNull Rect[] staticPositions) { + return new PrivacyIndicatorBounds(staticPositions, mRotation); + } + + /** + * Update the bounds of the indicator for a specific rotation, or ROTATION_0, if the provided + * rotation integer isn't found + */ + public PrivacyIndicatorBounds updateBoundsForRotation(@Nullable Rect bounds, + @Surface.Rotation int rotation) { + if (rotation >= mStaticBounds.length || rotation < 0) { + return this; + } + Rect[] newBounds = ArrayUtils.cloneOrNull(mStaticBounds); + newBounds[rotation] = bounds; + return updateStaticBounds(newBounds); + } + + /** + * Return an inset PrivacyIndicatorBounds + */ + public PrivacyIndicatorBounds inset(int insetLeft, int insetTop, int insetRight, + int insetBottom) { + if (insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) { + return this; + } + Rect[] insetStaticBounds = new Rect[mStaticBounds.length]; + for (int i = 0; i < mStaticBounds.length; i++) { + insetStaticBounds[i] = + insetRect(mStaticBounds[i], insetLeft, insetTop, insetRight, insetBottom); + } + return updateStaticBounds(insetStaticBounds); + } + + private static Rect insetRect(Rect orig, int insetLeft, int insetTop, int insetRight, + int insetBottom) { + if (orig == null) { + return null; + } + int left = Math.max(0, orig.left - insetLeft); + int top = Math.max(0, orig.top - insetTop); + int right = Math.max(left, orig.right - insetRight); + int bottom = Math.max(top, orig.bottom - insetBottom); + return new Rect(left, top, right, bottom); + } + + /** + * Return a PrivacyIndicatorBounds with the static position rotated. + */ + public PrivacyIndicatorBounds rotate(@Surface.Rotation int rotation) { + if (rotation == ROTATION_0) { + return this; + } + return new PrivacyIndicatorBounds(mStaticBounds, rotation); + } + + /** + * Returns a scaled PrivacyIndicatorBounds + */ + public PrivacyIndicatorBounds scale(float scale) { + if (scale == 1f) { + return this; + } + + Rect[] scaledStaticPos = new Rect[mStaticBounds.length]; + for (int i = 0; i < mStaticBounds.length; i++) { + scaledStaticPos[i] = scaleRect(mStaticBounds[i], scale); + } + return new PrivacyIndicatorBounds(scaledStaticPos, mRotation); + } + + private static Rect scaleRect(Rect orig, float scale) { + if (orig == null) { + return null; + } + + Rect newRect = new Rect(orig); + newRect.scale(scale); + return newRect; + } + + public Rect getStaticPrivacyIndicatorBounds() { + return mStaticBounds[mRotation]; + } + + @Override + public String toString() { + return "PrivacyIndicatorBounds {static bounds=" + getStaticPrivacyIndicatorBounds() + + " rotation=" + mRotation + "}"; + } + + + + // Code below generated by codegen v1.0.23. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/view/PrivacyIndicatorBounds.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(PrivacyIndicatorBounds other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + PrivacyIndicatorBounds that = (PrivacyIndicatorBounds) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Arrays.equals(mStaticBounds, that.mStaticBounds) + && mRotation == that.mRotation; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + java.util.Arrays.hashCode(mStaticBounds); + _hash = 31 * _hash + mRotation; + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeTypedArray(mStaticBounds, flags); + dest.writeInt(mRotation); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected PrivacyIndicatorBounds(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + Rect[] staticBounds = (Rect[]) in.createTypedArray(Rect.CREATOR); + int rotation = in.readInt(); + + this.mStaticBounds = staticBounds; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mStaticBounds); + this.mRotation = rotation; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator CREATOR + = new Parcelable.Creator() { + @Override + public PrivacyIndicatorBounds[] newArray(int size) { + return new PrivacyIndicatorBounds[size]; + } + + @Override + public PrivacyIndicatorBounds createFromParcel(@NonNull android.os.Parcel in) { + return new PrivacyIndicatorBounds(in); + } + }; + + @DataClass.Generated( + time = 1621526273838L, + codegenVersion = "1.0.23", + sourceFile = "frameworks/base/core/java/android/view/PrivacyIndicatorBounds.java", + inputSignatures = "private final @android.annotation.NonNull android.graphics.Rect[] mStaticBounds\nprivate final int mRotation\npublic android.view.PrivacyIndicatorBounds updateStaticBounds(android.graphics.Rect[])\npublic android.view.PrivacyIndicatorBounds updateBoundsForRotation(android.graphics.Rect,int)\npublic android.view.PrivacyIndicatorBounds inset(int,int,int,int)\nprivate static android.graphics.Rect insetRect(android.graphics.Rect,int,int,int,int)\npublic android.view.PrivacyIndicatorBounds rotate(int)\npublic android.view.PrivacyIndicatorBounds scale(float)\nprivate static android.graphics.Rect scaleRect(android.graphics.Rect,float)\npublic android.graphics.Rect getStaticPrivacyIndicatorBounds()\npublic @java.lang.Override java.lang.String toString()\nclass PrivacyIndicatorBounds extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genConstructor=false, genAidl=false, genGetters=false)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java index 41c38a113e051..0f1a9d9c0a986 100644 --- a/core/java/android/view/WindowInsets.java +++ b/core/java/android/view/WindowInsets.java @@ -17,6 +17,7 @@ package android.view; +import static android.view.Surface.ROTATION_0; import static android.view.WindowInsets.Type.DISPLAY_CUTOUT; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.IME; @@ -83,6 +84,7 @@ public final class WindowInsets { private final boolean mIsRound; @Nullable private final DisplayCutout mDisplayCutout; @Nullable private final RoundedCorners mRoundedCorners; + @Nullable private final PrivacyIndicatorBounds mPrivacyIndicatorBounds; /** * In multi-window we force show the navigation bar. Because we don't want that the surface size @@ -131,8 +133,8 @@ public final class WindowInsets { boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) { this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect), createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)), - isRound, alwaysConsumeSystemBars, displayCutout, null, systemBars(), - false /* compatIgnoreVisibility */); + isRound, alwaysConsumeSystemBars, displayCutout, null, null, + systemBars(), false /* compatIgnoreVisibility */); } /** @@ -152,8 +154,9 @@ public final class WindowInsets { boolean[] typeVisibilityMap, boolean isRound, boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, - RoundedCorners roundedCorners, @InsetsType int compatInsetsTypes, - boolean compatIgnoreVisibility) { + RoundedCorners roundedCorners, + PrivacyIndicatorBounds privacyIndicatorBounds, + @InsetsType int compatInsetsTypes, boolean compatIgnoreVisibility) { mSystemWindowInsetsConsumed = typeInsetsMap == null; mTypeInsetsMap = mSystemWindowInsetsConsumed ? new Insets[SIZE] @@ -175,6 +178,7 @@ public final class WindowInsets { ? null : displayCutout; mRoundedCorners = roundedCorners; + mPrivacyIndicatorBounds = privacyIndicatorBounds; } /** @@ -188,6 +192,7 @@ public final class WindowInsets { src.mTypeVisibilityMap, src.mIsRound, src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src), src.mRoundedCorners, + src.mPrivacyIndicatorBounds, src.mCompatInsetsTypes, src.mCompatIgnoreVisibility); } @@ -241,7 +246,7 @@ public final class WindowInsets { @UnsupportedAppUsage public WindowInsets(Rect systemWindowInsets) { this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null, - null, systemBars(), false /* compatIgnoreVisibility */); + null, null, systemBars(), false /* compatIgnoreVisibility */); } /** @@ -509,6 +514,19 @@ public final class WindowInsets { return mRoundedCorners == null ? null : mRoundedCorners.getRoundedCorner(position); } + /** + * Returns the {@link Rect} of the maximum bounds of the system privacy indicator, for the + * current orientation, in relative coordinates, or null if the bounds have not been loaded yet. + * The privacy indicator shows over apps when an app uses the microphone or camera permissions, + * while an app is in immersive mode. + * + * @return A rectangle representing the maximum bounds of the indicator + */ + public @Nullable Rect getPrivacyIndicatorBounds() { + return mPrivacyIndicatorBounds == null ? null + : mPrivacyIndicatorBounds.getStaticPrivacyIndicatorBounds(); + } + /** * Returns a copy of this WindowInsets with the cutout fully consumed. * @@ -524,7 +542,7 @@ public final class WindowInsets { mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, - null /* displayCutout */, mRoundedCorners, + null /* displayCutout */, mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -576,7 +594,8 @@ public final class WindowInsets { mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(this), - mRoundedCorners, mCompatInsetsTypes, mCompatIgnoreVisibility); + mRoundedCorners, mPrivacyIndicatorBounds, mCompatInsetsTypes, + mCompatIgnoreVisibility); } // TODO(b/119190588): replace @code with @link below @@ -881,6 +900,9 @@ public final class WindowInsets { result.append("\n "); result.append(mRoundedCorners != null ? "roundedCorners=" + mRoundedCorners : ""); result.append("\n "); + result.append(mPrivacyIndicatorBounds != null ? "privacyIndicatorBounds=" + + mPrivacyIndicatorBounds : ""); + result.append("\n "); result.append(isRound() ? "round" : ""); result.append("}"); return result.toString(); @@ -975,6 +997,9 @@ public final class WindowInsets { mRoundedCorners == null ? RoundedCorners.NO_ROUNDED_CORNERS : mRoundedCorners.inset(left, top, right, bottom), + mPrivacyIndicatorBounds == null + ? null + : mPrivacyIndicatorBounds.inset(left, top, right, bottom), mCompatInsetsTypes, mCompatIgnoreVisibility); } @@ -993,7 +1018,8 @@ public final class WindowInsets { && Arrays.equals(mTypeMaxInsetsMap, that.mTypeMaxInsetsMap) && Arrays.equals(mTypeVisibilityMap, that.mTypeVisibilityMap) && Objects.equals(mDisplayCutout, that.mDisplayCutout) - && Objects.equals(mRoundedCorners, that.mRoundedCorners); + && Objects.equals(mRoundedCorners, that.mRoundedCorners) + && Objects.equals(mPrivacyIndicatorBounds, that.mPrivacyIndicatorBounds); } @Override @@ -1001,7 +1027,7 @@ public final class WindowInsets { return Objects.hash(Arrays.hashCode(mTypeInsetsMap), Arrays.hashCode(mTypeMaxInsetsMap), Arrays.hashCode(mTypeVisibilityMap), mIsRound, mDisplayCutout, mRoundedCorners, mAlwaysConsumeSystemBars, mSystemWindowInsetsConsumed, mStableInsetsConsumed, - mDisplayCutoutConsumed); + mDisplayCutoutConsumed, mPrivacyIndicatorBounds); } @@ -1066,6 +1092,8 @@ public final class WindowInsets { private boolean mIsRound; private boolean mAlwaysConsumeSystemBars; + private PrivacyIndicatorBounds mPrivacyIndicatorBounds = new PrivacyIndicatorBounds(); + /** * Creates a builder where all insets are initially consumed. */ @@ -1090,6 +1118,7 @@ public final class WindowInsets { mRoundedCorners = insets.mRoundedCorners; mIsRound = insets.mIsRound; mAlwaysConsumeSystemBars = insets.mAlwaysConsumeSystemBars; + mPrivacyIndicatorBounds = insets.mPrivacyIndicatorBounds; } /** @@ -1314,6 +1343,26 @@ public final class WindowInsets { return this; } + /** @hide */ + @NonNull + public Builder setPrivacyIndicatorBounds(@Nullable PrivacyIndicatorBounds bounds) { + mPrivacyIndicatorBounds = bounds; + return this; + } + + /** + * Sets the bounds of the system privacy indicator. + * + * @param bounds The bounds of the system privacy indicator + */ + @NonNull + public Builder setPrivacyIndicatorBounds(@Nullable Rect bounds) { + //TODO 188788786: refactor the indicator bounds + Rect[] boundsArr = { bounds, bounds, bounds, bounds }; + mPrivacyIndicatorBounds = new PrivacyIndicatorBounds(boundsArr, ROTATION_0); + return this; + } + /** @hide */ @NonNull public Builder setRound(boolean round) { @@ -1338,7 +1387,7 @@ public final class WindowInsets { return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap, mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout, mRoundedCorners, - systemBars(), false /* compatIgnoreVisibility */); + mPrivacyIndicatorBounds, systemBars(), false /* compatIgnoreVisibility */); } } diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java index cfe38033c1dd5..3bd29398325f8 100644 --- a/core/tests/coretests/src/android/view/InsetsStateTest.java +++ b/core/tests/coretests/src/android/view/InsetsStateTest.java @@ -314,6 +314,51 @@ public class InsetsStateTest { assertEqualsAndHashCode(); } + @Test + public void testEquals_samePrivacyIndicator() { + Rect one = new Rect(0, 1, 2, 3); + Rect two = new Rect(4, 5, 6, 7); + Rect[] bounds = new Rect[] { one, two, one, two }; + mState.setPrivacyIndicatorBounds(new PrivacyIndicatorBounds(bounds, 0)); + mState2.setPrivacyIndicatorBounds(new PrivacyIndicatorBounds(bounds, 0)); + assertEqualsAndHashCode(); + } + + @Test + public void testEquals_differentPrivacyIndicatorStaticBounds() { + Rect one = new Rect(0, 1, 2, 3); + Rect two = new Rect(4, 5, 6, 7); + Rect three = new Rect(8, 9, 10, 11); + Rect[] boundsOne = new Rect[] { one, two, one, two }; + Rect[] boundsDifferent = new Rect[] { one, two, three, one }; + Rect[] boundsDifferentOrder = new Rect[] { two, one, one, two }; + Rect[] boundsDifferentLength = new Rect[] { one, two }; + Rect[] boundsNull = new Rect[4]; + mState.setPrivacyIndicatorBounds(new PrivacyIndicatorBounds(boundsOne, 0)); + + mState2.setPrivacyIndicatorBounds(new PrivacyIndicatorBounds(boundsDifferent, 0)); + assertNotEqualsAndHashCode(); + + mState2.setPrivacyIndicatorBounds(new PrivacyIndicatorBounds(boundsDifferentOrder, 0)); + assertNotEqualsAndHashCode(); + + mState2.setPrivacyIndicatorBounds(new PrivacyIndicatorBounds(boundsDifferentLength, 0)); + assertNotEqualsAndHashCode(); + + mState2.setPrivacyIndicatorBounds(new PrivacyIndicatorBounds(boundsNull, 0)); + assertNotEqualsAndHashCode(); + } + + @Test + public void testEquals_differentPrivacyIndicatorRotation() { + Rect one = new Rect(0, 1, 2, 3); + Rect two = new Rect(4, 5, 6, 7); + Rect[] arr = new Rect[] { one, two, one, two}; + mState.setPrivacyIndicatorBounds(new PrivacyIndicatorBounds(arr, 0)); + mState2.setPrivacyIndicatorBounds(new PrivacyIndicatorBounds(arr, 1)); + assertNotEqualsAndHashCode(); + } + @Test public void testEquals_excludeInvisibleIme() { mState.getSource(ITYPE_IME).setFrame(new Rect(0, 0, 100, 100)); diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java index b80837fbe6ca8..dd9634bd68fb0 100644 --- a/core/tests/coretests/src/android/view/WindowInsetsTest.java +++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java @@ -61,7 +61,7 @@ public class WindowInsetsTest { WindowInsets.assignCompatInsets(maxInsets, new Rect(0, 10, 0, 0)); WindowInsets.assignCompatInsets(insets, new Rect(0, 0, 0, 0)); WindowInsets windowInsets = new WindowInsets(insets, maxInsets, visible, false, false, null, - null, systemBars(), true /* compatIgnoreVisibility */); + null, null, systemBars(), true /* compatIgnoreVisibility */); assertEquals(Insets.of(0, 10, 0, 0), windowInsets.getSystemWindowInsets()); } } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 1ec07d047e13f..1911cb784404c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -200,6 +200,7 @@ import android.view.InsetsSource; import android.view.InsetsState; import android.view.InsetsState.InternalInsetsType; import android.view.MagnificationSpec; +import android.view.PrivacyIndicatorBounds; import android.view.RemoteAnimationDefinition; import android.view.RoundedCorners; import android.view.Surface; @@ -342,6 +343,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private final RotationCache mRoundedCornerCache = new RotationCache<>(this::calculateRoundedCornersForRotationUncached); + PrivacyIndicatorBounds mCurrentPrivacyIndicatorBounds = new PrivacyIndicatorBounds(); + private final RotationCache + mPrivacyIndicatorBoundsCache = new + RotationCache<>(this::calculatePrivacyIndicatorBoundsForRotationUncached); + /** * Overridden display size. Initialized with {@link #mInitialDisplayWidth} * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size". @@ -986,7 +992,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInsetsStateController = new InsetsStateController(this); mDisplayFrames = new DisplayFrames(mDisplayId, mInsetsStateController.getRawInsetsState(), mDisplayInfo, calculateDisplayCutoutForRotation(mDisplayInfo.rotation), - calculateRoundedCornersForRotation(mDisplayInfo.rotation)); + calculateRoundedCornersForRotation(mDisplayInfo.rotation), + calculatePrivacyIndicatorBoundsForRotation(mDisplayInfo.rotation)); initializeDisplayBaseInfo(); mAppTransition = new AppTransition(mWmService.mContext, mWmService, this); @@ -1713,8 +1720,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final DisplayInfo info = computeScreenConfiguration(mTmpConfiguration, rotation); final WmDisplayCutout cutout = calculateDisplayCutoutForRotation(rotation); final RoundedCorners roundedCorners = calculateRoundedCornersForRotation(rotation); + final PrivacyIndicatorBounds indicatorBounds = + calculatePrivacyIndicatorBoundsForRotation(rotation); final DisplayFrames displayFrames = new DisplayFrames(mDisplayId, new InsetsState(), info, - cutout, roundedCorners); + cutout, roundedCorners, indicatorBounds); token.applyFixedRotationTransform(info, displayFrames, mTmpConfiguration); } @@ -2012,6 +2021,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return roundedCorners.rotate(rotation, mInitialDisplayWidth, mInitialDisplayHeight); } + PrivacyIndicatorBounds calculatePrivacyIndicatorBoundsForRotation(int rotation) { + return mPrivacyIndicatorBoundsCache.getOrCompute(mCurrentPrivacyIndicatorBounds, rotation); + } + + private PrivacyIndicatorBounds calculatePrivacyIndicatorBoundsForRotationUncached( + PrivacyIndicatorBounds bounds, int rotation) { + if (bounds == null) { + return new PrivacyIndicatorBounds(new Rect[4], rotation); + } + + return bounds.rotate(rotation); + } + /** * Compute display info and configuration according to the given rotation without changing * current display. @@ -2527,14 +2549,30 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp onDisplayChanged(this); } + void updatePrivacyIndicatorBounds(Rect[] staticBounds) { + PrivacyIndicatorBounds oldBounds = mCurrentPrivacyIndicatorBounds; + mCurrentPrivacyIndicatorBounds = + mCurrentPrivacyIndicatorBounds.updateStaticBounds(staticBounds); + if (!Objects.equals(oldBounds, mCurrentPrivacyIndicatorBounds)) { + final DisplayInfo info = mDisplayInfo; + if (mDisplayFrames.onDisplayInfoUpdated(info, + calculateDisplayCutoutForRotation(info.rotation), + calculateRoundedCornersForRotation(info.rotation), + calculatePrivacyIndicatorBoundsForRotation(info.rotation))) { + mInsetsStateController.onDisplayInfoUpdated(true); + } + } + } + void onDisplayInfoChanged() { final DisplayInfo info = mDisplayInfo; if (mDisplayFrames.onDisplayInfoUpdated(info, calculateDisplayCutoutForRotation(info.rotation), - calculateRoundedCornersForRotation(info.rotation))) { + calculateRoundedCornersForRotation(info.rotation), + calculatePrivacyIndicatorBoundsForRotation(info.rotation))) { // TODO(b/161810301): Set notifyInsetsChange to true while the server no longer performs // layout. - mInsetsStateController.onDisplayInfoUpdated(false /* notifyInsetsChange */); + mInsetsStateController.onDisplayInfoUpdated(false /* notifyInsetsChanged */); } mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight); mDisplayPolicy.onDisplayInfoChanged(info); @@ -2569,6 +2607,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi; mInitialDisplayCutout = mDisplayInfo.displayCutout; mInitialRoundedCorners = mDisplayInfo.roundedCorners; + mCurrentPrivacyIndicatorBounds = new PrivacyIndicatorBounds(new Rect[4], + mDisplayInfo.rotation); } /** diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index a37f3f254276c..32e43ca4e56c1 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -27,6 +27,7 @@ import android.util.proto.ProtoOutputStream; import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.InsetsState; +import android.view.PrivacyIndicatorBounds; import android.view.RoundedCorners; import com.android.server.wm.utils.WmDisplayCutout; @@ -59,10 +60,11 @@ public class DisplayFrames { public int mRotation; public DisplayFrames(int displayId, InsetsState insetsState, DisplayInfo info, - WmDisplayCutout displayCutout, RoundedCorners roundedCorners) { + WmDisplayCutout displayCutout, RoundedCorners roundedCorners, + PrivacyIndicatorBounds indicatorBounds) { mDisplayId = displayId; mInsetsState = insetsState; - onDisplayInfoUpdated(info, displayCutout, roundedCorners); + onDisplayInfoUpdated(info, displayCutout, roundedCorners, indicatorBounds); } /** @@ -74,15 +76,17 @@ public class DisplayFrames { * @return {@code true} if the insets state has been changed; {@code false} otherwise. */ public boolean onDisplayInfoUpdated(DisplayInfo info, @NonNull WmDisplayCutout displayCutout, - @NonNull RoundedCorners roundedCorners) { + @NonNull RoundedCorners roundedCorners, + @NonNull PrivacyIndicatorBounds indicatorBounds) { mRotation = info.rotation; final InsetsState state = mInsetsState; final Rect safe = mDisplayCutoutSafe; final DisplayCutout cutout = displayCutout.getDisplayCutout(); if (mDisplayWidth == info.logicalWidth && mDisplayHeight == info.logicalHeight - && state.getDisplayCutout().equals(cutout) - && state.getRoundedCorners().equals(roundedCorners)) { + && state.getDisplayCutout().equals(cutout) + && state.getRoundedCorners().equals(roundedCorners) + && state.getPrivacyIndicatorBounds().equals(indicatorBounds)) { return false; } mDisplayWidth = info.logicalWidth; @@ -93,6 +97,7 @@ public class DisplayFrames { state.setDisplayFrame(unrestricted); state.setDisplayCutout(cutout); state.setRoundedCorners(roundedCorners); + state.setPrivacyIndicatorBounds(indicatorBounds); if (!cutout.isEmpty()) { if (cutout.getSafeInsetLeft() > 0) { safe.left = unrestricted.left + cutout.getSafeInsetLeft(); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8bee862c97689..025153706922e 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -5945,6 +5945,20 @@ public class WindowManagerService extends IWindowManager.Stub } } + @Override + public void updateStaticPrivacyIndicatorBounds(int displayId, + Rect[] staticBounds) { + synchronized (mGlobalLock) { + final DisplayContent displayContent = mRoot.getDisplayContent(displayId); + if (displayContent != null) { + displayContent.updatePrivacyIndicatorBounds(staticBounds); + } else { + Slog.w(TAG, "updateStaticPrivacyIndicatorBounds with invalid displayId=" + + displayId); + } + } + } + public void setNavBarVirtualKeyHapticFeedbackEnabled(boolean enabled) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR) != PackageManager.PERMISSION_GRANTED) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java index 5a0466afa85b3..03304bb9456a3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java @@ -65,6 +65,7 @@ import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.Gravity; import android.view.InsetsState; +import android.view.PrivacyIndicatorBounds; import android.view.RoundedCorners; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; @@ -176,7 +177,7 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase { ? mDisplayContent.calculateRoundedCornersForRotation(mRotation) : RoundedCorners.NO_ROUNDED_CORNERS; return new DisplayFrames(mDisplayContent.getDisplayId(), - insetsState, info.first, info.second, roundedCorners); + insetsState, info.first, info.second, roundedCorners, new PrivacyIndicatorBounds()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 2ca78533f8671..b793be74c033a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -54,6 +54,7 @@ import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import android.view.InsetsSource; import android.view.InsetsState; +import android.view.PrivacyIndicatorBounds; import android.view.WindowInsets.Side; import android.view.WindowManager; @@ -317,7 +318,7 @@ public class DisplayPolicyTests extends WindowTestsBase { final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState(); mImeWindow.mAboveInsetsState.set(state); mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(), - state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS); + state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds()); mDisplayContent.setInputMethodWindowLocked(mImeWindow); mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM); diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 8b4e94724e6c4..ada58a586ffcb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -49,6 +49,7 @@ import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; import android.view.Gravity; import android.view.InsetsState; +import android.view.PrivacyIndicatorBounds; import android.view.RoundedCorners; import android.view.Surface; import android.view.SurfaceControl; @@ -152,7 +153,7 @@ public class WallpaperControllerTests extends WindowTestsBase { final DisplayInfo info = dc.computeScreenConfiguration(config, Surface.ROTATION_0); final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0); final DisplayFrames displayFrames = new DisplayFrames(dc.getDisplayId(), new InsetsState(), - info, cutout, RoundedCorners.NO_ROUNDED_CORNERS); + info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds()); wallpaperWindowToken.applyFixedRotationTransform(info, displayFrames, config); // Check that the wallpaper has the same frame in landscape than in portrait