diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index dca5ea836fa47..f013f9b49955d 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -132,6 +132,9 @@ + + + diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index aaaa3f77924a8..9fab2ea341937 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -43,6 +43,7 @@ import android.graphics.drawable.Drawable; import android.hardware.display.DisplayManager; import android.hardware.graphics.common.AlphaInterpretation; import android.hardware.graphics.common.DisplayDecorationSupport; +import android.os.Build; import android.os.Handler; import android.os.SystemProperties; import android.os.Trace; @@ -72,6 +73,7 @@ import com.android.settingslib.Utils; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.decor.CutoutDecorProviderFactory; import com.android.systemui.decor.DecorProvider; import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.DecorProviderKt; @@ -139,13 +141,11 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab protected RoundedCornerResDelegate mRoundedCornerResDelegate; @VisibleForTesting protected DecorProviderFactory mRoundedCornerFactory; + private CutoutDecorProviderFactory mCutoutFactory; private int mProviderRefreshToken = 0; @VisibleForTesting protected OverlayWindow[] mOverlays = null; @VisibleForTesting - @Nullable - DisplayCutoutView[] mCutoutViews; - @VisibleForTesting ViewGroup mScreenDecorHwcWindow; @VisibleForTesting ScreenDecorHwcLayer mScreenDecorHwcLayer; @@ -187,17 +187,28 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } - if (mCutoutViews == null) { - Log.w(TAG, "DisplayCutoutView not initialized onApplyCameraProtection"); - return; + final int[] ids = { + R.id.display_cutout, + R.id.display_cutout_left, + R.id.display_cutout_right, + R.id.display_cutout_bottom + }; + int setProtectionCnt = 0; + for (int id: ids) { + final View view = getOverlayView(id); + if (!(view instanceof DisplayCutoutView)) { + continue; + } + ++setProtectionCnt; + final DisplayCutoutView dcv = (DisplayCutoutView) view; + dcv.setProtection(protectionPath, bounds); + dcv.enableShowProtection(true); } - - // Show the extra protection around the front facing camera if necessary - for (DisplayCutoutView dcv : mCutoutViews) { - // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile - if (dcv != null) { - dcv.setProtection(protectionPath, bounds); - dcv.enableShowProtection(true); + if (setProtectionCnt == 0) { + if (Build.isDebuggable()) { + throw new RuntimeException("CutoutView not initialized showCameraProtection"); + } else { + Log.e(TAG, "CutoutView not initialized showCameraProtection"); } } } @@ -219,15 +230,26 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return; } - if (mCutoutViews == null) { - Log.w(TAG, "DisplayCutoutView not initialized onHideCameraProtection"); - return; + final int[] ids = { + R.id.display_cutout, + R.id.display_cutout_left, + R.id.display_cutout_right, + R.id.display_cutout_bottom + }; + int setProtectionCnt = 0; + for (int id: ids) { + final View view = getOverlayView(id); + if (!(view instanceof DisplayCutoutView)) { + continue; + } + ++setProtectionCnt; + ((DisplayCutoutView) view).enableShowProtection(false); } - // Go back to the regular anti-aliasing - for (DisplayCutoutView dcv : mCutoutViews) { - // Check Null since not all mCutoutViews[pos] be inflated at the meanwhile - if (dcv != null) { - dcv.enableShowProtection(false); + if (setProtectionCnt == 0) { + if (Build.isDebuggable()) { + throw new RuntimeException("CutoutView not initialized hideCameraProtection"); + } else { + Log.e(TAG, "CutoutView not initialized hideCameraProtection"); } } } @@ -335,6 +357,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab decorProviders.addAll(mFaceScanningFactory.getProviders()); if (!hasHwLayer) { decorProviders.addAll(mRoundedCornerFactory.getProviders()); + decorProviders.addAll(mCutoutFactory.getProviders()); } return decorProviders; } @@ -379,6 +402,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mRoundedCornerResDelegate.setPhysicalPixelDisplaySizeRatio( getPhysicalPixelDisplaySizeRatio()); mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate); + mCutoutFactory = getCutoutFactory(); mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport(); updateHwLayerRoundedCornerDrawable(); setupDecorations(); @@ -483,18 +507,13 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (needToUpdateProviderViews) { updateOverlayProviderViews(null); } else { - updateOverlayProviderViews(new Integer[] { mFaceScanningViewId }); - } - - if (mCutoutViews != null) { - final int size = mCutoutViews.length; - for (int i = 0; i < size; i++) { - final DisplayCutoutView cutoutView = mCutoutViews[i]; - if (cutoutView == null) { - continue; - } - cutoutView.onDisplayChanged(newUniqueId); - } + updateOverlayProviderViews(new Integer[] { + mFaceScanningViewId, + R.id.display_cutout, + R.id.display_cutout_left, + R.id.display_cutout_right, + R.id.display_cutout_bottom, + }); } if (mScreenDecorHwcLayer != null) { @@ -507,8 +526,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateConfiguration(); } + @VisibleForTesting @Nullable - private View getOverlayView(@IdRes int id) { + View getOverlayView(@IdRes int id) { if (mOverlays == null) { return null; } @@ -565,18 +585,18 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab removeHwcOverlay(); } - final DisplayCutout cutout = getCutout(); + boolean[] hasCreatedOverlay = new boolean[BOUNDS_POSITION_LENGTH]; final boolean shouldOptimizeVisibility = shouldOptimizeVisibility(); + Integer bound; + while ((bound = DecorProviderKt.getProperBound(decorProviders)) != null) { + hasCreatedOverlay[bound] = true; + Pair, List> pair = + DecorProviderKt.partitionAlignedBound(decorProviders, bound); + decorProviders = pair.getSecond(); + createOverlay(bound, pair.getFirst(), shouldOptimizeVisibility); + } for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (shouldShowSwLayerCutout(i, cutout) - || shouldShowSwLayerFaceScan(i, cutout) - || shouldShowSwLayerRoundedCorner(i, cutout) - || shouldShowSwLayerPrivacyDot(i, cutout)) { - Pair, List> pair = - DecorProviderKt.partitionAlignedBound(decorProviders, i); - decorProviders = pair.getSecond(); - createOverlay(i, pair.getFirst(), shouldOptimizeVisibility); - } else { + if (!hasCreatedOverlay[i]) { removeOverlay(i); } } @@ -639,9 +659,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } } - @VisibleForTesting - DisplayCutout getCutout() { - return mContext.getDisplay().getCutout(); + // For unit test to override + protected CutoutDecorProviderFactory getCutoutFactory() { + return new CutoutDecorProviderFactory(mContext.getResources(), + mContext.getDisplay()); } @VisibleForTesting @@ -731,16 +752,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab overlayView.setAlpha(0); overlayView.setForceDarkAllowed(false); - // Only show cutout in mOverlays when hwc doesn't support screen decoration - if (mHwcScreenDecorationSupport == null) { - if (mCutoutViews == null) { - mCutoutViews = new DisplayCutoutView[BOUNDS_POSITION_LENGTH]; - } - mCutoutViews[pos] = new DisplayCutoutView(mContext, pos); - overlayView.addView(mCutoutViews[pos]); - mCutoutViews[pos].updateRotation(mRotation); - } - mWindowManager.addView(overlayView, getWindowLayoutParams(pos)); overlayView.addOnLayoutChangeListener(new OnLayoutChangeListener() { @@ -947,27 +958,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mTintColor = Color.RED; } - if (mOverlays == null) { - return; - } - - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (mOverlays[i] == null) { - continue; - } - final ViewGroup overlayView = mOverlays[i].getRootView(); - final int size = overlayView.getChildCount(); - View child; - for (int j = 0; j < size; j++) { - child = overlayView.getChildAt(j); - if (child instanceof DisplayCutoutView && child.getId() == R.id.display_cutout) { - ((DisplayCutoutView) child).setColor(mTintColor); - } - } - } - updateOverlayProviderViews(new Integer[] { mFaceScanningViewId, + R.id.display_cutout, + R.id.display_cutout_left, + R.id.display_cutout_right, + R.id.display_cutout_bottom, R.id.rounded_corner_top_left, R.id.rounded_corner_top_right, R.id.rounded_corner_bottom_left, @@ -1092,15 +1088,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateHwLayerRoundedCornerDrawable(); } updateLayoutParams(); - // update cutout view rotation - if (mCutoutViews != null) { - for (final DisplayCutoutView cutoutView: mCutoutViews) { - if (cutoutView == null) { - continue; - } - cutoutView.updateRotation(mRotation); - } - } // update all provider views inside overlay updateOverlayProviderViews(null); @@ -1119,46 +1106,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab return mRoundedCornerFactory.getHasProviders(); } - private boolean isDefaultShownOverlayPos(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - // for cutout is null or cutout with only waterfall. - final boolean emptyBoundsOrWaterfall = cutout == null || cutout.isBoundsEmpty(); - // Shows rounded corner on left and right overlays only when there is no top or bottom - // cutout. - final int rotatedTop = getBoundPositionFromRotation(BOUNDS_POSITION_TOP, mRotation); - final int rotatedBottom = getBoundPositionFromRotation(BOUNDS_POSITION_BOTTOM, mRotation); - if (emptyBoundsOrWaterfall || !cutout.getBoundingRectsAll()[rotatedTop].isEmpty() - || !cutout.getBoundingRectsAll()[rotatedBottom].isEmpty()) { - return pos == BOUNDS_POSITION_TOP || pos == BOUNDS_POSITION_BOTTOM; - } else { - return pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_RIGHT; - } - } - - private boolean shouldShowSwLayerRoundedCorner(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - return hasRoundedCorners() && isDefaultShownOverlayPos(pos, cutout) - && mHwcScreenDecorationSupport == null; - } - - private boolean shouldShowSwLayerPrivacyDot(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout); - } - - private boolean shouldShowSwLayerFaceScan(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - return mFaceScanningFactory.getHasProviders() && isDefaultShownOverlayPos(pos, cutout); - } - - private boolean shouldShowSwLayerCutout(@BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - final Rect[] bounds = cutout == null ? null : cutout.getBoundingRectsAll(); - final int rotatedPos = getBoundPositionFromRotation(pos, mRotation); - return (bounds != null && !bounds[rotatedPos].isEmpty() - && mHwcScreenDecorationSupport == null); - } - private boolean shouldOptimizeVisibility() { return (isPrivacyDotEnabled() || mFaceScanningFactory.getHasProviders()) && (mHwcScreenDecorationSupport != null @@ -1167,7 +1114,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private boolean shouldDrawCutout() { - return shouldDrawCutout(mContext); + return mCutoutFactory.getHasProviders(); } static boolean shouldDrawCutout(Context context) { @@ -1283,7 +1230,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab paint.setColor(mColor); paint.setStyle(Paint.Style.FILL); - setId(R.id.display_cutout); if (DEBUG) { getViewTreeObserver().addOnDrawListener(() -> Log.i(TAG, getWindowTitleByPos(pos) + " drawn in rot " + mRotation)); @@ -1291,6 +1237,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } public void setColor(int color) { + if (color == mColor) { + return; + } mColor = color; paint.setColor(mColor); invalidate(); diff --git a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt new file mode 100644 index 0000000000000..cbed21cf65d63 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2022 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 com.android.systemui.decor + +import android.content.res.Resources +import android.util.Log +import android.view.Display +import android.view.DisplayCutout +import android.view.DisplayInfo + +class CutoutDecorProviderFactory constructor( + private val res: Resources, + private val display: Display?, +) : DecorProviderFactory() { + + val displayInfo = DisplayInfo() + + override val hasProviders: Boolean + get() { + display?.getDisplayInfo(displayInfo) ?: run { + Log.w(TAG, "display is null, can't update displayInfo") + } + return DisplayCutout.getFillBuiltInDisplayCutout(res, displayInfo.uniqueId) + } + + override val providers: List + get() { + if (!hasProviders) { + return emptyList() + } + + return ArrayList().also { list -> + // We need to update displayInfo before using it, but it has already updated during + // accessing hasProviders field + displayInfo.displayCutout?.getBoundBaseOnCurrentRotation()?.let { bounds -> + for (bound in bounds) { + list.add( + CutoutDecorProviderImpl(bound.baseOnRotation0(displayInfo.rotation)) + ) + } + } + } + } +} + +private const val TAG = "CutoutDecorProviderFactory" diff --git a/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt new file mode 100644 index 0000000000000..991b54e8035ea --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022 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 com.android.systemui.decor + +import android.content.Context +import android.view.DisplayCutout +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import com.android.systemui.R +import com.android.systemui.ScreenDecorations.DisplayCutoutView + +class CutoutDecorProviderImpl( + @DisplayCutout.BoundsPosition override val alignedBound: Int +) : BoundDecorProvider() { + + override val viewId: Int = when (alignedBound) { + DisplayCutout.BOUNDS_POSITION_TOP -> R.id.display_cutout + DisplayCutout.BOUNDS_POSITION_LEFT -> R.id.display_cutout_left + DisplayCutout.BOUNDS_POSITION_RIGHT -> R.id.display_cutout_right + else -> R.id.display_cutout_bottom + } + + override fun inflateView( + context: Context, + parent: ViewGroup, + @Surface.Rotation rotation: Int, + tintColor: Int + ): View { + return DisplayCutoutView(context, alignedBound).also { view -> + view.id = viewId + view.setColor(tintColor) + parent.addView(view) + view.updateRotation(rotation) + } + } + + override fun onReloadResAndMeasure( + view: View, + reloadToken: Int, + @Surface.Rotation rotation: Int, + tintColor: Int, + displayUniqueId: String? + ) { + (view as? DisplayCutoutView)?.let { cutoutView -> + cutoutView.setColor(tintColor) + cutoutView.updateRotation(rotation) + cutoutView.onDisplayChanged(displayUniqueId) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt index de6d7278971c5..260c4b5a78300 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt @@ -32,7 +32,7 @@ abstract class DecorProvider { abstract val viewId: Int /** The number of total aligned bounds */ - val numOfAlignedEdge: Int + val numOfAlignedBound: Int get() = alignedBounds.size /** The aligned bounds for the view which is created through inflateView() */ @@ -57,16 +57,8 @@ abstract class DecorProvider { @Surface.Rotation rotation: Int, tintColor: Int ): View -} -/** - * Split list to 2 list, and return it back as Pair<>. The providers on the first list contains this - * alignedBound element. The providers on the second list do not contain this alignedBound element - */ -fun List.partitionAlignedBound( - @DisplayCutout.BoundsPosition alignedBound: Int -): Pair, List> { - return partition { it.alignedBounds.contains(alignedBound) } + override fun toString() = "${javaClass.simpleName}{alignedBounds=$alignedBounds}" } /** @@ -94,3 +86,60 @@ abstract class BoundDecorProvider : DecorProvider() { listOf(alignedBound) } } + +/** + * Split list to 2 list, and return it back as Pair<>. The providers on the first list contains this + * alignedBound element. The providers on the second list do not contain this alignedBound element + */ +fun List.partitionAlignedBound( + @DisplayCutout.BoundsPosition alignedBound: Int +): Pair, List> { + return partition { it.alignedBounds.contains(alignedBound) } +} + +/** + * Get the proper bound from DecorProvider list + * Time complexity: O(N), N is the number of providers + * + * Choose order + * 1. Return null if list is empty + * 2. If list contains BoundDecorProvider, return its alignedBound[0] because it is a must-have + * bound + * 3. Return the bound with most DecorProviders + */ +fun List.getProperBound(): Int? { + // Return null if list is empty + if (isEmpty()) { + return null + } + + // Choose alignedBounds[0] of BoundDecorProvider if any + val singleBoundProvider = firstOrNull { it.numOfAlignedBound == 1 } + if (singleBoundProvider != null) { + return singleBoundProvider.alignedBounds[0] + } + + // Return the bound with most DecorProviders + val boundCount = intArrayOf(0, 0, 0, 0) + for (provider in this) { + for (bound in provider.alignedBounds) { + boundCount[bound]++ + } + } + var maxCount = 0 + var maxCountBound: Int? = null + val bounds = arrayOf( + // Put top and bottom at first to get the highest priority to be chosen + DisplayCutout.BOUNDS_POSITION_TOP, + DisplayCutout.BOUNDS_POSITION_BOTTOM, + DisplayCutout.BOUNDS_POSITION_LEFT, + DisplayCutout.BOUNDS_POSITION_RIGHT + ) + for (bound in bounds) { + if (boundCount[bound] > maxCount) { + maxCountBound = bound + maxCount = boundCount[bound] + } + } + return maxCountBound +} diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt index dfb0b5aad9124..45b8a08a9b45c 100644 --- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt +++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt @@ -114,7 +114,8 @@ class OverlayWindow(private val context: Context) { pw.println(" rootView=$rootView") for (i in 0 until rootView.childCount) { val child = rootView.getChildAt(i) - pw.println(" child[$i]=$child") + val provider = viewProviderMap[child.id]?.second + pw.println(" child[$i]=$child $provider") } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index 64a7986d05b1f..df10dfe9f1603 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -16,7 +16,6 @@ package com.android.systemui; import static android.view.DisplayCutout.BOUNDS_POSITION_BOTTOM; import static android.view.DisplayCutout.BOUNDS_POSITION_LEFT; -import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH; import static android.view.DisplayCutout.BOUNDS_POSITION_RIGHT; import static android.view.DisplayCutout.BOUNDS_POSITION_TOP; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; @@ -36,6 +35,7 @@ import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isA; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -49,7 +49,6 @@ import android.annotation.IdRes; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.TypedArray; -import android.graphics.Insets; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -60,7 +59,6 @@ import android.os.Handler; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; -import android.util.RotationUtils; import android.util.Size; import android.view.Display; import android.view.DisplayCutout; @@ -80,6 +78,8 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.biometrics.AuthController; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.decor.CornerDecorProvider; +import com.android.systemui.decor.CutoutDecorProviderFactory; +import com.android.systemui.decor.CutoutDecorProviderImpl; import com.android.systemui.decor.DecorProvider; import com.android.systemui.decor.DecorProviderFactory; import com.android.systemui.decor.FaceScanningOverlayProviderImpl; @@ -157,6 +157,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { @Mock private DisplayInfo mDisplayInfo; private PrivacyDotViewController.ShowingListener mPrivacyDotShowingListener; + @Mock + private CutoutDecorProviderFactory mCutoutFactory; + private List mMockCutoutList; @Before public void setup() { @@ -206,6 +209,11 @@ public class ScreenDecorationsTest extends SysuiTestCase { DisplayCutout.BOUNDS_POSITION_RIGHT, R.layout.privacy_dot_bottom_right)); + // Default no cutout + mMockCutoutList = new ArrayList<>(); + doAnswer(it -> !(mMockCutoutList.isEmpty())).when(mCutoutFactory).getHasProviders(); + doReturn(mMockCutoutList).when(mCutoutFactory).getProviders(); + mFaceScanningDecorProvider = spy(new FaceScanningOverlayProviderImpl( BOUNDS_POSITION_TOP, mAuthController, @@ -239,6 +247,11 @@ public class ScreenDecorationsTest extends SysuiTestCase { super.updateOverlayWindowVisibilityIfViewExists(view); mExecutor.runAllReady(); } + + @Override + protected CutoutDecorProviderFactory getCutoutFactory() { + return ScreenDecorationsTest.this.mCutoutFactory; + } }); mScreenDecorations.mDisplayInfo = mDisplayInfo; doReturn(1f).when(mScreenDecorations).getPhysicalPixelDisplaySizeRatio(); @@ -429,11 +442,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_NoCutout_NoPrivacyDot_NoFaceScanning() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); // No views added. @@ -448,11 +459,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_NoCutout_PrivacyDot_NoFaceScanning() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); @@ -484,11 +493,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_NoCutout_NoPrivacyDot_NoFaceScanning() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); @@ -516,11 +523,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_NoCutout_PrivacyDot_NoFaceScanning() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); @@ -555,10 +560,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); + + // no cutout (default) mScreenDecorations.start(); // Size of corner view should same as rounded_corner_radius{_top|_bottom} @@ -574,11 +578,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded3px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView() @@ -611,13 +613,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); View topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() @@ -647,13 +646,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_CutoutShortEdge_NoPrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for top cutout. @@ -671,13 +667,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_CutoutShortEdge_PrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for top cutout. @@ -706,13 +699,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_CutoutLongEdge_NoPrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); // Left window is created for left cutout. @@ -734,13 +724,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_CutoutLongEdge_PrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); // Left window is created for left cutout. @@ -762,13 +749,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutShortEdge_NoPrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for rounded corner and top cutout. @@ -791,13 +775,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutShortEdge_PrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for rounded corner and top cutout. @@ -823,13 +804,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutLongEdge_NoPrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); // Left window is created for rounded corner and left cutout. @@ -842,13 +820,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutLongEdge_PrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.start(); // Left window is created for rounded corner, left cutout, and privacy. @@ -863,13 +838,11 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutShortAndLongEdge_NoPrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top and left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for rounded corner and top cutout. @@ -883,13 +856,11 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testRounding_CutoutShortAndLongEdge_PrivacyDot() { setupResources(20 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 20 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 20 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // top and left cutout - final Rect[] bounds = {new Rect(0, 50, 1, 60), new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Top window is created for rounded corner and top cutout. @@ -905,21 +876,16 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_NoPrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // Set to short edge cutout(top). - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); verifyOverlaysExistAndAdded(false, true, false, false, View.VISIBLE); // Switch to long edge cutout(left). - final Rect[] newBounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), newBounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.onConfigurationChanged(new Configuration()); verifyOverlaysExistAndAdded(true, false, false, false, View.VISIBLE); @@ -929,13 +895,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNoRounding_SwitchFrom_ShortEdgeCutout_To_LongCutout_PrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); // Set to short edge cutout(top). - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE); @@ -943,9 +906,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { verify(mDotViewController, times(1)).setShowingListener(null); // Switch to long edge cutout(left). - final Rect[] newBounds = {new Rect(0, 50, 1, 60), null, null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(1, 0, 0, 0), newBounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.set(0, new CutoutDecorProviderImpl(BOUNDS_POSITION_LEFT)); mScreenDecorations.onConfigurationChanged(new Configuration()); verifyOverlaysExistAndAdded(true, false, true, false, View.VISIBLE); @@ -973,20 +934,16 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testDelayedCutout_NoPrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); - // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + // No cutout (default) mScreenDecorations.start(); verifyOverlaysExistAndAdded(false, false, false, false, null); - when(mContext.getResources().getBoolean( - com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout)) - .thenReturn(true); + // top cutout + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); + mScreenDecorations.onConfigurationChanged(new Configuration()); // Only top windows should be added. @@ -997,13 +954,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testDelayedCutout_PrivacyDot() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); - // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); // Both top and bottom windows should be added with INVISIBLE because of only privacy dot, @@ -1015,9 +968,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { verify(mDotViewController, times(1)).setShowingListener( mScreenDecorations.mPrivacyDotShowingListener); - when(mContext.getResources().getBoolean( - com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout)) - .thenReturn(true); + // top cutout + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); + mScreenDecorations.onConfigurationChanged(new Configuration()); // Both top and bottom windows should be added with VISIBLE because of privacy dot and @@ -1043,8 +996,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning*/); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/); mDisplayInfo.rotation = Surface.ROTATION_0; mScreenDecorations.start(); @@ -1058,8 +1010,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded5px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning*/); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning*/); mDisplayInfo.rotation = Surface.ROTATION_270; mScreenDecorations.onConfigurationChanged(null); @@ -1072,8 +1023,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testOnlyRoundedCornerRadiusTop() { setupResources(0 /* radius */, 10 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); mScreenDecorations.start(); @@ -1094,8 +1044,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testOnlyRoundedCornerRadiusBottom() { setupResources(0 /* radius */, 0 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); mScreenDecorations.start(); @@ -1166,13 +1115,10 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testSupportHwcLayer_SwitchFrom_NotSupport() { setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // should only inflate mOverlays when the hwc doesn't support screen decoration @@ -1195,16 +1141,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testNotSupportHwcLayer_SwitchFrom_Support() { setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // should only inflate hwc layer when the hwc supports screen decoration @@ -1234,16 +1177,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { /* roundedTopDrawable */, getTestsDrawable(com.android.systemui.tests.R.drawable.rounded4px) /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - true /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, true /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); // Inflate top and bottom overlay with INVISIBLE because of only privacy dots on sw layer @@ -1277,11 +1217,9 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testAutoShowHideOverlayWindowWhenNoRoundedAndNoCutout() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, true /* privacyDot */, - true /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, true /* faceScanning */); - // no cutout - doReturn(null).when(mScreenDecorations).getCutout(); + // no cutout (default) mScreenDecorations.start(); // Inflate top and bottom overlay with INVISIBLE because of only privacy dots on sw layer @@ -1315,16 +1253,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testHwcLayer_noPrivacyDot_noFaceScanning() { setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); @@ -1337,16 +1272,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testHwcLayer_PrivacyDot_FaceScanning() { setupResources(0 /* radius */, 10 /* radiusTop */, 20 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - true /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, true /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); @@ -1364,16 +1296,13 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testOnDisplayChanged_hwcLayer() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); final DisplayDecorationSupport decorationSupport = new DisplayDecorationSupport(); decorationSupport.format = PixelFormat.R_8; doReturn(decorationSupport).when(mDisplay).getDisplayDecorationSupport(); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); @@ -1390,18 +1319,16 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testOnDisplayChanged_nonHwcLayer() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); // top cutout - final Rect[] bounds = {null, new Rect(9, 0, 10, 1), null, null}; - doReturn(getDisplayCutoutForRotation(Insets.of(0, 1, 0, 0), bounds)) - .when(mScreenDecorations).getCutout(); + mMockCutoutList.add(new CutoutDecorProviderImpl(BOUNDS_POSITION_TOP)); mScreenDecorations.start(); - final ScreenDecorations.DisplayCutoutView cutoutView = - mScreenDecorations.mCutoutViews[BOUNDS_POSITION_TOP]; + final ScreenDecorations.DisplayCutoutView cutoutView = (ScreenDecorations.DisplayCutoutView) + mScreenDecorations.getOverlayView(R.id.display_cutout); + assertNotNull(cutoutView); spyOn(cutoutView); doReturn(mDisplay).when(cutoutView).getDisplay(); @@ -1414,8 +1341,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testHasSameProvidersWithNullOverlays() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, false /* fillCutout */, false /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, false /* privacyDot */, false /* faceScanning */); mScreenDecorations.start(); @@ -1433,8 +1359,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { public void testHasSameProvidersWithPrivacyDots() { setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */, null /* roundedTopDrawable */, null /* roundedBottomDrawable */, - 0 /* roundedPadding */, true /* fillCutout */, true /* privacyDot */, - false /* faceScanning */); + 0 /* roundedPadding */, true /* privacyDot */, false /* faceScanning */); mScreenDecorations.start(); @@ -1471,7 +1396,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { private void setupResources(int radius, int radiusTop, int radiusBottom, @Nullable Drawable roundedTopDrawable, @Nullable Drawable roundedBottomDrawable, - int roundedPadding, boolean fillCutout, boolean privacyDot, boolean faceScanning) { + int roundedPadding, boolean privacyDot, boolean faceScanning) { mContext.getOrCreateTestableResources().addOverride( com.android.internal.R.array.config_displayUniqueIdArray, new String[]{}); @@ -1511,8 +1436,6 @@ public class ScreenDecorationsTest extends SysuiTestCase { } mContext.getOrCreateTestableResources().addOverride( R.dimen.rounded_corner_content_padding, roundedPadding); - mContext.getOrCreateTestableResources().addOverride( - com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout); mPrivacyDecorProviders = new ArrayList<>(); if (privacyDot) { @@ -1531,19 +1454,4 @@ public class ScreenDecorationsTest extends SysuiTestCase { when(mFaceScanningProviderFactory.getProviders()).thenReturn(mFaceScanningProviders); when(mFaceScanningProviderFactory.getHasProviders()).thenReturn(faceScanning); } - - private DisplayCutout getDisplayCutoutForRotation(Insets safeInsets, Rect[] cutoutBounds) { - final int rotation = mContext.getDisplay().getRotation(); - final Insets insets = RotationUtils.rotateInsets(safeInsets, rotation); - final Rect[] sorted = new Rect[BOUNDS_POSITION_LENGTH]; - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - final int rotatedPos = ScreenDecorations.getBoundPositionFromRotation(i, rotation); - if (cutoutBounds[i] != null) { - RotationUtils.rotateBounds(cutoutBounds[i], new Rect(0, 0, 100, 200), rotation); - } - sorted[rotatedPos] = cutoutBounds[i]; - } - return new DisplayCutout(insets, sorted[BOUNDS_POSITION_LEFT], sorted[BOUNDS_POSITION_TOP], - sorted[BOUNDS_POSITION_RIGHT], sorted[BOUNDS_POSITION_BOTTOM]); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt new file mode 100644 index 0000000000000..1040ec453a7c1 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2022 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 com.android.systemui.decor + +import android.graphics.Insets +import android.graphics.Rect +import android.testing.AndroidTestingRunner +import android.testing.TestableResources +import android.util.RotationUtils +import android.util.Size +import android.view.Display +import android.view.DisplayCutout +import android.view.DisplayCutout.BOUNDS_POSITION_LENGTH +import android.view.DisplayInfo +import android.view.Surface +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.any +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.doAnswer +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class CutoutDecorProviderFactoryTest : SysuiTestCase() { + + @Mock private lateinit var display: Display + private var testableRes: TestableResources? = null + private lateinit var factory: CutoutDecorProviderFactory + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + testableRes = mContext.orCreateTestableResources + factory = CutoutDecorProviderFactory(testableRes!!.resources, display) + } + + private fun setupFillCutout(fillCutout: Boolean) { + testableRes!!.addOverride( + com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout + ) + } + + private fun setupDisplayInfo( + displayCutout: DisplayCutout? = null, + @Surface.Rotation rotation: Int = Surface.ROTATION_0, + displayId: Int = -1 + ) { + doAnswer { + it.getArgument(0).let { info -> + info.displayCutout = displayCutout + info.rotation = rotation + info.displayId = displayId + } + true + }.`when`(display).getDisplayInfo(any()) + } + + private fun getCutout( + safeInsets: Insets, + cutoutBounds: Array, + @Surface.Rotation rotation: Int = Surface.ROTATION_0, + cutoutParentSizeForRotate: Size = Size(100, 200) + ): DisplayCutout { + val insets = RotationUtils.rotateInsets(safeInsets, rotation) + val sorted = arrayOfNulls(BOUNDS_POSITION_LENGTH) + for (pos in 0 until BOUNDS_POSITION_LENGTH) { + val rotatedPos = (pos - rotation + BOUNDS_POSITION_LENGTH) % BOUNDS_POSITION_LENGTH + if (cutoutBounds[pos] != null) { + RotationUtils.rotateBounds( + cutoutBounds[pos], + cutoutParentSizeForRotate.width, + cutoutParentSizeForRotate.height, + rotation + ) + } + sorted[rotatedPos] = cutoutBounds[pos] + } + return DisplayCutout( + insets, + sorted[DisplayCutout.BOUNDS_POSITION_LEFT], + sorted[DisplayCutout.BOUNDS_POSITION_TOP], + sorted[DisplayCutout.BOUNDS_POSITION_RIGHT], + sorted[DisplayCutout.BOUNDS_POSITION_BOTTOM] + ) + } + + @Test + fun testGetNothingIfNoCutout() { + setupFillCutout(false) + + Assert.assertFalse(factory.hasProviders) + Assert.assertEquals(0, factory.providers.size) + } + + @Test + fun testGetTopCutoutProvider() { + setupFillCutout(true) + setupDisplayInfo( + getCutout( + safeInsets = Insets.of(0, 1, 0, 0), + cutoutBounds = arrayOf(null, Rect(9, 0, 10, 1), null, null) + ) + ) + + Assert.assertTrue(factory.hasProviders) + + val providers = factory.providers + Assert.assertEquals(1, providers.size) + Assert.assertEquals(1, providers[0].numOfAlignedBound) + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_TOP, providers[0].alignedBounds[0]) + } + + @Test + fun testGetBottomCutoutProviderOnLandscape() { + setupFillCutout(true) + setupDisplayInfo( + getCutout( + safeInsets = Insets.of(0, 0, 0, 1), + cutoutBounds = arrayOf(null, null, null, Rect(45, 199, 55, 200)), + rotation = Surface.ROTATION_90 + ), + Surface.ROTATION_90 + ) + + Assert.assertTrue(factory.hasProviders) + + val providers = factory.providers + Assert.assertEquals(1, providers.size) + Assert.assertEquals(1, providers[0].numOfAlignedBound) + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_BOTTOM, providers[0].alignedBounds[0]) + } + + @Test + fun testGetLeftCutoutProviderOnSeascape() { + setupFillCutout(true) + setupDisplayInfo( + getCutout( + safeInsets = Insets.of(1, 0, 0, 0), + cutoutBounds = arrayOf(Rect(0, 20, 1, 40), null, null, null), + rotation = Surface.ROTATION_270 + ), + Surface.ROTATION_270 + ) + + Assert.assertTrue(factory.hasProviders) + + val providers = factory.providers + Assert.assertEquals(1, providers.size) + Assert.assertEquals(1, providers[0].numOfAlignedBound) + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_LEFT, providers[0].alignedBounds[0]) + } + + @Test + fun testGetTopRightCutoutProviderOnReverse() { + setupFillCutout(true) + setupDisplayInfo( + getCutout( + safeInsets = Insets.of(0, 1, 1, 0), + cutoutBounds = arrayOf( + null, + Rect(9, 0, 10, 1), + Rect(99, 40, 100, 60), + null + ), + rotation = Surface.ROTATION_180 + ), + Surface.ROTATION_180 + ) + + Assert.assertTrue(factory.hasProviders) + + val providers = factory.providers + Assert.assertEquals(2, providers.size) + Assert.assertEquals(1, providers[0].numOfAlignedBound) + Assert.assertEquals(1, providers[1].numOfAlignedBound) + providers.sortedBy { it.alignedBounds[0] }.let { + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_TOP, it[0].alignedBounds[0]) + Assert.assertEquals(DisplayCutout.BOUNDS_POSITION_RIGHT, it[1].alignedBounds[0]) + } + } +}