Merge changes from topic "presubmit-am-9b5e750ffe3a4cb798418c89d9e7f523"

* changes:
  [automerge] Re-land "Move rounded corner to provider" 2p: 4301034952 2p: 88e14532f8
  [automerge] Re-land "Move rounded corner to provider" 2p: 4301034952
  Re-land "Move rounded corner to provider"
diff --git a/packages/SystemUI/res/layout/rounded_corners_bottom.xml b/packages/SystemUI/res/layout/rounded_corners_bottom.xml
deleted file mode 100644
index bb6d4bd..0000000
--- a/packages/SystemUI/res/layout/rounded_corners_bottom.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2020, 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.
--->
-<com.android.systemui.RegionInterceptingFrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/rounded_corners_bottom"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <ImageView
-        android:id="@+id/left"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:layout_gravity="left|bottom"
-        android:tint="#ff000000"
-        android:visibility="gone"
-        android:src="@drawable/rounded_corner_bottom"/>
-
-    <ImageView
-        android:id="@+id/right"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:tint="#ff000000"
-        android:visibility="gone"
-        android:layout_gravity="right|bottom"
-        android:src="@drawable/rounded_corner_bottom"/>
-
-</com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/layout/rounded_corners_top.xml b/packages/SystemUI/res/layout/rounded_corners_top.xml
deleted file mode 100644
index 46648c8..0000000
--- a/packages/SystemUI/res/layout/rounded_corners_top.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2020, 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.
--->
-<com.android.systemui.RegionInterceptingFrameLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/rounded_corners_top"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <ImageView
-        android:id="@+id/left"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:layout_gravity="left|top"
-        android:tint="#ff000000"
-        android:visibility="gone"
-        android:src="@drawable/rounded_corner_top"/>
-
-    <ImageView
-        android:id="@+id/right"
-        android:layout_width="12dp"
-        android:layout_height="12dp"
-        android:tint="#ff000000"
-        android:visibility="gone"
-        android:layout_gravity="right|top"
-        android:src="@drawable/rounded_corner_top"/>
-
-</com.android.systemui.RegionInterceptingFrameLayout>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index ff71b4f..5eacc3e 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -170,5 +170,11 @@
     <item type="id" name="action_move_bottom_right"/>
     <item type="id" name="action_move_to_edge_and_hide"/>
     <item type="id" name="action_move_out_edge_and_show"/>
+
+    <!-- rounded corner view id -->
+    <item type="id" name="rounded_corner_top_left"/>
+    <item type="id" name="rounded_corner_top_right"/>
+    <item type="id" name="rounded_corner_bottom_left"/>
+    <item type="id" name="rounded_corner_bottom_right"/>
 </resources>
 
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 43d91a2..9b09101 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -77,6 +77,7 @@
 import com.android.systemui.decor.DecorProviderKt;
 import com.android.systemui.decor.OverlayWindow;
 import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
+import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
 import com.android.systemui.decor.RoundedCornerResDelegate;
 import com.android.systemui.qs.SettingObserver;
 import com.android.systemui.settings.UserTracker;
@@ -137,6 +138,9 @@
     @VisibleForTesting
     protected RoundedCornerResDelegate mRoundedCornerResDelegate;
     @VisibleForTesting
+    protected DecorProviderFactory mRoundedCornerFactory;
+    private int mProviderRefreshToken = 0;
+    @VisibleForTesting
     protected OverlayWindow[] mOverlays = null;
     @VisibleForTesting
     @Nullable
@@ -282,16 +286,58 @@
         return mDotFactory.getHasProviders();
     }
 
+    @NonNull
+    private List<DecorProvider> getProviders(boolean hasHwLayer) {
+        List<DecorProvider> decorProviders = new ArrayList<>(mDotFactory.getProviders());
+        if (!hasHwLayer) {
+            decorProviders.addAll(mRoundedCornerFactory.getProviders());
+        }
+        return decorProviders;
+    }
+
+    private void updateDisplayIdToProviderFactories() {
+        mDotFactory.onDisplayUniqueIdChanged(mDisplayUniqueId);
+        mRoundedCornerFactory.onDisplayUniqueIdChanged(mDisplayUniqueId);
+    }
+
+    /**
+     * Check that newProviders is the same list with decorProviders inside mOverlay.
+     * @param newProviders expected comparing DecorProviders
+     * @return true if same provider list
+     */
+    @VisibleForTesting
+    boolean hasSameProviders(@NonNull List<DecorProvider> newProviders) {
+        final ArrayList<Integer> overlayViewIds = new ArrayList<>();
+        if (mOverlays != null) {
+            for (OverlayWindow overlay : mOverlays) {
+                if (overlay == null) {
+                    continue;
+                }
+                overlayViewIds.addAll(overlay.getViewIds());
+            }
+        }
+        if (overlayViewIds.size() != newProviders.size()) {
+            return false;
+        }
+
+        for (DecorProvider provider: newProviders) {
+            if (!overlayViewIds.contains(provider.getViewId())) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private void startOnScreenDecorationsThread() {
         mRotation = mContext.getDisplay().getRotation();
         mDisplayUniqueId = mContext.getDisplay().getUniqueId();
         mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(),
                 mDisplayUniqueId);
+        mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate);
         mWindowManager = mContext.getSystemService(WindowManager.class);
         mDisplayManager = mContext.getSystemService(DisplayManager.class);
         mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport();
-        updateRoundedCornerDrawable();
-        updateRoundedCornerRadii();
+        updateHwLayerRoundedCornerDrawable();
         setupDecorations();
         setupCameraListener();
 
@@ -343,18 +389,27 @@
                 final String newUniqueId = mContext.getDisplay().getUniqueId();
                 if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
                     mDisplayUniqueId = newUniqueId;
-                    mRoundedCornerResDelegate.reloadAll(newUniqueId);
                     final DisplayDecorationSupport newScreenDecorationSupport =
                             mContext.getDisplay().getDisplayDecorationSupport();
-                    // When the value of mSupportHwcScreenDecoration is changed, re-setup the whole
-                    // screen decoration.
-                    if (!eq(newScreenDecorationSupport, mHwcScreenDecorationSupport)) {
+
+                    updateDisplayIdToProviderFactories();
+
+                    // When providers or the value of mSupportHwcScreenDecoration is changed,
+                    // re-setup the whole screen decoration.
+                    if (!hasSameProviders(getProviders(newScreenDecorationSupport != null))
+                            || !eq(newScreenDecorationSupport, mHwcScreenDecorationSupport)) {
                         mHwcScreenDecorationSupport = newScreenDecorationSupport;
                         removeAllOverlays();
                         setupDecorations();
                         return;
                     }
-                    updateRoundedCornerDrawable();
+
+                    if (mScreenDecorHwcLayer != null) {
+                        updateHwLayerRoundedCornerDrawable();
+                        updateHwLayerRoundedCornerSize();
+                    }
+
+                    updateOverlayProviderViews();
                 }
                 if (mCutoutViews != null) {
                     final int size = mCutoutViews.length;
@@ -369,7 +424,6 @@
                 if (mScreenDecorHwcLayer != null) {
                     mScreenDecorHwcLayer.onDisplayChanged(displayId);
                 }
-                updateOrientation();
             }
         };
 
@@ -396,6 +450,19 @@
         return null;
     }
 
+    private void removeRedundantOverlayViews(@NonNull List<DecorProvider> decorProviders) {
+        if (mOverlays == null) {
+            return;
+        }
+        int[] viewIds = decorProviders.stream().mapToInt(DecorProvider::getViewId).toArray();
+        for (final OverlayWindow overlay : mOverlays) {
+            if (overlay == null) {
+                continue;
+            }
+            overlay.removeRedundantViews(viewIds);
+        }
+    }
+
     private void removeOverlayView(@IdRes int id) {
         if (mOverlays == null) {
             return;
@@ -411,9 +478,10 @@
     }
 
     private void setupDecorations() {
-        List<DecorProvider> decorProviders = mDotFactory.getProviders();
+        if (hasRoundedCorners() || shouldDrawCutout() || isPrivacyDotEnabled()) {
+            List<DecorProvider> decorProviders = getProviders(mHwcScreenDecorationSupport != null);
+            removeRedundantOverlayViews(decorProviders);
 
-        if (hasRoundedCorners() || shouldDrawCutout() || !decorProviders.isEmpty()) {
             if (mHwcScreenDecorationSupport != null) {
                 createHwcOverlay();
             } else {
@@ -427,7 +495,7 @@
                     Pair<List<DecorProvider>, List<DecorProvider>> pair =
                             DecorProviderKt.partitionAlignedBound(decorProviders, i);
                     decorProviders = pair.getSecond();
-                    createOverlay(i, cutout, pair.getFirst(), isOnlyPrivacyDotInSwLayer);
+                    createOverlay(i, pair.getFirst(), isOnlyPrivacyDotInSwLayer);
                 } else {
                     removeOverlay(i);
                 }
@@ -560,7 +628,6 @@
 
     private void createOverlay(
             @BoundsPosition int pos,
-            @Nullable DisplayCutout cutout,
             @NonNull List<DecorProvider> decorProviders,
             boolean isOnlyPrivacyDotInSwLayer) {
         if (mOverlays == null) {
@@ -568,14 +635,12 @@
         }
 
         if (mOverlays[pos] != null) {
-            // When mOverlay[pos] is not null and only privacy dot in sw layer, use privacy dot
-            // view's visibility
-            mOverlays[pos].getRootView().setVisibility(
-                    getWindowVisibility(mOverlays[pos], isOnlyPrivacyDotInSwLayer));
+            initOverlay(mOverlays[pos], decorProviders, isOnlyPrivacyDotInSwLayer);
             return;
         }
 
-        mOverlays[pos] = overlayForPosition(pos, decorProviders, isOnlyPrivacyDotInSwLayer);
+        mOverlays[pos] = new OverlayWindow(mContext);
+        initOverlay(mOverlays[pos], decorProviders, isOnlyPrivacyDotInSwLayer);
         final ViewGroup overlayView = mOverlays[pos].getRootView();
         overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
         overlayView.setAlpha(0);
@@ -590,7 +655,7 @@
             mCutoutViews[pos] = new DisplayCutoutView(mContext, pos);
             mCutoutViews[pos].setColor(mTintColor);
             overlayView.addView(mCutoutViews[pos]);
-            updateView(pos, cutout);
+            mCutoutViews[pos].updateRotation(mRotation);
         }
 
         mWindowManager.addView(overlayView, getWindowLayoutParams(pos));
@@ -641,42 +706,24 @@
     }
 
     /**
-     * Allow overrides for top/bottom positions
+     * Init OverlayWindow with decorProviders
      */
-    private OverlayWindow overlayForPosition(
-            @BoundsPosition int pos,
+    private void initOverlay(
+            @NonNull OverlayWindow overlay,
             @NonNull List<DecorProvider> decorProviders,
             boolean isOnlyPrivacyDotInSwLayer) {
-        final OverlayWindow currentOverlay = new OverlayWindow(LayoutInflater.from(mContext), pos);
-        decorProviders.forEach(provider -> {
-            removeOverlayView(provider.getViewId());
-            currentOverlay.addDecorProvider(provider, mRotation);
-        });
-        // When only privacy dot in mOverlay, set the initial visibility of mOverlays to
-        // INVISIBLE and set it to VISIBLE when the privacy dot is showing.
-        if (isOnlyPrivacyDotInSwLayer) {
-            currentOverlay.getRootView().setVisibility(View.INVISIBLE);
+        if (!overlay.hasSameProviders(decorProviders)) {
+            decorProviders.forEach(provider -> {
+                if (overlay.getView(provider.getViewId()) != null) {
+                    return;
+                }
+                removeOverlayView(provider.getViewId());
+                overlay.addDecorProvider(provider, mRotation);
+            });
         }
-        return currentOverlay;
-    }
-
-    private void updateView(@BoundsPosition int pos, @Nullable DisplayCutout cutout) {
-        if (mOverlays == null || mOverlays[pos] == null || mHwcScreenDecorationSupport != null) {
-            return;
-        }
-
-        // update rounded corner view rotation
-        updateRoundedCornerView(pos, R.id.left, cutout);
-        updateRoundedCornerView(pos, R.id.right, cutout);
-        updateRoundedCornerSize(
-                mRoundedCornerResDelegate.getTopRoundedSize(),
-                mRoundedCornerResDelegate.getBottomRoundedSize());
-        updateRoundedCornerImageView();
-
-        // update cutout view rotation
-        if (mCutoutViews != null && mCutoutViews[pos] != null) {
-            mCutoutViews[pos].updateRotation(mRotation);
-        }
+        // Use visibility of privacy dot views if only privacy dot in sw layer
+        overlay.getRootView().setVisibility(
+                getWindowVisibility(overlay, isOnlyPrivacyDotInSwLayer));
     }
 
     @VisibleForTesting
@@ -849,7 +896,6 @@
             int oldRotation = mRotation;
             mPendingRotationChange = false;
             updateOrientation();
-            updateRoundedCornerRadii();
             if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
             setupDecorations();
             if (mOverlays != null) {
@@ -910,109 +956,32 @@
             mDotViewController.setNewRotation(newRotation);
         }
 
-        if (mPendingRotationChange) {
-            return;
-        }
-        if (newRotation != mRotation) {
+        if (!mPendingRotationChange && newRotation != mRotation) {
             mRotation = newRotation;
             if (mScreenDecorHwcLayer != null) {
                 mScreenDecorHwcLayer.pendingRotationChange = false;
                 mScreenDecorHwcLayer.updateRotation(mRotation);
+                updateHwLayerRoundedCornerSize();
+                updateHwLayerRoundedCornerDrawable();
             }
-            if (mOverlays != null) {
-                updateLayoutParams();
-                final DisplayCutout cutout = getCutout();
-                for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
-                    if (mOverlays[i] == null) {
+            updateLayoutParams();
+            // update cutout view rotation
+            if (mCutoutViews != null) {
+                for (final DisplayCutoutView cutoutView: mCutoutViews) {
+                    if (cutoutView == null) {
                         continue;
                     }
-                    updateView(i, cutout);
+                    cutoutView.updateRotation(mRotation);
                 }
             }
         }
+
+        // update all provider views inside overlay
+        updateOverlayProviderViews();
     }
 
-    private void updateRoundedCornerRadii() {
-        // We should eventually move to just using the intrinsic size of the drawables since
-        // they should be sized to the exact pixels they want to cover. Therefore I'm purposely not
-        // upgrading all of the configs to contain (width, height) pairs. Instead assume that a
-        // device configured using the single integer config value is okay with drawing the corners
-        // as a square
-        final Size oldRoundedDefaultTop = mRoundedCornerResDelegate.getTopRoundedSize();
-        final Size oldRoundedDefaultBottom = mRoundedCornerResDelegate.getBottomRoundedSize();
-        mRoundedCornerResDelegate.reloadAll(mDisplayUniqueId);
-        final Size newRoundedDefaultTop = mRoundedCornerResDelegate.getTopRoundedSize();
-        final Size newRoundedDefaultBottom = mRoundedCornerResDelegate.getBottomRoundedSize();
-
-        if (oldRoundedDefaultTop.getWidth() != newRoundedDefaultTop.getWidth()
-                || oldRoundedDefaultBottom.getWidth() != newRoundedDefaultBottom.getWidth()) {
-            onTuningChanged(SIZE, null);
-        }
-    }
-
-    private void updateRoundedCornerView(@BoundsPosition int pos, int id,
-            @Nullable DisplayCutout cutout) {
-        final View rounded = mOverlays[pos].getRootView().findViewById(id);
-        if (rounded == null) {
-            return;
-        }
-        rounded.setVisibility(View.GONE);
-        if (shouldShowSwLayerRoundedCorner(pos, cutout)) {
-            final int gravity = getRoundedCornerGravity(pos, id == R.id.left);
-            ((FrameLayout.LayoutParams) rounded.getLayoutParams()).gravity = gravity;
-            setRoundedCornerOrientation(rounded, gravity);
-            rounded.setVisibility(View.VISIBLE);
-        }
-    }
-
-    private int getRoundedCornerGravity(@BoundsPosition int pos, boolean isStart) {
-        final int rotatedPos = getBoundPositionFromRotation(pos, mRotation);
-        switch (rotatedPos) {
-            case BOUNDS_POSITION_LEFT:
-                return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.BOTTOM | Gravity.LEFT;
-            case BOUNDS_POSITION_TOP:
-                return isStart ? Gravity.TOP | Gravity.LEFT : Gravity.TOP | Gravity.RIGHT;
-            case BOUNDS_POSITION_RIGHT:
-                return isStart ? Gravity.TOP | Gravity.RIGHT : Gravity.BOTTOM | Gravity.RIGHT;
-            case BOUNDS_POSITION_BOTTOM:
-                return isStart ? Gravity.BOTTOM | Gravity.LEFT : Gravity.BOTTOM | Gravity.RIGHT;
-            default:
-                throw new IllegalArgumentException("Incorrect position: " + rotatedPos);
-        }
-    }
-
-    /**
-     * Configures the rounded corner drawable's view matrix based on the gravity.
-     *
-     * The gravity describes which corner to configure for, and the drawable we are rotating is
-     * assumed to be oriented for the top-left corner of the device regardless of the target corner.
-     * Therefore we need to rotate 180 degrees to get a bottom-left corner, and mirror in the x- or
-     * y-axis for the top-right and bottom-left corners.
-     */
-    private void setRoundedCornerOrientation(View corner, int gravity) {
-        corner.setRotation(0);
-        corner.setScaleX(1);
-        corner.setScaleY(1);
-        switch (gravity) {
-            case Gravity.TOP | Gravity.LEFT:
-                return;
-            case Gravity.TOP | Gravity.RIGHT:
-                corner.setScaleX(-1); // flip X axis
-                return;
-            case Gravity.BOTTOM | Gravity.LEFT:
-                corner.setScaleY(-1); // flip Y axis
-                return;
-            case Gravity.BOTTOM | Gravity.RIGHT:
-                corner.setRotation(180);
-                return;
-            default:
-                throw new IllegalArgumentException("Unsupported gravity: " + gravity);
-        }
-    }
     private boolean hasRoundedCorners() {
-        return mRoundedCornerResDelegate.getBottomRoundedSize().getWidth() > 0
-                || mRoundedCornerResDelegate.getTopRoundedSize().getWidth() > 0
-                || mRoundedCornerResDelegate.isMultipleRadius();
+        return mRoundedCornerFactory.getHasProviders();
     }
 
     private boolean isDefaultShownOverlayPos(@BoundsPosition int pos,
@@ -1066,6 +1035,19 @@
                 context.getResources(), context.getDisplay().getUniqueId());
     }
 
+    private void updateOverlayProviderViews() {
+        if (mOverlays == null) {
+            return;
+        }
+        ++mProviderRefreshToken;
+        for (final OverlayWindow overlay: mOverlays) {
+            if (overlay == null) {
+                continue;
+            }
+            overlay.onReloadResAndMeasure(null, mProviderRefreshToken, mRotation, mDisplayUniqueId);
+        }
+    }
+
     private void updateLayoutParams() {
         if (mOverlays == null) {
             return;
@@ -1085,63 +1067,33 @@
             return;
         }
         mExecutor.execute(() -> {
-            if (mOverlays == null) return;
-            if (SIZE.equals(key)) {
-                boolean hasReloadRoundedCornerRes = false;
-                if (newValue != null) {
-                    try {
-                        mRoundedCornerResDelegate.updateTuningSizeFactor(
-                                Integer.parseInt(newValue));
-                        hasReloadRoundedCornerRes = true;
-                    } catch (Exception e) {
-                    }
-                }
-
-                // When onTuningChanged() is not called through updateRoundedCornerRadii(),
-                // we need to reload rounded corner res to prevent incorrect dimen
-                if (!hasReloadRoundedCornerRes) {
-                    mRoundedCornerResDelegate.reloadAll(mDisplayUniqueId);
-                }
-
-                updateRoundedCornerSize(
-                        mRoundedCornerResDelegate.getTopRoundedSize(),
-                        mRoundedCornerResDelegate.getBottomRoundedSize());
+            if (mOverlays == null || !SIZE.equals(key)) {
+                return;
             }
+            ++mProviderRefreshToken;
+            try {
+                final int sizeFactor = Integer.parseInt(newValue);
+                mRoundedCornerResDelegate.updateTuningSizeFactor(sizeFactor, mProviderRefreshToken);
+            } catch (NumberFormatException e) {
+                mRoundedCornerResDelegate.updateTuningSizeFactor(null, mProviderRefreshToken);
+            }
+            Integer[] filterIds = {
+                    R.id.rounded_corner_top_left,
+                    R.id.rounded_corner_top_right,
+                    R.id.rounded_corner_bottom_left,
+                    R.id.rounded_corner_bottom_right
+            };
+            for (final OverlayWindow overlay: mOverlays) {
+                if (overlay == null) {
+                    continue;
+                }
+                overlay.onReloadResAndMeasure(filterIds, mProviderRefreshToken, mRotation,
+                        mDisplayUniqueId);
+            }
+            updateHwLayerRoundedCornerSize();
         });
     }
 
-    private void updateRoundedCornerDrawable() {
-        mRoundedCornerResDelegate.reloadAll(mDisplayUniqueId);
-        updateRoundedCornerImageView();
-    }
-
-    private void updateRoundedCornerImageView() {
-        final Drawable top = mRoundedCornerResDelegate.getTopRoundedDrawable();
-        final Drawable bottom = mRoundedCornerResDelegate.getBottomRoundedDrawable();
-
-        if (mScreenDecorHwcLayer != null) {
-            mScreenDecorHwcLayer.updateRoundedCornerDrawable(top, bottom);
-            return;
-        }
-
-        if (mOverlays == null) {
-            return;
-        }
-        final ColorStateList colorStateList = ColorStateList.valueOf(mTintColor);
-        for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
-            if (mOverlays[i] == null) {
-                continue;
-            }
-            final ViewGroup overlayView = mOverlays[i].getRootView();
-            ((ImageView) overlayView.findViewById(R.id.left)).setImageTintList(colorStateList);
-            ((ImageView) overlayView.findViewById(R.id.right)).setImageTintList(colorStateList);
-            ((ImageView) overlayView.findViewById(R.id.left)).setImageDrawable(
-                    isTopRoundedCorner(i, R.id.left) ? top : bottom);
-            ((ImageView) overlayView.findViewById(R.id.right)).setImageDrawable(
-                    isTopRoundedCorner(i, R.id.right) ? top : bottom);
-        }
-    }
-
     private void updateHwLayerRoundedCornerDrawable() {
         if (mScreenDecorHwcLayer == null) {
             return;
@@ -1156,25 +1108,6 @@
         mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable);
     }
 
-    @VisibleForTesting
-    boolean isTopRoundedCorner(@BoundsPosition int pos, int id) {
-        switch (pos) {
-            case BOUNDS_POSITION_LEFT:
-            case BOUNDS_POSITION_RIGHT:
-                if (mRotation == ROTATION_270) {
-                    return id == R.id.left ? false : true;
-                } else {
-                    return id == R.id.left ? true : false;
-                }
-            case BOUNDS_POSITION_TOP:
-                return true;
-            case BOUNDS_POSITION_BOTTOM:
-                return false;
-            default:
-                throw new IllegalArgumentException("Unknown bounds position");
-        }
-    }
-
     private void updateHwLayerRoundedCornerSize() {
         if (mScreenDecorHwcLayer == null) {
             return;
@@ -1186,28 +1119,6 @@
         mScreenDecorHwcLayer.updateRoundedCornerSize(topWidth, bottomWidth);
     }
 
-    private void updateRoundedCornerSize(Size sizeTop, Size sizeBottom) {
-
-        if (mScreenDecorHwcLayer != null) {
-            mScreenDecorHwcLayer.updateRoundedCornerSize(sizeTop.getWidth(), sizeBottom.getWidth());
-            return;
-        }
-
-        if (mOverlays == null) {
-            return;
-        }
-        for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) {
-            if (mOverlays[i] == null) {
-                continue;
-            }
-            final ViewGroup overlayView = mOverlays[i].getRootView();
-            setSize(overlayView.findViewById(R.id.left),
-                    isTopRoundedCorner(i, R.id.left) ? sizeTop : sizeBottom);
-            setSize(overlayView.findViewById(R.id.right),
-                    isTopRoundedCorner(i, R.id.right) ? sizeTop : sizeBottom);
-        }
-    }
-
     @VisibleForTesting
     protected void setSize(View view, Size pixelSize) {
         LayoutParams params = view.getLayoutParams();
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
index 3543bb4..03ee8b1 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt
@@ -15,8 +15,8 @@
  */
 
 package com.android.systemui.decor
+import android.content.Context
 import android.view.DisplayCutout
-import android.view.LayoutInflater
 import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
@@ -38,9 +38,20 @@
     /** The aligned bounds for the view which is created through inflateView() */
     abstract val alignedBounds: List<Int>
 
+    /**
+     * Called when res info changed.
+     * Child provider needs to implement it if its view needs to be updated.
+     */
+    abstract fun onReloadResAndMeasure(
+        view: View,
+        reloadToken: Int,
+        @Surface.Rotation rotation: Int,
+        displayUniqueId: String? = null
+    )
+
     /** Inflate view into parent as current rotation */
     abstract fun inflateView(
-        inflater: LayoutInflater,
+        context: Context,
         parent: ViewGroup,
         @Surface.Rotation rotation: Int
     ): View
diff --git a/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
index c60cad8..cc4096f 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt
@@ -19,4 +19,5 @@
 abstract class DecorProviderFactory {
     abstract val providers: List<DecorProvider>
     abstract val hasProviders: Boolean
+    abstract fun onDisplayUniqueIdChanged(displayUniqueId: String?)
 }
\ 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
index 9f8679c..d775ad3 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt
@@ -16,31 +16,25 @@
 package com.android.systemui.decor
 
 import android.annotation.IdRes
-import android.view.DisplayCutout
-import android.view.LayoutInflater
+import android.content.Context
 import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
-import com.android.systemui.R
-import java.util.HashMap
+import com.android.systemui.RegionInterceptingFrameLayout
 
-class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos: Int) {
+class OverlayWindow(private val context: Context) {
 
-    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 = RegionInterceptingFrameLayout(context) as ViewGroup
+    private val viewProviderMap = mutableMapOf<Int, Pair<View, DecorProvider>>()
 
-    val rootView = layoutInflater.inflate(layoutId, null) as ViewGroup
-    private val viewProviderMap: MutableMap<Int, Pair<View, DecorProvider>> = HashMap()
+    val viewIds: List<Int>
+        get() = viewProviderMap.keys.toList()
 
-    fun addDecorProvider(decorProvider: DecorProvider, @Surface.Rotation rotation: Int) {
-        val view = decorProvider.inflateView(layoutInflater, rootView, rotation)
+    fun addDecorProvider(
+        decorProvider: DecorProvider,
+        @Surface.Rotation rotation: Int
+    ) {
+        val view = decorProvider.inflateView(context, rootView, rotation)
         viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider)
     }
 
@@ -56,4 +50,54 @@
             viewProviderMap.remove(id)
         }
     }
+
+    /**
+     * Remove views which does not been found in expectExistViewIds
+     */
+    fun removeRedundantViews(expectExistViewIds: IntArray?) {
+        viewIds.forEach {
+            if (expectExistViewIds == null || !(expectExistViewIds.contains(it))) {
+                removeView(it)
+            }
+        }
+    }
+
+    /**
+     * Check that newProviders is the same list with viewProviderMap.
+     */
+    fun hasSameProviders(newProviders: List<DecorProvider>): Boolean {
+        return (newProviders.size == viewProviderMap.size) &&
+                newProviders.all { getView(it.viewId) != null }
+    }
+
+    /**
+     * Apply new configuration info into views.
+     * @param filterIds target view ids. Apply to all if null.
+     * @param rotation current or new rotation direction.
+     * @param displayUniqueId new displayUniqueId if any.
+     */
+    fun onReloadResAndMeasure(
+        filterIds: Array<Int>? = null,
+        reloadToken: Int,
+        @Surface.Rotation rotation: Int,
+        displayUniqueId: String? = null
+    ) {
+        filterIds?.forEach { id ->
+            viewProviderMap[id]?.let {
+                it.second.onReloadResAndMeasure(
+                        view = it.first,
+                        reloadToken = reloadToken,
+                        displayUniqueId = displayUniqueId,
+                        rotation = rotation)
+            }
+        } ?: run {
+            viewProviderMap.values.forEach {
+                it.second.onReloadResAndMeasure(
+                        view = it.first,
+                        reloadToken = reloadToken,
+                        displayUniqueId = displayUniqueId,
+                        rotation = rotation)
+            }
+        }
+    }
 }
\ 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
index 7afd7e0e..d16d960 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.decor
 
+import android.content.Context
 import android.content.res.Resources
 import android.view.DisplayCutout
 import android.view.LayoutInflater
@@ -38,6 +39,10 @@
     override val hasProviders: Boolean
         get() = isPrivacyDotEnabled
 
+    override fun onDisplayUniqueIdChanged(displayUniqueId: String?) {
+        // Do nothing for privacy dot
+    }
+
     override val providers: List<DecorProvider>
         get() {
             return if (hasProviders) {
@@ -76,12 +81,21 @@
     private val layoutId: Int
 ) : CornerDecorProvider() {
 
+    override fun onReloadResAndMeasure(
+        view: View,
+        reloadToken: Int,
+        rotation: Int,
+        displayUniqueId: String?
+    ) {
+        // Do nothing here because it is handled inside PrivacyDotViewController
+    }
+
     override fun inflateView(
-        inflater: LayoutInflater,
+        context: Context,
         parent: ViewGroup,
         @Surface.Rotation rotation: Int
     ): View {
-        inflater.inflate(layoutId, parent, true)
+        LayoutInflater.from(context).inflate(layoutId, parent, true)
         return parent.getChildAt(parent.childCount - 1 /* latest new added child */)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt
new file mode 100644
index 0000000..a4f7a58
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.systemui.R
+
+class RoundedCornerDecorProviderFactory(
+    private val roundedCornerResDelegate: RoundedCornerResDelegate
+) : DecorProviderFactory() {
+
+    override val hasProviders: Boolean
+        get() = roundedCornerResDelegate.run {
+            // We don't consider isMultipleRadius here because it makes no sense if size is zero.
+            topRoundedSize.width > 0 || bottomRoundedSize.width > 0
+        }
+
+    override fun onDisplayUniqueIdChanged(displayUniqueId: String?) {
+        roundedCornerResDelegate.updateDisplayUniqueId(displayUniqueId, null)
+    }
+
+    override val providers: List<DecorProvider>
+    get() {
+        val hasTop = roundedCornerResDelegate.topRoundedSize.width > 0
+        val hasBottom = roundedCornerResDelegate.bottomRoundedSize.width > 0
+        return when {
+            hasTop && hasBottom -> listOf(
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_top_left,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_top_right,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_bottom_left,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_bottom_right,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                    roundedCornerResDelegate = roundedCornerResDelegate)
+            )
+            hasTop -> listOf(
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_top_left,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_top_right,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                    roundedCornerResDelegate = roundedCornerResDelegate)
+            )
+            hasBottom -> listOf(
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_bottom_left,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                    roundedCornerResDelegate = roundedCornerResDelegate),
+                RoundedCornerDecorProviderImpl(
+                    viewId = R.id.rounded_corner_bottom_right,
+                    alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                    alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                    roundedCornerResDelegate = roundedCornerResDelegate)
+            )
+            else -> emptyList()
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
new file mode 100644
index 0000000..90ff950
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.decor
+
+import android.content.Context
+import android.view.DisplayCutout
+import android.view.Gravity
+import android.view.Surface
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import android.widget.ImageView
+import com.android.systemui.R
+
+class RoundedCornerDecorProviderImpl(
+    override val viewId: Int,
+    @DisplayCutout.BoundsPosition override val alignedBound1: Int,
+    @DisplayCutout.BoundsPosition override val alignedBound2: Int,
+    private val roundedCornerResDelegate: RoundedCornerResDelegate
+) : CornerDecorProvider() {
+
+    private val isTop = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+
+    override fun inflateView(
+        context: Context,
+        parent: ViewGroup,
+        @Surface.Rotation rotation: Int
+    ): View {
+        return ImageView(context).also { view ->
+            // View
+            view.id = viewId
+            initView(view, rotation)
+
+            // LayoutParams
+            val layoutSize = if (isTop) {
+                roundedCornerResDelegate.topRoundedSize
+            } else {
+                roundedCornerResDelegate.bottomRoundedSize
+            }
+            val params = FrameLayout.LayoutParams(
+                    layoutSize.width,
+                    layoutSize.height,
+                    alignedBound1.toLayoutGravity(rotation) or
+                            alignedBound2.toLayoutGravity(rotation))
+
+            // AddView
+            parent.addView(view, params)
+        }
+    }
+
+    private fun initView(view: ImageView, @Surface.Rotation rotation: Int) {
+        view.setRoundedCornerImage(roundedCornerResDelegate, isTop)
+        view.adjustRotation(alignedBounds, rotation)
+        view.setColorFilter(IMAGE_TINT_COLOR)
+    }
+
+    override fun onReloadResAndMeasure(
+        view: View,
+        reloadToken: Int,
+        @Surface.Rotation rotation: Int,
+        displayUniqueId: String?
+    ) {
+        roundedCornerResDelegate.updateDisplayUniqueId(displayUniqueId, reloadToken)
+
+        initView((view as ImageView), rotation)
+
+        val layoutSize = if (isTop) {
+            roundedCornerResDelegate.topRoundedSize
+        } else {
+            roundedCornerResDelegate.bottomRoundedSize
+        }
+        (view.layoutParams as FrameLayout.LayoutParams).let {
+            it.width = layoutSize.width
+            it.height = layoutSize.height
+            it.gravity = alignedBound1.toLayoutGravity(rotation) or
+                    alignedBound2.toLayoutGravity(rotation)
+            view.setLayoutParams(it)
+        }
+    }
+}
+
+private const val IMAGE_TINT_COLOR: Int = 0xFF000000.toInt()
+
+@DisplayCutout.BoundsPosition
+private fun Int.toLayoutGravity(@Surface.Rotation rotation: Int): Int = when (rotation) {
+    Surface.ROTATION_0 -> when (this) {
+        DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.LEFT
+        DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.TOP
+        DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.RIGHT
+        else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.BOTTOM
+    }
+    Surface.ROTATION_90 -> when (this) {
+        DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.BOTTOM
+        DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.LEFT
+        DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.TOP
+        else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.LEFT
+    }
+    Surface.ROTATION_270 -> when (this) {
+        DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.TOP
+        DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.RIGHT
+        DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.BOTTOM
+        else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.LEFT
+    }
+    else /* Surface.ROTATION_180 */ -> when (this) {
+        DisplayCutout.BOUNDS_POSITION_LEFT -> Gravity.RIGHT
+        DisplayCutout.BOUNDS_POSITION_TOP -> Gravity.BOTTOM
+        DisplayCutout.BOUNDS_POSITION_RIGHT -> Gravity.LEFT
+        else /* DisplayCutout.BOUNDS_POSITION_BOTTOM */ -> Gravity.TOP
+    }
+}
+
+private fun ImageView.setRoundedCornerImage(
+    resDelegate: RoundedCornerResDelegate,
+    isTop: Boolean
+) {
+    val drawable = if (isTop)
+        resDelegate.topRoundedDrawable
+    else
+        resDelegate.bottomRoundedDrawable
+
+    if (drawable != null) {
+        setImageDrawable(drawable)
+    } else {
+        setImageResource(
+                if (isTop)
+                    R.drawable.rounded_corner_top
+                else
+                    R.drawable.rounded_corner_bottom
+        )
+    }
+}
+
+/**
+ * Configures the rounded corner drawable's view matrix based on the gravity.
+ *
+ * The gravity describes which corner to configure for, and the drawable we are rotating is assumed
+ * to be oriented for the top-left corner of the device regardless of the target corner.
+ * Therefore we need to rotate 180 degrees to get a bottom-left corner, and mirror in the x- or
+ * y-axis for the top-right and bottom-left corners.
+ */
+private fun ImageView.adjustRotation(alignedBounds: List<Int>, @Surface.Rotation rotation: Int) {
+    var newRotation = 0F
+    var newScaleX = 1F
+    var newScaleY = 1F
+
+    val isTop = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+    val isLeft = alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_LEFT)
+    when (rotation) {
+        Surface.ROTATION_0 -> when {
+            isTop && isLeft -> {}
+            isTop && !isLeft -> { newScaleX = -1F }
+            !isTop && isLeft -> { newScaleY = -1F }
+            else /* !isTop && !isLeft */ -> { newRotation = 180F }
+        }
+        Surface.ROTATION_90 -> when {
+            isTop && isLeft -> { newScaleY = -1F }
+            isTop && !isLeft -> {}
+            !isTop && isLeft -> { newRotation = 180F }
+            else /* !isTop && !isLeft */ -> { newScaleX = -1F }
+        }
+        Surface.ROTATION_270 -> when {
+            isTop && isLeft -> { newScaleX = -1F }
+            isTop && !isLeft -> { newRotation = 180F }
+            !isTop && isLeft -> {}
+            else /* !isTop && !isLeft */ -> { newScaleY = -1F }
+        }
+        else /* Surface.ROTATION_180 */ -> when {
+            isTop && isLeft -> { newRotation = 180F }
+            isTop && !isLeft -> { newScaleY = -1F }
+            !isTop && isLeft -> { newScaleX = -1F }
+            else /* !isTop && !isLeft */ -> {}
+        }
+    }
+
+    this.rotation = newRotation
+    this.scaleX = newScaleX
+    this.scaleY = newScaleY
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
index ed175ef..c2bab26 100644
--- a/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt
@@ -35,6 +35,8 @@
     private val density: Float
         get() = res.displayMetrics.density
 
+    private var reloadToken: Int = 0
+
     var isMultipleRadius: Boolean = false
         private set
 
@@ -59,12 +61,26 @@
         reloadMeasures()
     }
 
-    fun reloadAll(newDisplayUniqueId: String?) {
-        displayUniqueId = newDisplayUniqueId
+    private fun reloadAll(newReloadToken: Int) {
+        if (reloadToken == newReloadToken) {
+            return
+        }
+        reloadToken = newReloadToken
         reloadRes()
         reloadMeasures()
     }
 
+    fun updateDisplayUniqueId(newDisplayUniqueId: String?, newReloadToken: Int?) {
+        if (displayUniqueId != newDisplayUniqueId) {
+            displayUniqueId = newDisplayUniqueId
+            newReloadToken ?.let { reloadToken = it }
+            reloadRes()
+            reloadMeasures()
+        } else {
+            newReloadToken?.let { reloadAll(it) }
+        }
+    }
+
     private fun reloadRes() {
         val configIdx = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId)
         isMultipleRadius = getIsMultipleRadius(configIdx)
@@ -122,7 +138,11 @@
         }
     }
 
-    fun updateTuningSizeFactor(factor: Int) {
+    fun updateTuningSizeFactor(factor: Int?, newReloadToken: Int) {
+        if (reloadToken == newReloadToken) {
+            return
+        }
+        reloadToken = newReloadToken
         reloadMeasures(factor)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
index 50bd9b0..6bb994f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ScreenDecorationsTest.java
@@ -27,14 +27,15 @@
 
 import static org.hamcrest.Matchers.is;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isA;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -62,9 +63,11 @@
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowMetrics;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.filters.SmallTest;
 
@@ -103,7 +106,7 @@
     private SecureSettings mSecureSettings;
     private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
     private FakeThreadFactory mThreadFactory;
-    private ArrayList<DecorProvider> mDecorProviders;
+    private ArrayList<DecorProvider> mPrivacyDecorProviders;
     @Mock
     private Display mDisplay;
     @Mock
@@ -216,16 +219,43 @@
         }
     }
 
-    private void verifyRoundedCornerViewsVisibility(
+    @NonNull
+    private int[] getRoundCornerIdsFromOverlayId(@DisplayCutout.BoundsPosition int overlayId) {
+        switch (overlayId) {
+            case BOUNDS_POSITION_LEFT:
+                return new int[] {
+                        R.id.rounded_corner_top_left,
+                        R.id.rounded_corner_top_left };
+            case BOUNDS_POSITION_TOP:
+                return new int[] {
+                        R.id.rounded_corner_top_left,
+                        R.id.rounded_corner_top_right };
+            case BOUNDS_POSITION_RIGHT:
+                return new int[] {
+                        R.id.rounded_corner_top_right,
+                        R.id.rounded_corner_bottom_right };
+            case BOUNDS_POSITION_BOTTOM:
+                return new int[] {
+                        R.id.rounded_corner_bottom_left,
+                        R.id.rounded_corner_bottom_right };
+            default:
+                throw new IllegalArgumentException("unknown overlayId: " + overlayId);
+        }
+    }
+
+    private void verifyRoundedCornerViewsExist(
             @DisplayCutout.BoundsPosition final int overlayId,
-            @View.Visibility final int visibility) {
+            @View.Visibility final boolean isExist) {
         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);
-        assertNotNull(right);
-        assertThat(left.getVisibility()).isEqualTo(visibility);
-        assertThat(right.getVisibility()).isEqualTo(visibility);
+        for (int id: getRoundCornerIdsFromOverlayId(overlayId)) {
+            final View view = overlay.findViewById(id);
+            if (isExist) {
+                assertNotNull(view);
+                assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+            } else {
+                assertNull(view);
+            }
+        }
     }
 
     @Nullable
@@ -388,8 +418,8 @@
                 mScreenDecorations.mPrivacyDotShowingListener);
 
         // Rounded corner views shall not exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
 
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -417,8 +447,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Privacy dots shall not exist
         verifyDotViewsNullable(true);
@@ -447,8 +477,8 @@
         verify(mDotViewController, times(1)).setShowingListener(null);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -488,21 +518,26 @@
 
         mScreenDecorations.start();
         View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
-                .findViewById(R.id.left);
+                .findViewById(R.id.rounded_corner_top_left);
         View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
-                .findViewById(R.id.right);
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(leftRoundedCorner, new Size(testTopRadius, testTopRadius));
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(rightRoundedCorner, new Size(testTopRadius, testTopRadius));
+                .findViewById(R.id.rounded_corner_top_right);
+        ViewGroup.LayoutParams leftParams = leftRoundedCorner.getLayoutParams();
+        ViewGroup.LayoutParams rightParams = rightRoundedCorner.getLayoutParams();
+        assertEquals(leftParams.width, testTopRadius);
+        assertEquals(leftParams.height, testTopRadius);
+        assertEquals(rightParams.width, testTopRadius);
+        assertEquals(rightParams.height, testTopRadius);
+
         leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
-                .findViewById(R.id.left);
+                .findViewById(R.id.rounded_corner_bottom_left);
         rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
-                .findViewById(R.id.right);
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(leftRoundedCorner, new Size(testBottomRadius, testBottomRadius));
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(rightRoundedCorner, new Size(testBottomRadius, testBottomRadius));
+                .findViewById(R.id.rounded_corner_bottom_right);
+        leftParams = leftRoundedCorner.getLayoutParams();
+        rightParams = rightRoundedCorner.getLayoutParams();
+        assertEquals(leftParams.width, testBottomRadius);
+        assertEquals(leftParams.height, testBottomRadius);
+        assertEquals(rightParams.width, testBottomRadius);
+        assertEquals(rightParams.height, testBottomRadius);
     }
 
     @Test
@@ -518,31 +553,27 @@
                 .when(mScreenDecorations).getCutout();
 
         mScreenDecorations.start();
-        final Size topRadius = new Size(testTopRadius, testTopRadius);
-        final Size bottomRadius = new Size(testBottomRadius, testBottomRadius);
-        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 topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
+                .findViewById(R.id.rounded_corner_top_left);
+        View bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
+                .findViewById(R.id.rounded_corner_bottom_left);
+        ViewGroup.LayoutParams topParams = topRoundedCorner.getLayoutParams();
+        ViewGroup.LayoutParams bottomParams = bottomRoundedCorner.getLayoutParams();
+        assertEquals(topParams.width, testTopRadius);
+        assertEquals(topParams.height, testTopRadius);
+        assertEquals(bottomParams.width, testBottomRadius);
+        assertEquals(bottomParams.height, testBottomRadius);
 
-        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].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].getRootView()
-                .findViewById(R.id.right);
-        isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.right);
-        verify(mScreenDecorations, atLeastOnce())
-                .setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius);
+        topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
+                .findViewById(R.id.rounded_corner_top_right);
+        bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
+                .findViewById(R.id.rounded_corner_bottom_right);
+        topParams = topRoundedCorner.getLayoutParams();
+        bottomParams = bottomRoundedCorner.getLayoutParams();
+        assertEquals(topParams.width, testTopRadius);
+        assertEquals(topParams.height, testTopRadius);
+        assertEquals(bottomParams.width, testBottomRadius);
+        assertEquals(bottomParams.height, testBottomRadius);
     }
 
     @Test
@@ -562,8 +593,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Privacy dots shall not exist
         verifyDotViewsNullable(true);
@@ -598,8 +629,8 @@
         verify(mDotViewController, times(1)).setShowingListener(null);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -660,10 +691,10 @@
 
         // Top rounded corner views shall exist because of cutout
         // but be gone because of no rounded corner
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
         // Bottom rounded corner views shall exist because of privacy dot
         // but be gone because of no rounded corner
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
 
         // Privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -691,7 +722,7 @@
 
         // Left rounded corner views shall exist because of cutout
         // but be gone because of no rounded corner
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_LEFT, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_LEFT, false);
 
         // Top privacy dots shall not exist because of no privacy
         verifyDotViewsNullable(true);
@@ -745,8 +776,8 @@
         verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Top privacy dots shall not exist because of no privacy dot
         verifyDotViewsNullable(true);
@@ -775,8 +806,8 @@
         verify(mDotViewController, times(1)).setShowingListener(null);
 
         // Rounded corner views shall exist
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
 
         // Top privacy dots shall exist but invisible
         verifyDotViewsVisibility(View.INVISIBLE);
@@ -914,7 +945,7 @@
         verify(mDotViewController, times(2)).setShowingListener(null);
 
         // Verify each privacy dot id appears only once
-        mDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> {
+        mPrivacyDecorProviders.stream().map(DecorProvider::getViewId).forEach(viewId -> {
             int findCount = 0;
             for (OverlayWindow overlay: mScreenDecorations.mOverlays) {
                 if (overlay == null) {
@@ -968,8 +999,8 @@
         // Both top and bottom windows should be added with INVISIBLE because of only privacy dot,
         // but rounded corners visibility shall be gone because of no rounding.
         verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
         verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
         verify(mDotViewController, times(1)).setShowingListener(
                 mScreenDecorations.mPrivacyDotShowingListener);
@@ -982,8 +1013,8 @@
         // Both top and bottom windows should be added with VISIBLE because of privacy dot and
         // cutout, but rounded corners visibility shall be gone because of no rounding.
         verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE);
-        verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
+        verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
         verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
         verify(mDotViewController, times(1)).setShowingListener(null);
     }
@@ -1297,6 +1328,48 @@
         verify(cutoutView, times(1)).onDisplayChanged(1);
     }
 
+    @Test
+    public void testHasSameProvidersWithNullOverlays() {
+        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                0 /* roundedPadding */, false /* multipleRadius */,
+                false /* fillCutout */, false /* privacyDot */);
+
+        mScreenDecorations.start();
+
+        final ArrayList<DecorProvider> newProviders = new ArrayList<>();
+        assertTrue(mScreenDecorations.hasSameProviders(newProviders));
+
+        newProviders.add(mPrivacyDotTopLeftDecorProvider);
+        assertFalse(mScreenDecorations.hasSameProviders(newProviders));
+
+        newProviders.add(mPrivacyDotTopRightDecorProvider);
+        assertFalse(mScreenDecorations.hasSameProviders(newProviders));
+    }
+
+    @Test
+    public void testHasSameProvidersWithPrivacyDots() {
+        setupResources(0 /* radius */, 0 /* radiusTop */, 0 /* radiusBottom */,
+                0 /* roundedPadding */, false /* multipleRadius */,
+                true /* fillCutout */, true /* privacyDot */);
+
+        mScreenDecorations.start();
+
+        final ArrayList<DecorProvider> newProviders = new ArrayList<>();
+        assertFalse(mScreenDecorations.hasSameProviders(newProviders));
+
+        newProviders.add(mPrivacyDotTopLeftDecorProvider);
+        assertFalse(mScreenDecorations.hasSameProviders(newProviders));
+
+        newProviders.add(mPrivacyDotTopRightDecorProvider);
+        assertFalse(mScreenDecorations.hasSameProviders(newProviders));
+
+        newProviders.add(mPrivacyDotBottomLeftDecorProvider);
+        assertFalse(mScreenDecorations.hasSameProviders(newProviders));
+
+        newProviders.add(mPrivacyDotBottomRightDecorProvider);
+        assertTrue(mScreenDecorations.hasSameProviders(newProviders));
+    }
+
     private void setupResources(int radius, int radiusTop, int radiusBottom, int roundedPadding,
             boolean multipleRadius, boolean fillCutout, boolean privacyDot) {
         mContext.getOrCreateTestableResources().addOverride(
@@ -1336,14 +1409,14 @@
         mContext.getOrCreateTestableResources().addOverride(
                 com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout);
 
-        mDecorProviders = new ArrayList<>();
+        mPrivacyDecorProviders = new ArrayList<>();
         if (privacyDot) {
-            mDecorProviders.add(mPrivacyDotTopLeftDecorProvider);
-            mDecorProviders.add(mPrivacyDotTopRightDecorProvider);
-            mDecorProviders.add(mPrivacyDotBottomLeftDecorProvider);
-            mDecorProviders.add(mPrivacyDotBottomRightDecorProvider);
+            mPrivacyDecorProviders.add(mPrivacyDotTopLeftDecorProvider);
+            mPrivacyDecorProviders.add(mPrivacyDotTopRightDecorProvider);
+            mPrivacyDecorProviders.add(mPrivacyDotBottomLeftDecorProvider);
+            mPrivacyDecorProviders.add(mPrivacyDotBottomRightDecorProvider);
         }
-        when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mDecorProviders);
+        when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mPrivacyDecorProviders);
         when(mPrivacyDotDecorProviderFactory.getHasProviders()).thenReturn(privacyDot);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
index ca74df0..69366fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt
@@ -19,25 +19,19 @@
 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.never
 import org.mockito.Mockito.spy
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
@@ -45,62 +39,144 @@
 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 val TEST_DECOR_VIEW_ID_1 = R.id.privacy_dot_top_left_container
+        private val TEST_DECOR_VIEW_ID_2 = R.id.privacy_dot_bottom_left_container
+        private val TEST_DECOR_VIEW_ID_3 = R.id.privacy_dot_bottom_right_container
     }
 
     private lateinit var overlay: OverlayWindow
-
-    @Mock private lateinit var layoutInflater: LayoutInflater
-    @Mock private lateinit var decorProvider: DecorProvider
+    private lateinit var decorProvider1: DecorProvider
+    private lateinit var decorProvider2: DecorProvider
+    private lateinit var decorProvider3: DecorProvider
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
+        decorProvider1 = spy(PrivacyDotCornerDecorProviderImpl(
+                viewId = TEST_DECOR_VIEW_ID_1,
+                alignedBound1 = DisplayCutout.BOUNDS_POSITION_TOP,
+                alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                layoutId = R.layout.privacy_dot_top_left))
+        decorProvider2 = spy(PrivacyDotCornerDecorProviderImpl(
+                viewId = TEST_DECOR_VIEW_ID_2,
+                alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                alignedBound2 = DisplayCutout.BOUNDS_POSITION_LEFT,
+                layoutId = R.layout.privacy_dot_bottom_left))
+        decorProvider3 = spy(PrivacyDotCornerDecorProviderImpl(
+                viewId = TEST_DECOR_VIEW_ID_3,
+                alignedBound1 = DisplayCutout.BOUNDS_POSITION_BOTTOM,
+                alignedBound2 = DisplayCutout.BOUNDS_POSITION_RIGHT,
+                layoutId = R.layout.privacy_dot_bottom_right))
 
-        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<LayoutInflater>(0)
-            val parent = it.getArgument<ViewGroup>(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)
+        overlay = OverlayWindow(mContext)
     }
 
     @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<View>(TEST_DECOR_VIEW_ID)
-        Assert.assertNotNull(viewFoundFromRootView)
-        Assert.assertEquals(viewFoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID))
+        overlay.addDecorProvider(decorProvider1, rotation)
+        overlay.addDecorProvider(decorProvider2, rotation)
+
+        verify(decorProvider1, times(1)).inflateView(
+                mContext, overlay.rootView, rotation)
+        verify(decorProvider2, times(1)).inflateView(
+                mContext, overlay.rootView, rotation)
+
+        val view1FoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1)
+        Assert.assertNotNull(view1FoundFromRootView)
+        Assert.assertEquals(view1FoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID_1))
+        val view2FoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_2)
+        Assert.assertNotNull(view2FoundFromRootView)
+        Assert.assertEquals(view2FoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID_2))
     }
 
     @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<View>(TEST_DECOR_VIEW_ID)
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+        overlay.removeView(TEST_DECOR_VIEW_ID_1)
+
+        val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1)
         Assert.assertNull(viewFoundFromRootView)
-        Assert.assertNull(overlay.getView(TEST_DECOR_LAYOUT_ID))
+        Assert.assertNull(overlay.getView(TEST_DECOR_VIEW_ID_1))
+    }
+
+    @Test
+    fun testOnReloadResAndMeasureWithoutIds() {
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+
+        overlay.onReloadResAndMeasure(
+                reloadToken = 1,
+                rotation = Surface.ROTATION_90,
+                displayUniqueId = null)
+        verify(decorProvider1, times(1)).onReloadResAndMeasure(
+                overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null)
+        verify(decorProvider2, times(1)).onReloadResAndMeasure(
+                overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null)
+    }
+
+    @Test
+    fun testOnReloadResAndMeasureWithIds() {
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+
+        overlay.onReloadResAndMeasure(
+                filterIds = arrayOf(TEST_DECOR_VIEW_ID_2),
+                reloadToken = 1,
+                rotation = Surface.ROTATION_90,
+                displayUniqueId = null)
+        verify(decorProvider1, never()).onReloadResAndMeasure(
+                overlay.getView(TEST_DECOR_VIEW_ID_1)!!, 1, Surface.ROTATION_90, null)
+        verify(decorProvider2, times(1)).onReloadResAndMeasure(
+                overlay.getView(TEST_DECOR_VIEW_ID_2)!!, 1, Surface.ROTATION_90, null)
+    }
+
+    @Test
+    fun testRemoveRedundantViewsWithNullParameter() {
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+
+        overlay.removeRedundantViews(null)
+
+        Assert.assertNull(overlay.getView(TEST_DECOR_VIEW_ID_1))
+        Assert.assertNull(overlay.rootView.findViewById(TEST_DECOR_VIEW_ID_1))
+        Assert.assertNull(overlay.getView(TEST_DECOR_VIEW_ID_2))
+        Assert.assertNull(overlay.rootView.findViewById(TEST_DECOR_VIEW_ID_2))
+    }
+
+    @Test
+    fun testRemoveRedundantViewsWith2Providers() {
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
+
+        overlay.removeRedundantViews(IntArray(2).apply {
+            this[0] = TEST_DECOR_VIEW_ID_3
+            this[1] = TEST_DECOR_VIEW_ID_1
+        })
+
+        Assert.assertNotNull(overlay.getView(TEST_DECOR_VIEW_ID_1))
+        Assert.assertNotNull(overlay.rootView.findViewById(TEST_DECOR_VIEW_ID_1))
+        Assert.assertNull(overlay.getView(TEST_DECOR_VIEW_ID_2))
+        Assert.assertNull(overlay.rootView.findViewById(TEST_DECOR_VIEW_ID_2))
+    }
+
+    @Test
+    fun testHasSameProviders() {
+        Assert.assertTrue(overlay.hasSameProviders(emptyList()))
+        Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider1)))
+        Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2)))
+        Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1)))
+
+        overlay.addDecorProvider(decorProvider1, Surface.ROTATION_0)
+        Assert.assertFalse(overlay.hasSameProviders(emptyList()))
+        Assert.assertTrue(overlay.hasSameProviders(listOf(decorProvider1)))
+        Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2)))
+        Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1)))
+
+        overlay.addDecorProvider(decorProvider2, Surface.ROTATION_0)
+        Assert.assertFalse(overlay.hasSameProviders(emptyList()))
+        Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider1)))
+        Assert.assertFalse(overlay.hasSameProviders(listOf(decorProvider2)))
+        Assert.assertTrue(overlay.hasSameProviders(listOf(decorProvider2, decorProvider1)))
     }
 }
\ 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
index bac0817..171b767 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt
@@ -18,7 +18,6 @@
 
 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
@@ -32,7 +31,6 @@
 import org.mockito.Mockito.`when` as whenever
 
 @RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
 @SmallTest
 class PrivacyDotDecorProviderFactoryTest : SysuiTestCase() {
     private lateinit var mPrivacyDotDecorProviderFactory: PrivacyDotDecorProviderFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
new file mode 100644
index 0000000..621bcf6
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt
@@ -0,0 +1,142 @@
+/*
+ * 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.util.Size
+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
+import org.mockito.Mockito.spy
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class RoundedCornerDecorProviderFactoryTest : SysuiTestCase() {
+
+    @Mock private lateinit var roundedCornerResDelegate: RoundedCornerResDelegate
+    private lateinit var roundedCornerDecorProviderFactory: RoundedCornerDecorProviderFactory
+
+    @Before
+    fun setUp() {
+        roundedCornerResDelegate = spy(RoundedCornerResDelegate(mContext.resources, null))
+    }
+
+    @Test
+    fun testNoRoundedCorners() {
+        Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).topRoundedSize
+        Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).bottomRoundedSize
+        Mockito.doReturn(false).`when`(roundedCornerResDelegate).isMultipleRadius
+
+        roundedCornerDecorProviderFactory =
+                RoundedCornerDecorProviderFactory(roundedCornerResDelegate)
+
+        Assert.assertEquals(false, roundedCornerDecorProviderFactory.hasProviders)
+        Assert.assertEquals(0, roundedCornerDecorProviderFactory.providers.size)
+    }
+
+    @Test
+    fun testHasRoundedCornersIfTopWidthLargerThan0() {
+        Mockito.doReturn(Size(1, 0)).`when`(roundedCornerResDelegate).topRoundedSize
+        Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).bottomRoundedSize
+        Mockito.doReturn(false).`when`(roundedCornerResDelegate).isMultipleRadius
+
+        roundedCornerDecorProviderFactory =
+                RoundedCornerDecorProviderFactory(roundedCornerResDelegate)
+
+        Assert.assertEquals(true, roundedCornerDecorProviderFactory.hasProviders)
+        roundedCornerDecorProviderFactory.providers.let { providers ->
+            Assert.assertEquals(2, providers.size)
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_top_left)
+                        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.rounded_corner_top_right)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_TOP)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
+            })
+        }
+    }
+
+    @Test
+    fun testHasRoundedCornersIfBottomWidthLargerThan0() {
+        Mockito.doReturn(Size(0, 0)).`when`(roundedCornerResDelegate).topRoundedSize
+        Mockito.doReturn(Size(1, 1)).`when`(roundedCornerResDelegate).bottomRoundedSize
+        Mockito.doReturn(false).`when`(roundedCornerResDelegate).isMultipleRadius
+
+        roundedCornerDecorProviderFactory =
+                RoundedCornerDecorProviderFactory(roundedCornerResDelegate)
+
+        Assert.assertEquals(true, roundedCornerDecorProviderFactory.hasProviders)
+        roundedCornerDecorProviderFactory.providers.let { providers ->
+            Assert.assertEquals(2, providers.size)
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_bottom_left)
+                        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.rounded_corner_bottom_right)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
+            })
+        }
+    }
+
+    @Test
+    fun test4CornerDecorProvidersInfo() {
+        Mockito.doReturn(Size(10, 10)).`when`(roundedCornerResDelegate).topRoundedSize
+        Mockito.doReturn(Size(10, 10)).`when`(roundedCornerResDelegate).bottomRoundedSize
+        Mockito.doReturn(true).`when`(roundedCornerResDelegate).isMultipleRadius
+
+        roundedCornerDecorProviderFactory =
+                RoundedCornerDecorProviderFactory(roundedCornerResDelegate)
+
+        Assert.assertEquals(true, roundedCornerDecorProviderFactory.hasProviders)
+        roundedCornerDecorProviderFactory.providers.let { providers ->
+            Assert.assertEquals(4, providers.size)
+            Assert.assertEquals(1, providers.count {
+                ((it.viewId == R.id.rounded_corner_top_left)
+                        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.rounded_corner_top_right)
+                        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.rounded_corner_bottom_left)
+                        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.rounded_corner_bottom_right)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_BOTTOM)
+                        and it.alignedBounds.contains(DisplayCutout.BOUNDS_POSITION_RIGHT))
+            })
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
index 2effaec..1fec380 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt
@@ -45,7 +45,7 @@
     }
 
     @Test
-    fun testReloadAllAndDefaultRadius() {
+    fun testUpdateDisplayUniqueId() {
         mContext.orCreateTestableResources.addOverrides(
                 mockTypeArray = mockTypedArray,
                 radius = 3,
@@ -65,7 +65,34 @@
                 radiusTop = 6,
                 radiusBottom = 0)
 
-        roundedCornerResDelegate.reloadAll("test")
+        roundedCornerResDelegate.updateDisplayUniqueId("test", null)
+
+        assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
+        assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
+    }
+
+    @Test
+    fun testNotUpdateDisplayUniqueIdButChangeRefreshToken() {
+        mContext.orCreateTestableResources.addOverrides(
+                mockTypeArray = mockTypedArray,
+                radius = 3,
+                radiusTop = 0,
+                radiusBottom = 4,
+                multipleRadius = false)
+
+        roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
+
+        assertEquals(Size(3, 3), roundedCornerResDelegate.topRoundedSize)
+        assertEquals(Size(4, 4), roundedCornerResDelegate.bottomRoundedSize)
+        assertEquals(false, roundedCornerResDelegate.isMultipleRadius)
+
+        mContext.orCreateTestableResources.addOverrides(
+                mockTypeArray = mockTypedArray,
+                radius = 5,
+                radiusTop = 6,
+                radiusBottom = 0)
+
+        roundedCornerResDelegate.updateDisplayUniqueId(null, 1)
 
         assertEquals(Size(6, 6), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
@@ -82,11 +109,21 @@
         roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
 
         val factor = 5
-        roundedCornerResDelegate.updateTuningSizeFactor(factor)
+        roundedCornerResDelegate.updateTuningSizeFactor(factor, 1)
         val length = (factor * mContext.resources.displayMetrics.density).toInt()
 
         assertEquals(Size(length, length), roundedCornerResDelegate.topRoundedSize)
         assertEquals(Size(length, length), roundedCornerResDelegate.bottomRoundedSize)
+
+        mContext.orCreateTestableResources.addOverrides(
+                mockTypeArray = mockTypedArray,
+                radiusTop = 1,
+                radiusBottom = 2,
+                multipleRadius = false)
+        roundedCornerResDelegate.updateTuningSizeFactor(null, 2)
+
+        assertEquals(Size(1, 1), roundedCornerResDelegate.topRoundedSize)
+        assertEquals(Size(2, 2), roundedCornerResDelegate.bottomRoundedSize)
     }
 
     @Test