From 724172b7597d30504a55ea68a7315b005a6d6e6b Mon Sep 17 00:00:00 2001 From: Milton Wu Date: Thu, 23 Dec 2021 15:16:17 +0800 Subject: [PATCH] Move Privacy dots to new DecorProvider Provide privacy dot information as DecorProvider, and we can reduce the dependency to privacy dot, and let ScreenDecorations focus on interactions between DecorProvider and related views life cycle. Bug: 213291394 Test: atest ScreenDecorationsTest PrivacyDotDecorProviderFactoryTest OverlayWindowTest Change-Id: I2ecab3f145fbb5c72114455054763b5db614a003 --- .../res/layout/privacy_dot_bottom_left.xml | 33 +++ .../res/layout/privacy_dot_bottom_right.xml | 33 +++ .../res/layout/privacy_dot_top_left.xml | 33 +++ .../res/layout/privacy_dot_top_right.xml | 33 +++ .../res/layout/rounded_corners_bottom.xml | 32 --- .../res/layout/rounded_corners_top.xml | 32 --- .../android/systemui/ScreenDecorations.java | 199 ++++++++++-------- .../android/systemui/decor/DecorProvider.kt | 71 +++++++ .../systemui/decor/DecorProviderFactory.kt | 22 ++ .../android/systemui/decor/OverlayWindow.kt | 59 ++++++ .../decor/PrivacyDotDecorProviderFactory.kt | 87 ++++++++ .../systemui/ScreenDecorationsTest.java | 170 +++++++++++---- .../systemui/decor/OverlayWindowTest.kt | 106 ++++++++++ .../PrivacyDotDecorProviderFactoryTest.kt | 88 ++++++++ 14 files changed, 811 insertions(+), 187 deletions(-) create mode 100644 packages/SystemUI/res/layout/privacy_dot_bottom_left.xml create mode 100644 packages/SystemUI/res/layout/privacy_dot_bottom_right.xml create mode 100644 packages/SystemUI/res/layout/privacy_dot_top_left.xml create mode 100644 packages/SystemUI/res/layout/privacy_dot_top_right.xml create mode 100644 packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt create mode 100644 packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt create mode 100644 packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt create mode 100644 packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt diff --git a/packages/SystemUI/res/layout/privacy_dot_bottom_left.xml b/packages/SystemUI/res/layout/privacy_dot_bottom_left.xml new file mode 100644 index 0000000000000..328570b29b69a --- /dev/null +++ b/packages/SystemUI/res/layout/privacy_dot_bottom_left.xml @@ -0,0 +1,33 @@ + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/privacy_dot_bottom_right.xml b/packages/SystemUI/res/layout/privacy_dot_bottom_right.xml new file mode 100644 index 0000000000000..34b74f36a2d46 --- /dev/null +++ b/packages/SystemUI/res/layout/privacy_dot_bottom_right.xml @@ -0,0 +1,33 @@ + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/privacy_dot_top_left.xml b/packages/SystemUI/res/layout/privacy_dot_top_left.xml new file mode 100644 index 0000000000000..ea6c886a4233e --- /dev/null +++ b/packages/SystemUI/res/layout/privacy_dot_top_left.xml @@ -0,0 +1,33 @@ + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/privacy_dot_top_right.xml b/packages/SystemUI/res/layout/privacy_dot_top_right.xml new file mode 100644 index 0000000000000..bcda0da187784 --- /dev/null +++ b/packages/SystemUI/res/layout/privacy_dot_top_right.xml @@ -0,0 +1,33 @@ + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml index f91ab6f4980ab..b2857abc19d2c 100644 --- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml +++ b/packages/SystemUI/res/layout/rounded_corners_bottom.xml @@ -27,22 +27,6 @@ android:tint="#ff000000" android:src="@drawable/rounded_corner_bottom"/> - - - - - - - - diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml index 819a9a4e9b029..9937c215e71cd 100644 --- a/packages/SystemUI/res/layout/rounded_corners_top.xml +++ b/packages/SystemUI/res/layout/rounded_corners_top.xml @@ -27,22 +27,6 @@ android:tint="#ff000000" android:src="@drawable/rounded_corner_top"/> - - - - - - - - diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 9c2971cda4938..783415e98875f 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -32,6 +32,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.Dimension; +import android.annotation.IdRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; @@ -87,6 +88,11 @@ import com.android.systemui.animation.Interpolators; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; +import com.android.systemui.decor.DecorProvider; +import com.android.systemui.decor.DecorProviderFactory; +import com.android.systemui.decor.DecorProviderKt; +import com.android.systemui.decor.OverlayWindow; +import com.android.systemui.decor.PrivacyDotDecorProviderFactory; import com.android.systemui.qs.SettingObserver; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.events.PrivacyDotViewController; @@ -104,6 +110,8 @@ import java.util.concurrent.Executor; import javax.inject.Inject; +import kotlin.Pair; + /** * An overlay that draws screen decorations in software (e.g for rounded corners or display cutout) * for antialiasing and emulation purposes. @@ -135,6 +143,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private final UserTracker mUserTracker; private final PrivacyDotViewController mDotViewController; private final ThreadFactory mThreadFactory; + private final DecorProviderFactory mDotFactory; //TODO: These are piecemeal being updated to Points for now to support non-square rounded // corners. for now it is only supposed when reading the intrinsic size from the drawables with @@ -146,14 +155,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab @VisibleForTesting protected Point mRoundedDefaultBottom = new Point(0, 0); @VisibleForTesting - protected View[] mOverlays; + protected OverlayWindow[] mOverlays = null; @Nullable private DisplayCutoutView[] mCutoutViews; - //TODO: - View mTopLeftDot; - View mTopRightDot; - View mBottomLeftDot; - View mBottomRightDot; private float mDensity; private WindowManager mWindowManager; private int mRotation; @@ -162,7 +166,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private Handler mHandler; private boolean mPendingRotationChange; private boolean mIsRoundedCornerMultipleRadius; - private boolean mIsPrivacyDotEnabled; private Drawable mRoundedCornerDrawable; private Drawable mRoundedCornerDrawableTop; private Drawable mRoundedCornerDrawableBottom; @@ -227,7 +230,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab TunerService tunerService, UserTracker userTracker, PrivacyDotViewController dotViewController, - ThreadFactory threadFactory) { + ThreadFactory threadFactory, + PrivacyDotDecorProviderFactory dotFactory) { super(context); mMainExecutor = mainExecutor; mSecureSettings = secureSettings; @@ -236,6 +240,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mUserTracker = userTracker; mDotViewController = dotViewController; mThreadFactory = threadFactory; + mDotFactory = dotFactory; } @Override @@ -250,11 +255,14 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab mDotViewController.setUiExecutor(mExecutor); } + private boolean isPrivacyDotEnabled() { + return mDotFactory.getHasProviders(); + } + private void startOnScreenDecorationsThread() { mRotation = mContext.getDisplay().getRotation(); mDisplayUniqueId = mContext.getDisplay().getUniqueId(); mIsRoundedCornerMultipleRadius = isRoundedCornerMultipleRadius(mContext, mDisplayUniqueId); - mIsPrivacyDotEnabled = mContext.getResources().getBoolean(R.bool.config_enablePrivacyDot); mWindowManager = mContext.getSystemService(WindowManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class); updateRoundedCornerDrawable(); @@ -292,8 +300,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (mOverlays[i] != null) { - mOverlays[i].getViewTreeObserver().addOnPreDrawListener( - new RestartingPreDrawListener(mOverlays[i], i, newRotation)); + final ViewGroup overlayView = mOverlays[i].getRootView(); + overlayView.getViewTreeObserver().addOnPreDrawListener( + new RestartingPreDrawListener(overlayView, i, newRotation)); } } } @@ -313,24 +322,64 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab updateOrientation(); } + @Nullable + private View getOverlayView(@IdRes int id) { + if (mOverlays == null) { + return null; + } + + for (final OverlayWindow overlay : mOverlays) { + if (overlay == null) { + continue; + } + + final View view = overlay.getView(id); + if (view != null) { + return view; + } + } + return null; + } + + private void removeOverlayView(@IdRes int id) { + if (mOverlays == null) { + return; + } + + for (final OverlayWindow overlay : mOverlays) { + if (overlay == null) { + continue; + } + + overlay.removeView(id); + } + } + private void setupDecorations() { - if (hasRoundedCorners() || shouldDrawCutout() || mIsPrivacyDotEnabled) { + List decorProviders = mDotFactory.getProviders(); + + if (hasRoundedCorners() || shouldDrawCutout() || !decorProviders.isEmpty()) { final DisplayCutout cutout = getCutout(); for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { if (shouldShowCutout(i, cutout) || shouldShowRoundedCorner(i, cutout) || shouldShowPrivacyDot(i, cutout)) { - createOverlay(i, cutout); + Pair, List> pair = + DecorProviderKt.partitionAlignedBound(decorProviders, i); + decorProviders = pair.getSecond(); + createOverlay(i, cutout, pair.getFirst()); } else { removeOverlay(i); } } - if (mTopLeftDot != null && mTopRightDot != null && mBottomLeftDot != null - && mBottomRightDot != null) { + final View tl, tr, bl, br; + if ((tl = getOverlayView(R.id.privacy_dot_top_left_container)) != null + && (tr = getOverlayView(R.id.privacy_dot_top_right_container)) != null + && (bl = getOverlayView(R.id.privacy_dot_bottom_left_container)) != null + && (br = getOverlayView(R.id.privacy_dot_bottom_right_container)) != null) { // Overlays have been created, send the dots to the controller //TODO: need a better way to do this - mDotViewController.initialize( - mTopLeftDot, mTopRightDot, mBottomLeftDot, mBottomRightDot); + mDotViewController.initialize(tl, tr, bl, br); } } else { removeAllOverlays(); @@ -414,13 +463,16 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mOverlays == null || mOverlays[pos] == null) { return; } - mWindowManager.removeViewImmediate(mOverlays[pos]); + mWindowManager.removeViewImmediate(mOverlays[pos].getRootView()); mOverlays[pos] = null; } - private void createOverlay(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { + private void createOverlay( + @BoundsPosition int pos, + @Nullable DisplayCutout cutout, + @NonNull List decorProviders) { if (mOverlays == null) { - mOverlays = new View[BOUNDS_POSITION_LENGTH]; + mOverlays = new OverlayWindow[BOUNDS_POSITION_LENGTH]; } if (mCutoutViews == null) { @@ -430,78 +482,49 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mOverlays[pos] != null) { return; } - mOverlays[pos] = overlayForPosition(pos, cutout); + + mOverlays[pos] = overlayForPosition(pos, decorProviders); mCutoutViews[pos] = new DisplayCutoutView(mContext, pos, this); - ((ViewGroup) mOverlays[pos]).addView(mCutoutViews[pos]); + mOverlays[pos].getRootView().addView(mCutoutViews[pos]); - mOverlays[pos].setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); - mOverlays[pos].setAlpha(0); - mOverlays[pos].setForceDarkAllowed(false); + final ViewGroup overlayView = mOverlays[pos].getRootView(); + overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + overlayView.setAlpha(0); + overlayView.setForceDarkAllowed(false); updateView(pos, cutout); - mWindowManager.addView(mOverlays[pos], getWindowLayoutParams(pos)); + mWindowManager.addView(overlayView, getWindowLayoutParams(pos)); - mOverlays[pos].addOnLayoutChangeListener(new OnLayoutChangeListener() { + overlayView.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - mOverlays[pos].removeOnLayoutChangeListener(this); - mOverlays[pos].animate() + overlayView.removeOnLayoutChangeListener(this); + overlayView.animate() .alpha(1) .setDuration(1000) .start(); } }); - mOverlays[pos].getViewTreeObserver().addOnPreDrawListener( - new ValidatingPreDrawListener(mOverlays[pos])); + mOverlays[pos].getRootView().getViewTreeObserver().addOnPreDrawListener( + new ValidatingPreDrawListener(mOverlays[pos].getRootView())); } /** * Allow overrides for top/bottom positions */ - private View overlayForPosition(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { - final int layoutId = (pos == BOUNDS_POSITION_LEFT || pos == BOUNDS_POSITION_TOP) - ? R.layout.rounded_corners_top : R.layout.rounded_corners_bottom; - final ViewGroup vg = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, null); - initPrivacyDotView(vg, pos, cutout); - return vg; - } - - private void initPrivacyDotView(@NonNull ViewGroup viewGroup, @BoundsPosition int pos, - @Nullable DisplayCutout cutout) { - final View left = viewGroup.findViewById(R.id.privacy_dot_left_container); - final View right = viewGroup.findViewById(R.id.privacy_dot_right_container); - if (!shouldShowPrivacyDot(pos, cutout)) { - viewGroup.removeView(left); - viewGroup.removeView(right); - return; - } - - switch (pos) { - case BOUNDS_POSITION_LEFT: { - mTopLeftDot = left; - mBottomLeftDot = right; - break; - } - case BOUNDS_POSITION_TOP: { - mTopLeftDot = left; - mTopRightDot = right; - break; - } - case BOUNDS_POSITION_RIGHT: { - mTopRightDot = left; - mBottomRightDot = right; - break; - } - case BOUNDS_POSITION_BOTTOM: { - mBottomLeftDot = left; - mBottomRightDot = right; - break; - } - } + private OverlayWindow overlayForPosition( + @BoundsPosition int pos, + @NonNull List decorProviders) { + final OverlayWindow currentOverlay = new OverlayWindow(LayoutInflater.from(mContext), pos); + decorProviders.forEach(provider -> { + removeOverlayView(provider.getViewId()); + currentOverlay.addDecorProvider(provider, mRotation); + }); + return currentOverlay; } private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { @@ -638,12 +661,15 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mOverlays[i] == null) { continue; } - final int size = ((ViewGroup) mOverlays[i]).getChildCount(); + final ViewGroup overlayView = mOverlays[i].getRootView(); + final int size = overlayView.getChildCount(); View child; for (int j = 0; j < size; j++) { - child = ((ViewGroup) mOverlays[i]).getChildAt(j); - if (child.getId() == R.id.privacy_dot_left_container - || child.getId() == R.id.privacy_dot_right_container) { + child = overlayView.getChildAt(j); + if (child.getId() == R.id.privacy_dot_top_left_container + || child.getId() == R.id.privacy_dot_top_right_container + || child.getId() == R.id.privacy_dot_bottom_left_container + || child.getId() == R.id.privacy_dot_bottom_right_container) { // Exclude privacy dot from color inversion (for now?) continue; } @@ -684,13 +710,18 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab pw.println("ScreenDecorations state:"); pw.println(" DEBUG_DISABLE_SCREEN_DECORATIONS:" + DEBUG_DISABLE_SCREEN_DECORATIONS); pw.println(" mIsRoundedCornerMultipleRadius:" + mIsRoundedCornerMultipleRadius); - pw.println(" mIsPrivacyDotEnabled:" + mIsPrivacyDotEnabled); + pw.println(" mIsPrivacyDotEnabled:" + isPrivacyDotEnabled()); pw.println(" mPendingRotationChange:" + mPendingRotationChange); pw.println(" mRoundedDefault(x,y)=(" + mRoundedDefault.x + "," + mRoundedDefault.y + ")"); pw.println(" mRoundedDefaultTop(x,y)=(" + mRoundedDefaultTop.x + "," + mRoundedDefaultTop.y + ")"); pw.println(" mRoundedDefaultBottom(x,y)=(" + mRoundedDefaultBottom.x + "," + mRoundedDefaultBottom.y + ")"); + pw.println(" mOverlays(left,top,right,bottom)=(" + + (mOverlays != null && mOverlays[BOUNDS_POSITION_LEFT] != null) + "," + + (mOverlays != null && mOverlays[BOUNDS_POSITION_TOP] != null) + "," + + (mOverlays != null && mOverlays[BOUNDS_POSITION_RIGHT] != null) + "," + + (mOverlays != null && mOverlays[BOUNDS_POSITION_BOTTOM] != null) + ")"); } private void updateOrientation() { @@ -843,7 +874,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab private void updateRoundedCornerView(@BoundsPosition int pos, int id, @Nullable DisplayCutout cutout) { - final View rounded = mOverlays[pos].findViewById(id); + final View rounded = mOverlays[pos].getRootView().findViewById(id); if (rounded == null) { return; } @@ -929,7 +960,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private boolean shouldShowPrivacyDot(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { - return mIsPrivacyDotEnabled && isDefaultShownOverlayPos(pos, cutout); + return isPrivacyDotEnabled() && isDefaultShownOverlayPos(pos, cutout); } private boolean shouldShowCutout(@BoundsPosition int pos, @Nullable DisplayCutout cutout) { @@ -955,7 +986,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mOverlays[i] == null) { continue; } - mWindowManager.updateViewLayout(mOverlays[i], getWindowLayoutParams(i)); + mWindowManager.updateViewLayout(mOverlays[i].getRootView(), getWindowLayoutParams(i)); } } @@ -1003,9 +1034,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mOverlays[i] == null) { continue; } - ((ImageView) mOverlays[i].findViewById(R.id.left)).setImageDrawable( + final ViewGroup overlayView = mOverlays[i].getRootView(); + ((ImageView) overlayView.findViewById(R.id.left)).setImageDrawable( isTopRoundedCorner(i, R.id.left) ? top : bottom); - ((ImageView) mOverlays[i].findViewById(R.id.right)).setImageDrawable( + ((ImageView) overlayView.findViewById(R.id.right)).setImageDrawable( isTopRoundedCorner(i, R.id.right) ? top : bottom); } } @@ -1047,9 +1079,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab if (mOverlays[i] == null) { continue; } - setSize(mOverlays[i].findViewById(R.id.left), + final ViewGroup overlayView = mOverlays[i].getRootView(); + setSize(overlayView.findViewById(R.id.left), isTopRoundedCorner(i, R.id.left) ? sizeTop : sizeBottom); - setSize(mOverlays[i].findViewById(R.id.right), + setSize(overlayView.findViewById(R.id.right), isTopRoundedCorner(i, R.id.right) ? sizeTop : sizeBottom); } } diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt new file mode 100644 index 0000000000000..3543bb4ab9e96 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt @@ -0,0 +1,71 @@ +/* + * 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.view.DisplayCutout +import android.view.LayoutInflater +import android.view.Surface +import android.view.View +import android.view.ViewGroup + +/** + * An interface for providing view with a specific functionality. Take an example, if privacy dot + * is enabled, there are 4 DecorProviders which are used to provide privacy dot views on top-left, + * top-right, bottom-left, bottom-right. + */ +abstract class DecorProvider { + + /** Id for the view which is created through inflateView() */ + abstract val viewId: Int + + /** The number of total aligned bounds */ + val numOfAlignedEdge: Int + get() = alignedBounds.size + + /** The aligned bounds for the view which is created through inflateView() */ + abstract val alignedBounds: List + + /** Inflate view into parent as current rotation */ + abstract fun inflateView( + inflater: LayoutInflater, + parent: ViewGroup, + @Surface.Rotation rotation: 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) } +} + +/** + * A provider for view shown on corner. + */ +abstract class CornerDecorProvider : DecorProvider() { + /** The first bound which a corner view is aligned based on rotation 0 */ + @DisplayCutout.BoundsPosition protected abstract val alignedBound1: Int + /** The second bound which a corner view is aligned based on rotation 0 */ + @DisplayCutout.BoundsPosition protected abstract val alignedBound2: Int + + override val alignedBounds: List by lazy { + listOf(alignedBound1, alignedBound2) + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt new file mode 100644 index 0000000000000..c60cad8419d29 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt @@ -0,0 +1,22 @@ +/* + * 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 + +abstract class DecorProviderFactory { + abstract val providers: List + abstract val hasProviders: Boolean +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt new file mode 100644 index 0000000000000..9f8679cdea4a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt @@ -0,0 +1,59 @@ +/* + * 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.annotation.IdRes +import android.view.DisplayCutout +import android.view.LayoutInflater +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import com.android.systemui.R +import java.util.HashMap + +class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos: Int) { + + private val layoutId: Int + get() { + return if (pos == DisplayCutout.BOUNDS_POSITION_LEFT || + pos == DisplayCutout.BOUNDS_POSITION_TOP) { + R.layout.rounded_corners_top + } else { + R.layout.rounded_corners_bottom + } + } + + val rootView = layoutInflater.inflate(layoutId, null) as ViewGroup + private val viewProviderMap: MutableMap> = HashMap() + + fun addDecorProvider(decorProvider: DecorProvider, @Surface.Rotation rotation: Int) { + val view = decorProvider.inflateView(layoutInflater, rootView, rotation) + viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider) + } + + fun getView(@IdRes id: Int): View? { + val pair = viewProviderMap[id] + return pair?.first + } + + fun removeView(@IdRes id: Int) { + val view = getView(id) + if (view != null) { + rootView.removeView(view) + viewProviderMap.remove(id) + } + } +} \ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt new file mode 100644 index 0000000000000..7afd7e0eedc51 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt @@ -0,0 +1,87 @@ +/* + * 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.view.DisplayCutout +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.Surface +import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Main +import javax.inject.Inject + +@SysUISingleton +class PrivacyDotDecorProviderFactory @Inject constructor( + @Main private val res: Resources +) : DecorProviderFactory() { + + private val isPrivacyDotEnabled: Boolean + get() = res.getBoolean(R.bool.config_enablePrivacyDot) + + override val hasProviders: Boolean + get() = isPrivacyDotEnabled + + override val providers: List + get() { + return if (hasProviders) { + listOf( + PrivacyDotCornerDecorProviderImpl( + viewId = R.id.privacy_dot_top_left_container, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT, + layoutId = R.layout.privacy_dot_top_left), + PrivacyDotCornerDecorProviderImpl( + viewId = R.id.privacy_dot_top_right_container, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT, + layoutId = R.layout.privacy_dot_top_right), + PrivacyDotCornerDecorProviderImpl( + viewId = R.id.privacy_dot_bottom_left_container, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT, + layoutId = R.layout.privacy_dot_bottom_left), + PrivacyDotCornerDecorProviderImpl( + viewId = R.id.privacy_dot_bottom_right_container, + alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM, + alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT, + layoutId = R.layout.privacy_dot_bottom_right) + ) + } else { + emptyList() + } + } +} + +class PrivacyDotCornerDecorProviderImpl( + override val viewId: Int, + @DisplayCutout.BoundsPosition override val alignedBound1: Int, + @DisplayCutout.BoundsPosition override val alignedBound2: Int, + private val layoutId: Int +) : CornerDecorProvider() { + + override fun inflateView( + inflater: LayoutInflater, + parent: ViewGroup, + @Surface.Rotation rotation: Int + ): View { + inflater.inflate(layoutId, parent, true) + return parent.getChildAt(parent.childCount - 1 /* latest new added child */) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java index c6df1c15e0b22..72d72c8c3b5e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java @@ -45,6 +45,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.annotation.IdRes; import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Insets; @@ -63,9 +64,15 @@ import android.view.View; import android.view.WindowManager; import android.view.WindowMetrics; +import androidx.annotation.Nullable; import androidx.test.filters.SmallTest; import com.android.systemui.broadcast.BroadcastDispatcher; +import com.android.systemui.decor.CornerDecorProvider; +import com.android.systemui.decor.DecorProvider; +import com.android.systemui.decor.OverlayWindow; +import com.android.systemui.decor.PrivacyDotCornerDecorProviderImpl; +import com.android.systemui.decor.PrivacyDotDecorProviderFactory; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.events.PrivacyDotViewController; import com.android.systemui.tuner.TunerService; @@ -81,6 +88,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.ArrayList; import java.util.Collections; @RunWithLooper @@ -96,6 +104,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { private SecureSettings mSecureSettings; private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private FakeThreadFactory mThreadFactory; + private ArrayList mDecorProviders; @Mock private TunerService mTunerService; @Mock @@ -106,6 +115,16 @@ public class ScreenDecorationsTest extends SysuiTestCase { private PrivacyDotViewController mDotViewController; @Mock private TypedArray mMockTypedArray; + @Mock + private PrivacyDotDecorProviderFactory mPrivacyDotDecorProviderFactory; + @Mock + private CornerDecorProvider mPrivacyDotTopLeftDecorProvider; + @Mock + private CornerDecorProvider mPrivacyDotTopRightDecorProvider; + @Mock + private CornerDecorProvider mPrivacyDotBottomLeftDecorProvider; + @Mock + private CornerDecorProvider mPrivacyDotBottomRightDecorProvider; @Before public void setup() { @@ -129,9 +148,33 @@ public class ScreenDecorationsTest extends SysuiTestCase { mContext.addMockSystemService(DisplayManager.class, mDisplayManager); when(mMockTypedArray.length()).thenReturn(0); + mPrivacyDotTopLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( + R.id.privacy_dot_top_left_container, + DisplayCutout.BOUNDS_POSITION_TOP, + DisplayCutout.BOUNDS_POSITION_LEFT, + R.layout.privacy_dot_top_left)); + + mPrivacyDotTopRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( + R.id.privacy_dot_top_right_container, + DisplayCutout.BOUNDS_POSITION_TOP, + DisplayCutout.BOUNDS_POSITION_RIGHT, + R.layout.privacy_dot_top_right)); + + mPrivacyDotBottomLeftDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( + R.id.privacy_dot_bottom_left_container, + DisplayCutout.BOUNDS_POSITION_BOTTOM, + DisplayCutout.BOUNDS_POSITION_LEFT, + R.layout.privacy_dot_bottom_left)); + + mPrivacyDotBottomRightDecorProvider = spy(new PrivacyDotCornerDecorProviderImpl( + R.id.privacy_dot_bottom_right_container, + DisplayCutout.BOUNDS_POSITION_BOTTOM, + DisplayCutout.BOUNDS_POSITION_RIGHT, + R.layout.privacy_dot_bottom_right)); + mScreenDecorations = spy(new ScreenDecorations(mContext, mExecutor, mSecureSettings, mBroadcastDispatcher, mTunerService, mUserTracker, mDotViewController, - mThreadFactory) { + mThreadFactory, mPrivacyDotDecorProviderFactory) { @Override public void start() { super.start(); @@ -157,7 +200,7 @@ public class ScreenDecorationsTest extends SysuiTestCase { private void verifyRoundedCornerViewsVisibility( @DisplayCutout.BoundsPosition final int overlayId, @View.Visibility final int visibility) { - final View overlay = mScreenDecorations.mOverlays[overlayId]; + final View overlay = mScreenDecorations.mOverlays[overlayId].getRootView(); final View left = overlay.findViewById(R.id.left); final View right = overlay.findViewById(R.id.right); assertNotNull(left); @@ -166,23 +209,42 @@ public class ScreenDecorationsTest extends SysuiTestCase { assertThat(right.getVisibility()).isEqualTo(visibility); } + @Nullable + private View findViewFromOverlays(@IdRes int id) { + for (OverlayWindow overlay: mScreenDecorations.mOverlays) { + if (overlay == null) { + continue; + } + + View view = overlay.getRootView().findViewById(id); + if (view != null) { + return view; + } + } + return null; + } + private void verifyTopDotViewsNullable(final boolean isAssertNull) { + View tl = findViewFromOverlays(R.id.privacy_dot_top_left_container); + View tr = findViewFromOverlays(R.id.privacy_dot_top_right_container); if (isAssertNull) { - assertNull(mScreenDecorations.mTopLeftDot); - assertNull(mScreenDecorations.mTopRightDot); + assertNull(tl); + assertNull(tr); } else { - assertNotNull(mScreenDecorations.mTopLeftDot); - assertNotNull(mScreenDecorations.mTopRightDot); + assertNotNull(tl); + assertNotNull(tr); } } private void verifyBottomDotViewsNullable(final boolean isAssertNull) { + View bl = findViewFromOverlays(R.id.privacy_dot_bottom_left_container); + View br = findViewFromOverlays(R.id.privacy_dot_bottom_right_container); if (isAssertNull) { - assertNull(mScreenDecorations.mBottomLeftDot); - assertNull(mScreenDecorations.mBottomRightDot); + assertNull(bl); + assertNull(br); } else { - assertNotNull(mScreenDecorations.mBottomLeftDot); - assertNotNull(mScreenDecorations.mBottomRightDot); + assertNotNull(bl); + assertNotNull(br); } } @@ -193,14 +255,18 @@ public class ScreenDecorationsTest extends SysuiTestCase { private void verifyTopDotViewsVisibility(@View.Visibility final int visibility) { verifyTopDotViewsNullable(false); - assertThat(mScreenDecorations.mTopLeftDot.getVisibility()).isEqualTo(visibility); - assertThat(mScreenDecorations.mTopRightDot.getVisibility()).isEqualTo(visibility); + View tl = findViewFromOverlays(R.id.privacy_dot_top_left_container); + View tr = findViewFromOverlays(R.id.privacy_dot_top_right_container); + assertThat(tl.getVisibility()).isEqualTo(visibility); + assertThat(tr.getVisibility()).isEqualTo(visibility); } private void verifyBottomDotViewsVisibility(@View.Visibility final int visibility) { verifyBottomDotViewsNullable(false); - assertThat(mScreenDecorations.mBottomLeftDot.getVisibility()).isEqualTo(visibility); - assertThat(mScreenDecorations.mBottomRightDot.getVisibility()).isEqualTo(visibility); + View bl = findViewFromOverlays(R.id.privacy_dot_bottom_left_container); + View br = findViewFromOverlays(R.id.privacy_dot_bottom_right_container); + assertThat(bl.getVisibility()).isEqualTo(visibility); + assertThat(br.getVisibility()).isEqualTo(visibility); } private void verifyDotViewsVisibility(@View.Visibility final int visibility) { @@ -219,32 +285,32 @@ public class ScreenDecorationsTest extends SysuiTestCase { if (left) { assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]); - verify(mWindowManager, times(1)) - .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]), any()); + verify(mWindowManager, times(1)).addView( + eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()), any()); } else { assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT]); } if (top) { assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]); - verify(mWindowManager, times(1)) - .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]), any()); + verify(mWindowManager, times(1)).addView( + eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()), any()); } else { assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP]); } if (right) { assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]); - verify(mWindowManager, times(1)) - .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]), any()); + verify(mWindowManager, times(1)).addView( + eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()), any()); } else { assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT]); } if (bottom) { assertNotNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]); - verify(mWindowManager, times(1)) - .addView(eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]), any()); + verify(mWindowManager, times(1)).addView( + eq(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()), any()); } else { assertNull(mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM]); } @@ -381,18 +447,18 @@ public class ScreenDecorationsTest extends SysuiTestCase { doReturn(null).when(mScreenDecorations).getCutout(); mScreenDecorations.start(); - View leftRoundedCorner = - mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].findViewById(R.id.left); - View rightRoundedCorner = - mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].findViewById(R.id.right); + View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView() + .findViewById(R.id.left); + View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView() + .findViewById(R.id.right); verify(mScreenDecorations, atLeastOnce()) .setSize(leftRoundedCorner, new Point(testTopRadius, testTopRadius)); verify(mScreenDecorations, atLeastOnce()) .setSize(rightRoundedCorner, new Point(testTopRadius, testTopRadius)); - leftRoundedCorner = - mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].findViewById(R.id.left); - rightRoundedCorner = - mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].findViewById(R.id.right); + leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView() + .findViewById(R.id.left); + rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView() + .findViewById(R.id.right); verify(mScreenDecorations, atLeastOnce()) .setSize(leftRoundedCorner, new Point(testBottomRadius, testBottomRadius)); verify(mScreenDecorations, atLeastOnce()) @@ -414,26 +480,26 @@ public class ScreenDecorationsTest extends SysuiTestCase { mScreenDecorations.start(); final Point topRadius = new Point(testTopRadius, testTopRadius); final Point bottomRadius = new Point(testBottomRadius, testBottomRadius); - View leftRoundedCorner = - mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.left); + View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() + .findViewById(R.id.left); boolean isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.left); verify(mScreenDecorations, atLeastOnce()) .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius); - View rightRoundedCorner = - mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].findViewById(R.id.right); + View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() + .findViewById(R.id.right); isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.right); verify(mScreenDecorations, atLeastOnce()) .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius); - leftRoundedCorner = - mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.left); + leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView() + .findViewById(R.id.left); isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.left); verify(mScreenDecorations, atLeastOnce()) .setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius); - rightRoundedCorner = - mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].findViewById(R.id.right); + rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView() + .findViewById(R.id.right); isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.right); verify(mScreenDecorations, atLeastOnce()) .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius); @@ -790,6 +856,22 @@ public class ScreenDecorationsTest extends SysuiTestCase { mScreenDecorations.onConfigurationChanged(new Configuration()); verifyOverlaysExistAndAdded(true, false, true, false); + + // Verify each privacy dot id appears only once + mDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> { + int findCount = 0; + for (OverlayWindow overlay: mScreenDecorations.mOverlays) { + if (overlay == null) { + continue; + } + final View view = overlay.getRootView().findViewById(viewId); + if (view != null) { + findCount++; + } + } + assertEquals(1, findCount); + }); + } @Test @@ -985,8 +1067,16 @@ public class ScreenDecorationsTest extends SysuiTestCase { R.bool.config_roundedCornerMultipleRadius, multipleRadius); mContext.getOrCreateTestableResources().addOverride( com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout); - mContext.getOrCreateTestableResources().addOverride( - R.bool.config_enablePrivacyDot, privacyDot); + + mDecorProviders = new ArrayList<>(); + if (privacyDot) { + mDecorProviders.add(mPrivacyDotTopLeftDecorProvider); + mDecorProviders.add(mPrivacyDotTopRightDecorProvider); + mDecorProviders.add(mPrivacyDotBottomLeftDecorProvider); + mDecorProviders.add(mPrivacyDotBottomRightDecorProvider); + } + when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mDecorProviders); + when(mPrivacyDotDecorProviderFactory.getHasProviders()).thenReturn(privacyDot); } private DisplayCutout getDisplayCutoutForRotation(Insets safeInsets, Rect[] cutoutBounds) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt new file mode 100644 index 0000000000000..ca74df0a23c59 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt @@ -0,0 +1,106 @@ +/* + * 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.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.DisplayCutout +import android.view.LayoutInflater +import android.view.Surface +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.eq +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 +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.spy +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +class OverlayWindowTest : SysuiTestCase() { + + companion object { + private val TEST_DECOR_VIEW_ID = R.id.privacy_dot_bottom_right_container + private val TEST_DECOR_LAYOUT_ID = R.layout.privacy_dot_bottom_right + } + + private lateinit var overlay: OverlayWindow + + @Mock private lateinit var layoutInflater: LayoutInflater + @Mock private lateinit var decorProvider: DecorProvider + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + layoutInflater = spy(LayoutInflater.from(mContext)) + + overlay = OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT) + + whenever(decorProvider.viewId).thenReturn(TEST_DECOR_VIEW_ID) + whenever(decorProvider.inflateView( + eq(layoutInflater), + eq(overlay.rootView), + anyInt()) + ).then { + val layoutInflater = it.getArgument(0) + val parent = it.getArgument(1) + layoutInflater.inflate(TEST_DECOR_LAYOUT_ID, parent) + return@then parent.getChildAt(parent.childCount - 1) + } + } + + @Test + fun testAnyBoundsPositionShallNoExceptionForConstructor() { + OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_LEFT) + OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_TOP) + OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_RIGHT) + OverlayWindow(layoutInflater, DisplayCutout.BOUNDS_POSITION_BOTTOM) + } + + @Test + fun testAddProvider() { + @Surface.Rotation val rotation = Surface.ROTATION_270 + overlay.addDecorProvider(decorProvider, rotation) + verify(decorProvider, Mockito.times(1)).inflateView( + eq(layoutInflater), eq(overlay.rootView), eq(rotation)) + val viewFoundFromRootView = overlay.rootView.findViewById(TEST_DECOR_VIEW_ID) + Assert.assertNotNull(viewFoundFromRootView) + Assert.assertEquals(viewFoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID)) + } + + @Test + fun testRemoveView() { + @Surface.Rotation val rotation = Surface.ROTATION_270 + overlay.addDecorProvider(decorProvider, rotation) + overlay.removeView(TEST_DECOR_VIEW_ID) + val viewFoundFromRootView = overlay.rootView.findViewById(TEST_DECOR_VIEW_ID) + Assert.assertNull(viewFoundFromRootView) + Assert.assertNull(overlay.getView(TEST_DECOR_LAYOUT_ID)) + } +} \ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt new file mode 100644 index 0000000000000..bac08176d2ebc --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt @@ -0,0 +1,88 @@ +/* + * 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.testing.AndroidTestingRunner +import android.testing.TestableLooper.RunWithLooper +import android.view.DisplayCutout +import androidx.test.filters.SmallTest +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +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.spy +import org.mockito.Mockito.`when` as whenever + +@RunWith(AndroidTestingRunner::class) +@RunWithLooper(setAsMainLooper = true) +@SmallTest +class PrivacyDotDecorProviderFactoryTest : SysuiTestCase() { + private lateinit var mPrivacyDotDecorProviderFactory: PrivacyDotDecorProviderFactory + + @Mock private lateinit var resources: Resources + + @Before + fun setUp() { + resources = spy(mContext.resources) + mPrivacyDotDecorProviderFactory = PrivacyDotDecorProviderFactory(resources) + } + + private fun setPrivacyDotResources(isEnable: Boolean) { + whenever(resources.getBoolean(R.bool.config_enablePrivacyDot)).thenReturn(isEnable) + } + + @Test + fun testGetNoCornerDecorProviderWithNoPrivacyDot() { + setPrivacyDotResources(false) + + Assert.assertEquals(false, mPrivacyDotDecorProviderFactory.hasProviders) + Assert.assertEquals(0, mPrivacyDotDecorProviderFactory.providers.size) + } + + @Test + fun testGet4CornerDecorProvidersWithPrivacyDot() { + setPrivacyDotResources(true) + val providers = mPrivacyDotDecorProviderFactory.providers + + Assert.assertEquals(true, mPrivacyDotDecorProviderFactory.hasProviders) + Assert.assertEquals(4, providers.size) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.privacy_dot_top_left_container) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT)) + }) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.privacy_dot_top_right_container) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT)) + }) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.privacy_dot_bottom_left_container) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT)) + }) + Assert.assertEquals(1, providers.count { + ((it.viewId == R.id.privacy_dot_bottom_right_container) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM) + and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT)) + }) + } +} \ No newline at end of file