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

View File

@@ -77,6 +77,7 @@ import com.android.systemui.decor.DecorProviderFactory;
import com.android.systemui.decor.DecorProviderKt;
import com.android.systemui.decor.OverlayWindow;
import com.android.systemui.decor.PrivacyDotDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerDecorProviderFactory;
import com.android.systemui.decor.RoundedCornerResDelegate;
import com.android.systemui.qs.SettingObserver;
import com.android.systemui.settings.UserTracker;
@@ -137,6 +138,9 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
@VisibleForTesting
protected RoundedCornerResDelegate mRoundedCornerResDelegate;
@VisibleForTesting
protected DecorProviderFactory mRoundedCornerFactory;
private int mProviderRefreshToken = 0;
@VisibleForTesting
protected OverlayWindow[] mOverlays = null;
@VisibleForTesting
@Nullable
@@ -282,16 +286,58 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
if (mScreenDecorHwcLayer != null) {
mScreenDecorHwcLayer.onDisplayChanged(displayId);
}
updateOrientation();
}
};
@@ -396,6 +450,19 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
private void createOverlay(
@BoundsPosition int pos,
@Nullable DisplayCutout cutout,
@NonNull List<DecorProvider> decorProviders,
boolean isOnlyPrivacyDotInSwLayer) {
if (mOverlays == null) {
@@ -568,14 +635,12 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
}
/**
* 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);
}
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);
if (!overlay.hasSameProviders(decorProviders)) {
decorProviders.forEach(provider -> {
if (overlay.getView(provider.getViewId()) != null) {
return;
}
removeOverlayView(provider.getViewId());
overlay.addDecorProvider(provider, 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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab
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();

View File

@@ -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 @@ abstract class DecorProvider {
/** 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

View File

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

View File

@@ -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 @@ class OverlayWindow(private val layoutInflater: LayoutInflater, private val pos:
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
import android.content.Context
import android.content.res.Resources
import android.view.DisplayCutout
import android.view.LayoutInflater
@@ -38,6 +39,10 @@ class PrivacyDotDecorProviderFactory @Inject constructor(
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 @@ class PrivacyDotCornerDecorProviderImpl(
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 */)
}
}

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
get() = res.displayMetrics.density
private var reloadToken: Int = 0
var isMultipleRadius: Boolean = false
private set
@@ -59,12 +61,26 @@ class RoundedCornerResDelegate(
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 @@ class RoundedCornerResDelegate(
}
}
fun updateTuningSizeFactor(factor: Int) {
fun updateTuningSizeFactor(factor: Int?, newReloadToken: Int) {
if (reloadToken == newReloadToken) {
return
}
reloadToken = newReloadToken
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.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.util.Size;
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ 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,
@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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
.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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
// 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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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 @@ public class ScreenDecorationsTest extends SysuiTestCase {
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);
}

View File

@@ -19,25 +19,19 @@ package com.android.systemui.decor
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.DisplayCutout
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.eq
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.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 @@ import org.mockito.Mockito.`when` as whenever
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)))
}
}

View File

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

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
fun testReloadAllAndDefaultRadius() {
fun testUpdateDisplayUniqueId() {
mContext.orCreateTestableResources.addOverrides(
mockTypeArray = mockTypedArray,
radius = 3,
@@ -65,7 +65,34 @@ class RoundedCornerResDelegateTest : SysuiTestCase() {
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 @@ class RoundedCornerResDelegateTest : SysuiTestCase() {
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