Re-land "Move rounded corner to provider"

Move rounded corners code from ScreenDecorations.
1. Move business logic to RoundedCornerDecorProviderFactory.
2. Move view and inflating into RounedCornerDecorProviderImpl.

Previous landed CL: ag/17154523
Previous reverted CL: ag/17350408

Bug: 218951516
Test: atest RoundedCornerResDelegateTest ScreenDecorationsTest \
      RoundedCornerDecorProviderFactoryTest OverlayWindowTest \
      PrivacyDotDecorProviderFactoryTest
Test: Manually testing rounded corner when display size, rotation
      changed on normal devices, foldable devices, and hardware-overlay
      devices
Change-Id: If5e8da7abb16069fde8a6eb7b03b4cc67de3fde9
This commit is contained in:
Milton Wu
2022-04-07 18:57:54 +08:00
parent e821fd3069
commit 4301034952
16 changed files with 1002 additions and 467 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -170,5 +170,11 @@
<item type="id" name="action_move_bottom_right"/> <item type="id" name="action_move_bottom_right"/>
<item type="id" name="action_move_to_edge_and_hide"/> <item type="id" name="action_move_to_edge_and_hide"/>
<item type="id" name="action_move_out_edge_and_show"/> <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> </resources>

View File

@@ -77,6 +77,7 @@ import com.android.systemui.decor.DecorProviderFactory;
import com.android.systemui.decor.DecorProviderKt; import com.android.systemui.decor.DecorProviderKt;
import com.android.systemui.decor.OverlayWindow; import com.android.systemui.decor.OverlayWindow;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory; import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate; import com.android.systemui.decor.RoundedCornerResDelegate;
import com.android.systemui.qs.SettingObserver; import com.android.systemui.qs.SettingObserver;
import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.UserTracker;
@@ -137,6 +138,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
@VisibleForTesting @VisibleForTesting
protected RoundedCornerResDelegate mRoundedCornerResDelegate; protected RoundedCornerResDelegate mRoundedCornerResDelegate;
@VisibleForTesting @VisibleForTesting
protected DecorProviderFactory mRoundedCornerFactory;
private int mProviderRefreshToken = 0;
@VisibleForTesting
protected OverlayWindow[] mOverlays = null; protected OverlayWindow[] mOverlays = null;
@VisibleForTesting @VisibleForTesting
@Nullable @Nullable
@@ -282,16 +286,58 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
return mDotFactory.getHasProviders(); 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() { private void startOnScreenDecorationsThread() {
mRotation = mContext.getDisplay().getRotation(); mRotation = mContext.getDisplay().getRotation();
mDisplayUniqueId = mContext.getDisplay().getUniqueId(); mDisplayUniqueId = mContext.getDisplay().getUniqueId();
mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(), mRoundedCornerResDelegate = new RoundedCornerResDelegate(mContext.getResources(),
mDisplayUniqueId); mDisplayUniqueId);
mRoundedCornerFactory = new RoundedCornerDecorProviderFactory(mRoundedCornerResDelegate);
mWindowManager = mContext.getSystemService(WindowManager.class); mWindowManager = mContext.getSystemService(WindowManager.class);
mDisplayManager = mContext.getSystemService(DisplayManager.class); mDisplayManager = mContext.getSystemService(DisplayManager.class);
mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport(); mHwcScreenDecorationSupport = mContext.getDisplay().getDisplayDecorationSupport();
updateRoundedCornerDrawable(); updateHwLayerRoundedCornerDrawable();
updateRoundedCornerRadii();
setupDecorations(); setupDecorations();
setupCameraListener(); setupCameraListener();
@@ -343,18 +389,27 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
final String newUniqueId = mContext.getDisplay().getUniqueId(); final String newUniqueId = mContext.getDisplay().getUniqueId();
if (!Objects.equals(newUniqueId, mDisplayUniqueId)) { if (!Objects.equals(newUniqueId, mDisplayUniqueId)) {
mDisplayUniqueId = newUniqueId; mDisplayUniqueId = newUniqueId;
mRoundedCornerResDelegate.reloadAll(newUniqueId);
final DisplayDecorationSupport newScreenDecorationSupport = final DisplayDecorationSupport newScreenDecorationSupport =
mContext.getDisplay().getDisplayDecorationSupport(); mContext.getDisplay().getDisplayDecorationSupport();
// When the value of mSupportHwcScreenDecoration is changed, re-setup the whole
// screen decoration. updateDisplayIdToProviderFactories();
if (!eq(newScreenDecorationSupport, mHwcScreenDecorationSupport)) {
// 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; mHwcScreenDecorationSupport = newScreenDecorationSupport;
removeAllOverlays(); removeAllOverlays();
setupDecorations(); setupDecorations();
return; return;
} }
updateRoundedCornerDrawable();
if (mScreenDecorHwcLayer != null) {
updateHwLayerRoundedCornerDrawable();
updateHwLayerRoundedCornerSize();
}
updateOverlayProviderViews();
} }
if (mCutoutViews != null) { if (mCutoutViews != null) {
final int size = mCutoutViews.length; final int size = mCutoutViews.length;
@@ -369,7 +424,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
if (mScreenDecorHwcLayer != null) { if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.onDisplayChanged(displayId); mScreenDecorHwcLayer.onDisplayChanged(displayId);
} }
updateOrientation();
} }
}; };
@@ -396,6 +450,19 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
return null; 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) { private void removeOverlayView(@IdRes int id) {
if (mOverlays == null) { if (mOverlays == null) {
return; return;
@@ -411,9 +478,10 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
} }
private void setupDecorations() { 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) { if (mHwcScreenDecorationSupport != null) {
createHwcOverlay(); createHwcOverlay();
} else { } else {
@@ -427,7 +495,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
Pair<List<DecorProvider>, List<DecorProvider>> pair = Pair<List<DecorProvider>, List<DecorProvider>> pair =
DecorProviderKt.partitionAlignedBound(decorProviders, i); DecorProviderKt.partitionAlignedBound(decorProviders, i);
decorProviders = pair.getSecond(); decorProviders = pair.getSecond();
createOverlay(i, cutout, pair.getFirst(), isOnlyPrivacyDotInSwLayer); createOverlay(i, pair.getFirst(), isOnlyPrivacyDotInSwLayer);
} else { } else {
removeOverlay(i); removeOverlay(i);
} }
@@ -560,7 +628,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
private void createOverlay( private void createOverlay(
@BoundsPosition int pos, @BoundsPosition int pos,
@Nullable DisplayCutout cutout,
@NonNull List<DecorProvider> decorProviders, @NonNull List<DecorProvider> decorProviders,
boolean isOnlyPrivacyDotInSwLayer) { boolean isOnlyPrivacyDotInSwLayer) {
if (mOverlays == null) { if (mOverlays == null) {
@@ -568,14 +635,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
} }
if (mOverlays[pos] != null) { if (mOverlays[pos] != null) {
// When mOverlay[pos] is not null and only privacy dot in sw layer, use privacy dot initOverlay(mOverlays[pos], decorProviders, isOnlyPrivacyDotInSwLayer);
// view's visibility
mOverlays[pos].getRootView().setVisibility(
getWindowVisibility(mOverlays[pos], isOnlyPrivacyDotInSwLayer));
return; return;
} }
mOverlays[pos] = overlayForPosition(pos, decorProviders, isOnlyPrivacyDotInSwLayer); mOverlays[pos] = new OverlayWindow(mContext);
initOverlay(mOverlays[pos], decorProviders, isOnlyPrivacyDotInSwLayer);
final ViewGroup overlayView = mOverlays[pos].getRootView(); final ViewGroup overlayView = mOverlays[pos].getRootView();
overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); overlayView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
overlayView.setAlpha(0); overlayView.setAlpha(0);
@@ -590,7 +655,7 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
mCutoutViews[pos] = new DisplayCutoutView(mContext, pos); mCutoutViews[pos] = new DisplayCutoutView(mContext, pos);
mCutoutViews[pos].setColor(mTintColor); mCutoutViews[pos].setColor(mTintColor);
overlayView.addView(mCutoutViews[pos]); overlayView.addView(mCutoutViews[pos]);
updateView(pos, cutout); mCutoutViews[pos].updateRotation(mRotation);
} }
mWindowManager.addView(overlayView, getWindowLayoutParams(pos)); mWindowManager.addView(overlayView, getWindowLayoutParams(pos));
@@ -641,42 +706,24 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
} }
/** /**
* Allow overrides for top/bottom positions * Init OverlayWindow with decorProviders
*/ */
private OverlayWindow overlayForPosition( private void initOverlay(
@BoundsPosition int pos, @NonNull OverlayWindow overlay,
@NonNull List<DecorProvider> decorProviders, @NonNull List<DecorProvider> decorProviders,
boolean isOnlyPrivacyDotInSwLayer) { boolean isOnlyPrivacyDotInSwLayer) {
final OverlayWindow currentOverlay = new OverlayWindow(LayoutInflater.from(mContext), pos); if (!overlay.hasSameProviders(decorProviders)) {
decorProviders.forEach(provider -> { decorProviders.forEach(provider -> {
removeOverlayView(provider.getViewId()); if (overlay.getView(provider.getViewId()) != null) {
currentOverlay.addDecorProvider(provider, mRotation); return;
}); }
// When only privacy dot in mOverlay, set the initial visibility of mOverlays to removeOverlayView(provider.getViewId());
// INVISIBLE and set it to VISIBLE when the privacy dot is showing. overlay.addDecorProvider(provider, mRotation);
if (isOnlyPrivacyDotInSwLayer) { });
currentOverlay.getRootView().setVisibility(View.INVISIBLE);
}
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 @VisibleForTesting
@@ -849,7 +896,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
int oldRotation = mRotation; int oldRotation = mRotation;
mPendingRotationChange = false; mPendingRotationChange = false;
updateOrientation(); updateOrientation();
updateRoundedCornerRadii();
if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation); if (DEBUG) Log.i(TAG, "onConfigChanged from rot " + oldRotation + " to " + mRotation);
setupDecorations(); setupDecorations();
if (mOverlays != null) { if (mOverlays != null) {
@@ -910,109 +956,32 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
mDotViewController.setNewRotation(newRotation); mDotViewController.setNewRotation(newRotation);
} }
if (mPendingRotationChange) { if (!mPendingRotationChange && newRotation != mRotation) {
return;
}
if (newRotation != mRotation) {
mRotation = newRotation; mRotation = newRotation;
if (mScreenDecorHwcLayer != null) { if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.pendingRotationChange = false; mScreenDecorHwcLayer.pendingRotationChange = false;
mScreenDecorHwcLayer.updateRotation(mRotation); mScreenDecorHwcLayer.updateRotation(mRotation);
updateHwLayerRoundedCornerSize();
updateHwLayerRoundedCornerDrawable();
} }
if (mOverlays != null) { updateLayoutParams();
updateLayoutParams(); // update cutout view rotation
final DisplayCutout cutout = getCutout(); if (mCutoutViews != null) {
for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { for (final DisplayCutoutView cutoutView: mCutoutViews) {
if (mOverlays[i] == null) { if (cutoutView == null) {
continue; 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() { private boolean hasRoundedCorners() {
return mRoundedCornerResDelegate.getBottomRoundedSize().getWidth() > 0 return mRoundedCornerFactory.getHasProviders();
|| mRoundedCornerResDelegate.getTopRoundedSize().getWidth() > 0
|| mRoundedCornerResDelegate.isMultipleRadius();
} }
private boolean isDefaultShownOverlayPos(@BoundsPosition int pos, private boolean isDefaultShownOverlayPos(@BoundsPosition int pos,
@@ -1066,6 +1035,19 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
context.getResources(), context.getDisplay().getUniqueId()); 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() { private void updateLayoutParams() {
if (mOverlays == null) { if (mOverlays == null) {
return; return;
@@ -1085,63 +1067,33 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
return; return;
} }
mExecutor.execute(() -> { mExecutor.execute(() -> {
if (mOverlays == null) return; if (mOverlays == null || !SIZE.equals(key)) {
if (SIZE.equals(key)) { return;
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());
} }
++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() { private void updateHwLayerRoundedCornerDrawable() {
if (mScreenDecorHwcLayer == null) { if (mScreenDecorHwcLayer == null) {
return; return;
@@ -1156,25 +1108,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
mScreenDecorHwcLayer.updateRoundedCornerDrawable(topDrawable, bottomDrawable); 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() { private void updateHwLayerRoundedCornerSize() {
if (mScreenDecorHwcLayer == null) { if (mScreenDecorHwcLayer == null) {
return; return;
@@ -1186,28 +1119,6 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
mScreenDecorHwcLayer.updateRoundedCornerSize(topWidth, bottomWidth); 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 @VisibleForTesting
protected void setSize(View view, Size pixelSize) { protected void setSize(View view, Size pixelSize) {
LayoutParams params = view.getLayoutParams(); LayoutParams params = view.getLayoutParams();

View File

@@ -15,8 +15,8 @@
*/ */
package com.android.systemui.decor package com.android.systemui.decor
import android.content.Context
import android.view.DisplayCutout import android.view.DisplayCutout
import android.view.LayoutInflater
import android.view.Surface import android.view.Surface
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -38,9 +38,20 @@ abstract class DecorProvider {
/** The aligned bounds for the view which is created through inflateView() */ /** The aligned bounds for the view which is created through inflateView() */
abstract val alignedBounds: List<Int> 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 */ /** Inflate view into parent as current rotation */
abstract fun inflateView( abstract fun inflateView(
inflater: LayoutInflater, context: Context,
parent: ViewGroup, parent: ViewGroup,
@Surface.Rotation rotation: Int @Surface.Rotation rotation: Int
): View ): View

View File

@@ -19,4 +19,5 @@ package com.android.systemui.decor
abstract class DecorProviderFactory { abstract class DecorProviderFactory {
abstract val providers: List<DecorProvider> abstract val providers: List<DecorProvider>
abstract val hasProviders: Boolean abstract val hasProviders: Boolean
abstract fun onDisplayUniqueIdChanged(displayUniqueId: String?)
} }

View File

@@ -16,31 +16,25 @@
package com.android.systemui.decor package com.android.systemui.decor
import android.annotation.IdRes import android.annotation.IdRes
import android.view.DisplayCutout import android.content.Context
import android.view.LayoutInflater
import android.view.Surface import android.view.Surface
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import com.android.systemui.R import com.android.systemui.RegionInterceptingFrameLayout
import java.util.HashMap
class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos: Int) { class OverlayWindow(private val context: Context) {
private val layoutId: Int val rootView = RegionInterceptingFrameLayout(context) as ViewGroup
get() { private val viewProviderMap = mutableMapOf<Int, Pair<View, DecorProvider>>()
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 val viewIds: List<Int>
private val viewProviderMap: MutableMap<Int, Pair<View, DecorProvider>> = HashMap() get() = viewProviderMap.keys.toList()
fun addDecorProvider(decorProvider: DecorProvider, @Surface.Rotation rotation: Int) { fun addDecorProvider(
val view = decorProvider.inflateView(layoutInflater, rootView, rotation) decorProvider: DecorProvider,
@Surface.Rotation rotation: Int
) {
val view = decorProvider.inflateView(context, rootView, rotation)
viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider) viewProviderMap[decorProvider.viewId] = Pair(view, decorProvider)
} }
@@ -56,4 +50,54 @@ class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos:
viewProviderMap.remove(id) 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)
}
}
}
} }

View File

@@ -16,6 +16,7 @@
package com.android.systemui.decor package com.android.systemui.decor
import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.view.DisplayCutout import android.view.DisplayCutout
import android.view.LayoutInflater import android.view.LayoutInflater
@@ -38,6 +39,10 @@ class PrivacyDotDecorProviderFactory @Inject constructor(
override val hasProviders: Boolean override val hasProviders: Boolean
get() = isPrivacyDotEnabled get() = isPrivacyDotEnabled
override fun onDisplayUniqueIdChanged(displayUniqueId: String?) {
// Do nothing for privacy dot
}
override val providers: List<DecorProvider> override val providers: List<DecorProvider>
get() { get() {
return if (hasProviders) { return if (hasProviders) {
@@ -76,12 +81,21 @@ class PrivacyDotCornerDecorProviderImpl(
private val layoutId: Int private val layoutId: Int
) : CornerDecorProvider() { ) : CornerDecorProvider() {
override fun onReloadResAndMeasure(
view: View,
reloadToken: Int,
rotation: Int,
displayUniqueId: String?
) {
// Do nothing here because it is handled inside PrivacyDotViewController
}
override fun inflateView( override fun inflateView(
inflater: LayoutInflater, context: Context,
parent: ViewGroup, parent: ViewGroup,
@Surface.Rotation rotation: Int @Surface.Rotation rotation: Int
): View { ): View {
inflater.inflate(layoutId, parent, true) LayoutInflater.from(context).inflate(layoutId, parent, true)
return parent.getChildAt(parent.childCount - 1 /* latest new added child */) return parent.getChildAt(parent.childCount - 1 /* latest new added child */)
} }
} }

View File

@@ -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()
}
}
}

View File

@@ -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
}

View File

@@ -35,6 +35,8 @@ class RoundedCornerResDelegate(
private val density: Float private val density: Float
get() = res.displayMetrics.density get() = res.displayMetrics.density
private var reloadToken: Int = 0
var isMultipleRadius: Boolean = false var isMultipleRadius: Boolean = false
private set private set
@@ -59,12 +61,26 @@ class RoundedCornerResDelegate(
reloadMeasures() reloadMeasures()
} }
fun reloadAll(newDisplayUniqueId: String?) { private fun reloadAll(newReloadToken: Int) {
displayUniqueId = newDisplayUniqueId if (reloadToken == newReloadToken) {
return
}
reloadToken = newReloadToken
reloadRes() reloadRes()
reloadMeasures() 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() { private fun reloadRes() {
val configIdx = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId) val configIdx = DisplayUtils.getDisplayUniqueIdConfigIndex(res, displayUniqueId)
isMultipleRadius = getIsMultipleRadius(configIdx) isMultipleRadius = getIsMultipleRadius(configIdx)
@@ -122,7 +138,11 @@ class RoundedCornerResDelegate(
} }
} }
fun updateTuningSizeFactor(factor: Int) { fun updateTuningSizeFactor(factor: Int?, newReloadToken: Int) {
if (reloadToken == newReloadToken) {
return
}
reloadToken = newReloadToken
reloadMeasures(factor) reloadMeasures(factor)
} }

View File

@@ -27,14 +27,15 @@ import static com.google.common.truth.Truth.assertThat;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isA; import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
@@ -62,9 +63,11 @@ import android.util.Size;
import android.view.Display; import android.view.Display;
import android.view.DisplayCutout; import android.view.DisplayCutout;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager; import android.view.WindowManager;
import android.view.WindowMetrics; import android.view.WindowMetrics;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
@@ -103,7 +106,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
private SecureSettings mSecureSettings; private SecureSettings mSecureSettings;
private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
private FakeThreadFactory mThreadFactory; private FakeThreadFactory mThreadFactory;
private ArrayList<DecorProvider> mDecorProviders; private ArrayList<DecorProvider> mPrivacyDecorProviders;
@Mock @Mock
private Display mDisplay; private Display mDisplay;
@Mock @Mock
@@ -216,16 +219,43 @@ public class ScreenDecorationsTest extends SysuiTestCase {
} }
} }
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, @DisplayCutout.BoundsPosition final int overlayId,
@View.Visibility final int visibility) { @View.Visibility final boolean isExist) {
final View overlay = mScreenDecorations.mOverlays[overlayId].getRootView(); final View overlay = mScreenDecorations.mOverlays[overlayId].getRootView();
final View left = overlay.findViewById(R.id.left); for (int id: getRoundCornerIdsFromOverlayId(overlayId)) {
final View right = overlay.findViewById(R.id.right); final View view = overlay.findViewById(id);
assertNotNull(left); if (isExist) {
assertNotNull(right); assertNotNull(view);
assertThat(left.getVisibility()).isEqualTo(visibility); assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
assertThat(right.getVisibility()).isEqualTo(visibility); } else {
assertNull(view);
}
}
} }
@Nullable @Nullable
@@ -388,8 +418,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
mScreenDecorations.mPrivacyDotShowingListener); mScreenDecorations.mPrivacyDotShowingListener);
// Rounded corner views shall not exist // Rounded corner views shall not exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
// Privacy dots shall exist but invisible // Privacy dots shall exist but invisible
verifyDotViewsVisibility(View.INVISIBLE); verifyDotViewsVisibility(View.INVISIBLE);
@@ -417,8 +447,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE); verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
// Rounded corner views shall exist // Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
// Privacy dots shall not exist // Privacy dots shall not exist
verifyDotViewsNullable(true); verifyDotViewsNullable(true);
@@ -447,8 +477,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
verify(mDotViewController, times(1)).setShowingListener(null); verify(mDotViewController, times(1)).setShowingListener(null);
// Rounded corner views shall exist // Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
// Privacy dots shall exist but invisible // Privacy dots shall exist but invisible
verifyDotViewsVisibility(View.INVISIBLE); verifyDotViewsVisibility(View.INVISIBLE);
@@ -488,21 +518,26 @@ public class ScreenDecorationsTest extends SysuiTestCase {
mScreenDecorations.start(); mScreenDecorations.start();
View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView() 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() View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_TOP].getRootView()
.findViewById(R.id.right); .findViewById(R.id.rounded_corner_top_right);
verify(mScreenDecorations, atLeastOnce()) ViewGroup.LayoutParams leftParams = leftRoundedCorner.getLayoutParams();
.setSize(leftRoundedCorner, new Size(testTopRadius, testTopRadius)); ViewGroup.LayoutParams rightParams = rightRoundedCorner.getLayoutParams();
verify(mScreenDecorations, atLeastOnce()) assertEquals(leftParams.width, testTopRadius);
.setSize(rightRoundedCorner, new Size(testTopRadius, testTopRadius)); assertEquals(leftParams.height, testTopRadius);
assertEquals(rightParams.width, testTopRadius);
assertEquals(rightParams.height, testTopRadius);
leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView() 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() rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_BOTTOM].getRootView()
.findViewById(R.id.right); .findViewById(R.id.rounded_corner_bottom_right);
verify(mScreenDecorations, atLeastOnce()) leftParams = leftRoundedCorner.getLayoutParams();
.setSize(leftRoundedCorner, new Size(testBottomRadius, testBottomRadius)); rightParams = rightRoundedCorner.getLayoutParams();
verify(mScreenDecorations, atLeastOnce()) assertEquals(leftParams.width, testBottomRadius);
.setSize(rightRoundedCorner, new Size(testBottomRadius, testBottomRadius)); assertEquals(leftParams.height, testBottomRadius);
assertEquals(rightParams.width, testBottomRadius);
assertEquals(rightParams.height, testBottomRadius);
} }
@Test @Test
@@ -518,31 +553,27 @@ public class ScreenDecorationsTest extends SysuiTestCase {
.when(mScreenDecorations).getCutout(); .when(mScreenDecorations).getCutout();
mScreenDecorations.start(); mScreenDecorations.start();
final Size topRadius = new Size(testTopRadius, testTopRadius); View topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
final Size bottomRadius = new Size(testBottomRadius, testBottomRadius); .findViewById(R.id.rounded_corner_top_left);
View leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() View bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView()
.findViewById(R.id.left); .findViewById(R.id.rounded_corner_bottom_left);
boolean isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.left); ViewGroup.LayoutParams topParams = topRoundedCorner.getLayoutParams();
verify(mScreenDecorations, atLeastOnce()) ViewGroup.LayoutParams bottomParams = bottomRoundedCorner.getLayoutParams();
.setSize(leftRoundedCorner, isTop ? topRadius : bottomRadius); assertEquals(topParams.width, testTopRadius);
assertEquals(topParams.height, testTopRadius);
assertEquals(bottomParams.width, testBottomRadius);
assertEquals(bottomParams.height, testBottomRadius);
View rightRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_LEFT].getRootView() topRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
.findViewById(R.id.right); .findViewById(R.id.rounded_corner_top_right);
isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_LEFT, R.id.right); bottomRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView()
verify(mScreenDecorations, atLeastOnce()) .findViewById(R.id.rounded_corner_bottom_right);
.setSize(rightRoundedCorner, isTop ? topRadius : bottomRadius); topParams = topRoundedCorner.getLayoutParams();
bottomParams = bottomRoundedCorner.getLayoutParams();
leftRoundedCorner = mScreenDecorations.mOverlays[BOUNDS_POSITION_RIGHT].getRootView() assertEquals(topParams.width, testTopRadius);
.findViewById(R.id.left); assertEquals(topParams.height, testTopRadius);
isTop = mScreenDecorations.isTopRoundedCorner(BOUNDS_POSITION_RIGHT, R.id.left); assertEquals(bottomParams.width, testBottomRadius);
verify(mScreenDecorations, atLeastOnce()) assertEquals(bottomParams.height, testBottomRadius);
.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);
} }
@Test @Test
@@ -562,8 +593,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE); verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
// Rounded corner views shall exist // Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
// Privacy dots shall not exist // Privacy dots shall not exist
verifyDotViewsNullable(true); verifyDotViewsNullable(true);
@@ -598,8 +629,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
verify(mDotViewController, times(1)).setShowingListener(null); verify(mDotViewController, times(1)).setShowingListener(null);
// Rounded corner views shall exist // Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
// Privacy dots shall exist but invisible // Privacy dots shall exist but invisible
verifyDotViewsVisibility(View.INVISIBLE); verifyDotViewsVisibility(View.INVISIBLE);
@@ -660,10 +691,10 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// Top rounded corner views shall exist because of cutout // Top rounded corner views shall exist because of cutout
// but be gone because of no rounded corner // 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 // Bottom rounded corner views shall exist because of privacy dot
// but be gone because of no rounded corner // 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 // Privacy dots shall exist but invisible
verifyDotViewsVisibility(View.INVISIBLE); verifyDotViewsVisibility(View.INVISIBLE);
@@ -691,7 +722,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// Left rounded corner views shall exist because of cutout // Left rounded corner views shall exist because of cutout
// but be gone because of no rounded corner // 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 // Top privacy dots shall not exist because of no privacy
verifyDotViewsNullable(true); verifyDotViewsNullable(true);
@@ -745,8 +776,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE); verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
// Rounded corner views shall exist // Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
// Top privacy dots shall not exist because of no privacy dot // Top privacy dots shall not exist because of no privacy dot
verifyDotViewsNullable(true); verifyDotViewsNullable(true);
@@ -775,8 +806,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
verify(mDotViewController, times(1)).setShowingListener(null); verify(mDotViewController, times(1)).setShowingListener(null);
// Rounded corner views shall exist // Rounded corner views shall exist
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, true);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.VISIBLE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, true);
// Top privacy dots shall exist but invisible // Top privacy dots shall exist but invisible
verifyDotViewsVisibility(View.INVISIBLE); verifyDotViewsVisibility(View.INVISIBLE);
@@ -914,7 +945,7 @@ public class ScreenDecorationsTest extends SysuiTestCase {
verify(mDotViewController, times(2)).setShowingListener(null); verify(mDotViewController, times(2)).setShowingListener(null);
// Verify each privacy dot id appears only once // 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; int findCount = 0;
for (OverlayWindow overlay: mScreenDecorations.mOverlays) { for (OverlayWindow overlay: mScreenDecorations.mOverlays) {
if (overlay == null) { if (overlay == null) {
@@ -968,8 +999,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// Both top and bottom windows should be added with INVISIBLE because of only privacy dot, // 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. // but rounded corners visibility shall be gone because of no rounding.
verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE); verifyOverlaysExistAndAdded(false, true, false, true, View.INVISIBLE);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
verify(mDotViewController, times(1)).initialize(any(), any(), any(), any()); verify(mDotViewController, times(1)).initialize(any(), any(), any(), any());
verify(mDotViewController, times(1)).setShowingListener( verify(mDotViewController, times(1)).setShowingListener(
mScreenDecorations.mPrivacyDotShowingListener); mScreenDecorations.mPrivacyDotShowingListener);
@@ -982,8 +1013,8 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// Both top and bottom windows should be added with VISIBLE because of privacy dot and // 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. // cutout, but rounded corners visibility shall be gone because of no rounding.
verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE); verifyOverlaysExistAndAdded(false, true, false, true, View.VISIBLE);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_TOP, View.GONE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_TOP, false);
verifyRoundedCornerViewsVisibility(BOUNDS_POSITION_BOTTOM, View.GONE); verifyRoundedCornerViewsExist(BOUNDS_POSITION_BOTTOM, false);
verify(mDotViewController, times(2)).initialize(any(), any(), any(), any()); verify(mDotViewController, times(2)).initialize(any(), any(), any(), any());
verify(mDotViewController, times(1)).setShowingListener(null); verify(mDotViewController, times(1)).setShowingListener(null);
} }
@@ -1297,6 +1328,48 @@ public class ScreenDecorationsTest extends SysuiTestCase {
verify(cutoutView, times(1)).onDisplayChanged(1); 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, private void setupResources(int radius, int radiusTop, int radiusBottom, int roundedPadding,
boolean multipleRadius, boolean fillCutout, boolean privacyDot) { boolean multipleRadius, boolean fillCutout, boolean privacyDot) {
mContext.getOrCreateTestableResources().addOverride( mContext.getOrCreateTestableResources().addOverride(
@@ -1336,14 +1409,14 @@ public class ScreenDecorationsTest extends SysuiTestCase {
mContext.getOrCreateTestableResources().addOverride( mContext.getOrCreateTestableResources().addOverride(
com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout); com.android.internal.R.bool.config_fillMainBuiltInDisplayCutout, fillCutout);
mDecorProviders = new ArrayList<>(); mPrivacyDecorProviders = new ArrayList<>();
if (privacyDot) { if (privacyDot) {
mDecorProviders.add(mPrivacyDotTopLeftDecorProvider); mPrivacyDecorProviders.add(mPrivacyDotTopLeftDecorProvider);
mDecorProviders.add(mPrivacyDotTopRightDecorProvider); mPrivacyDecorProviders.add(mPrivacyDotTopRightDecorProvider);
mDecorProviders.add(mPrivacyDotBottomLeftDecorProvider); mPrivacyDecorProviders.add(mPrivacyDotBottomLeftDecorProvider);
mDecorProviders.add(mPrivacyDotBottomRightDecorProvider); mPrivacyDecorProviders.add(mPrivacyDotBottomRightDecorProvider);
} }
when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mDecorProviders); when(mPrivacyDotDecorProviderFactory.getProviders()).thenReturn(mPrivacyDecorProviders);
when(mPrivacyDotDecorProviderFactory.getHasProviders()).thenReturn(privacyDot); when(mPrivacyDotDecorProviderFactory.getHasProviders()).thenReturn(privacyDot);
} }

View File

@@ -19,25 +19,19 @@ package com.android.systemui.decor
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper import android.testing.TestableLooper.RunWithLooper
import android.view.DisplayCutout import android.view.DisplayCutout
import android.view.LayoutInflater
import android.view.Surface import android.view.Surface
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.R import com.android.systemui.R
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mockito.never
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.spy import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true) @RunWithLooper(setAsMainLooper = true)
@@ -45,62 +39,144 @@ import org.mockito.Mockito.`when` as whenever
class OverlayWindowTest : SysuiTestCase() { class OverlayWindowTest : SysuiTestCase() {
companion object { companion object {
private val TEST_DECOR_VIEW_ID = R.id.privacy_dot_bottom_right_container private val TEST_DECOR_VIEW_ID_1 = R.id.privacy_dot_top_left_container
private val TEST_DECOR_LAYOUT_ID = R.layout.privacy_dot_bottom_right 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 private lateinit var overlay: OverlayWindow
private lateinit var decorProvider1: DecorProvider
@Mock private lateinit var layoutInflater: LayoutInflater private lateinit var decorProvider2: DecorProvider
@Mock private lateinit var decorProvider: DecorProvider private lateinit var decorProvider3: DecorProvider
@Before @Before
fun setUp() { 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(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)
} }
@Test @Test
fun testAddProvider() { fun testAddProvider() {
@Surface.Rotation val rotation = Surface.ROTATION_270 @Surface.Rotation val rotation = Surface.ROTATION_270
overlay.addDecorProvider(decorProvider, rotation) overlay.addDecorProvider(decorProvider1, rotation)
verify(decorProvider, Mockito.times(1)).inflateView( overlay.addDecorProvider(decorProvider2, rotation)
eq(layoutInflater), eq(overlay.rootView), eq(rotation))
val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID) verify(decorProvider1, times(1)).inflateView(
Assert.assertNotNull(viewFoundFromRootView) mContext, overlay.rootView, rotation)
Assert.assertEquals(viewFoundFromRootView, overlay.getView(TEST_DECOR_VIEW_ID)) 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 @Test
fun testRemoveView() { fun testRemoveView() {
@Surface.Rotation val rotation = Surface.ROTATION_270 overlay.addDecorProvider(decorProvider1, Surface.ROTATION_270)
overlay.addDecorProvider(decorProvider, rotation) overlay.addDecorProvider(decorProvider2, Surface.ROTATION_270)
overlay.removeView(TEST_DECOR_VIEW_ID) overlay.removeView(TEST_DECOR_VIEW_ID_1)
val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID)
val viewFoundFromRootView = overlay.rootView.findViewById<View>(TEST_DECOR_VIEW_ID_1)
Assert.assertNull(viewFoundFromRootView) 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)))
} }
} }

View File

@@ -18,7 +18,6 @@ package com.android.systemui.decor
import android.content.res.Resources import android.content.res.Resources
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.DisplayCutout import android.view.DisplayCutout
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.R import com.android.systemui.R
@@ -32,7 +31,6 @@ import org.mockito.Mockito.spy
import org.mockito.Mockito.`when` as whenever import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest @SmallTest
class PrivacyDotDecorProviderFactoryTest : SysuiTestCase() { class PrivacyDotDecorProviderFactoryTest : SysuiTestCase() {
private lateinit var mPrivacyDotDecorProviderFactory: PrivacyDotDecorProviderFactory private lateinit var mPrivacyDotDecorProviderFactory: PrivacyDotDecorProviderFactory

View File

@@ -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))
})
}
}
}

View File

@@ -45,7 +45,7 @@ class RoundedCornerResDelegateTest : SysuiTestCase() {
} }
@Test @Test
fun testReloadAllAndDefaultRadius() { fun testUpdateDisplayUniqueId() {
mContext.orCreateTestableResources.addOverrides( mContext.orCreateTestableResources.addOverrides(
mockTypeArray = mockTypedArray, mockTypeArray = mockTypedArray,
radius = 3, radius = 3,
@@ -65,7 +65,34 @@ class RoundedCornerResDelegateTest : SysuiTestCase() {
radiusTop = 6, radiusTop = 6,
radiusBottom = 0) 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(6, 6), roundedCornerResDelegate.topRoundedSize)
assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize) assertEquals(Size(5, 5), roundedCornerResDelegate.bottomRoundedSize)
@@ -82,11 +109,21 @@ class RoundedCornerResDelegateTest : SysuiTestCase() {
roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null) roundedCornerResDelegate = RoundedCornerResDelegate(mContext.resources, null)
val factor = 5 val factor = 5
roundedCornerResDelegate.updateTuningSizeFactor(factor) roundedCornerResDelegate.updateTuningSizeFactor(factor, 1)
val length = (factor * mContext.resources.displayMetrics.density).toInt() val length = (factor * mContext.resources.displayMetrics.density).toInt()
assertEquals(Size(length, length), roundedCornerResDelegate.topRoundedSize) assertEquals(Size(length, length), roundedCornerResDelegate.topRoundedSize)
assertEquals(Size(length, length), roundedCornerResDelegate.bottomRoundedSize) 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 @Test