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