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