Cache rounded corner masks.
Also reduce the creation of Paints and Canvases.
PiperOrigin-RevId: 239806821
Change-Id: Idbe7fc45a188ce657142d03cd6ab1a95ca9c067c
diff --git a/src/main/java/com/google/android/libraries/feed/piet/AdapterParameters.java b/src/main/java/com/google/android/libraries/feed/piet/AdapterParameters.java
index 94423cb..7f2174b 100644
--- a/src/main/java/com/google/android/libraries/feed/piet/AdapterParameters.java
+++ b/src/main/java/com/google/android/libraries/feed/piet/AdapterParameters.java
@@ -21,6 +21,7 @@
import com.google.android.libraries.feed.common.functional.Supplier;
import com.google.android.libraries.feed.common.time.Clock;
import com.google.android.libraries.feed.piet.PietStylesHelper.PietStylesHelperFactory;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache;
/**
* A state shared by instances of Cards and Slices. The state is accessed directly from the instance
@@ -41,8 +42,8 @@
final TemplateBinder templateBinder;
final StyleProvider defaultStyleProvider;
final Clock clock;
-
final PietStylesHelperFactory pietStylesHelperFactory;
+ final RoundedCornerMaskCache roundedCornerMaskCache;
// Doesn't like passing "this" to the new ElementAdapterFactory; however, nothing in the factory's
// construction will reference the elementAdapterFactory member of this, so should be safe.
@@ -67,6 +68,7 @@
this.defaultStyleProvider = new StyleProvider(hostProviders.getAssetProvider());
this.pietStylesHelperFactory = new PietStylesHelperFactory();
+ this.roundedCornerMaskCache = new RoundedCornerMaskCache();
}
/** Testing-only constructor for mocking the internally-constructed objects. */
@@ -87,7 +89,8 @@
elementAdapterFactory,
templateBinder,
clock,
- new PietStylesHelperFactory());
+ new PietStylesHelperFactory(),
+ new RoundedCornerMaskCache());
}
/** Testing-only constructor for mocking the internally-constructed objects. */
@@ -100,7 +103,8 @@
ElementAdapterFactory elementAdapterFactory,
TemplateBinder templateBinder,
Clock clock,
- PietStylesHelperFactory pietStylesHelperFactory) {
+ PietStylesHelperFactory pietStylesHelperFactory,
+ RoundedCornerMaskCache maskCache) {
this.context = context;
this.parentViewSupplier = parentViewSupplier;
this.hostProviders = hostProviders;
@@ -111,5 +115,6 @@
this.defaultStyleProvider = new StyleProvider(hostProviders.getAssetProvider());
this.clock = clock;
this.pietStylesHelperFactory = pietStylesHelperFactory;
+ this.roundedCornerMaskCache = maskCache;
}
}
diff --git a/src/main/java/com/google/android/libraries/feed/piet/ElementAdapter.java b/src/main/java/com/google/android/libraries/feed/piet/ElementAdapter.java
index 2fcfb54..2bd4c6a 100644
--- a/src/main/java/com/google/android/libraries/feed/piet/ElementAdapter.java
+++ b/src/main/java/com/google/android/libraries/feed/piet/ElementAdapter.java
@@ -173,7 +173,8 @@
return;
}
- FrameLayout wrapper = elementStyle.createWrapperView(context);
+ FrameLayout wrapper =
+ elementStyle.createWrapperView(context, parameters.roundedCornerMaskCache);
wrapper.addView(getBaseView());
if (baseElement.getOverlaysCount() > 0) {
diff --git a/src/main/java/com/google/android/libraries/feed/piet/PietManagerImpl.java b/src/main/java/com/google/android/libraries/feed/piet/PietManagerImpl.java
index e597a0a..4f20fe2 100644
--- a/src/main/java/com/google/android/libraries/feed/piet/PietManagerImpl.java
+++ b/src/main/java/com/google/android/libraries/feed/piet/PietManagerImpl.java
@@ -84,6 +84,7 @@
AdapterParameters adapterParametersNonNull = adapterParameters;
adapterParametersNonNull.elementAdapterFactory.purgeRecyclerPools();
adapterParametersNonNull.pietStylesHelperFactory.purge();
+ adapterParametersNonNull.roundedCornerMaskCache.purge();
}
}
}
diff --git a/src/main/java/com/google/android/libraries/feed/piet/StyleProvider.java b/src/main/java/com/google/android/libraries/feed/piet/StyleProvider.java
index 798260b..c060e4c 100644
--- a/src/main/java/com/google/android/libraries/feed/piet/StyleProvider.java
+++ b/src/main/java/com/google/android/libraries/feed/piet/StyleProvider.java
@@ -30,6 +30,7 @@
import com.google.android.libraries.feed.piet.host.AssetProvider;
import com.google.android.libraries.feed.piet.ui.BorderDrawable;
import com.google.android.libraries.feed.piet.ui.GradientDrawable;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache;
import com.google.android.libraries.feed.piet.ui.RoundedCornerViewHelper;
import com.google.android.libraries.feed.piet.ui.RoundedCornerWrapperView;
import com.google.search.now.ui.piet.ErrorsProto.ErrorCode;
@@ -407,14 +408,19 @@
}
}
- FrameLayout createWrapperView(Context context) {
+ FrameLayout createWrapperView(Context context, RoundedCornerMaskCache maskCache) {
if (!hasRoundedCorners()) {
return new FrameLayout(context);
}
int radiusOverride =
getRoundedCorners().getUseHostRadiusOverride() ? assetProvider.getDefaultCornerRadius() : 0;
return new RoundedCornerWrapperView(
- context, getRoundedCorners(), assetProvider.isRtLSupplier(), radiusOverride, getBorders());
+ context,
+ getRoundedCorners(),
+ maskCache,
+ assetProvider.isRtLSupplier(),
+ radiusOverride,
+ getBorders());
}
/**
diff --git a/src/main/java/com/google/android/libraries/feed/piet/ui/BorderDrawable.java b/src/main/java/com/google/android/libraries/feed/piet/ui/BorderDrawable.java
index e485c58..2b518cc 100644
--- a/src/main/java/com/google/android/libraries/feed/piet/ui/BorderDrawable.java
+++ b/src/main/java/com/google/android/libraries/feed/piet/ui/BorderDrawable.java
@@ -99,7 +99,6 @@
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(
-
left + offsetToHideLeft,
top + offsetToHideTop,
right + offsetToHideRight,
diff --git a/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerMaskCache.java b/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerMaskCache.java
new file mode 100644
index 0000000..3beb3a6
--- /dev/null
+++ b/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerMaskCache.java
@@ -0,0 +1,138 @@
+// Copyright 2018 The Feed Authors.
+//
+// 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.google.android.libraries.feed.piet.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuffXfermode;
+import android.support.annotation.IntDef;
+import android.support.annotation.VisibleForTesting;
+import android.util.LruCache;
+
+/** Caches rounded corner masks to save memory and time spent creating them */
+public class RoundedCornerMaskCache {
+ // TODO: Make cache size configurable.
+ private static final int CACHE_SIZE = 8;
+
+ @IntDef({Corner.TOP_LEFT, Corner.TOP_RIGHT, Corner.BOTTOM_LEFT, Corner.BOTTOM_RIGHT})
+ @interface Corner {
+ int TOP_LEFT = 0;
+ int TOP_RIGHT = 1;
+ int BOTTOM_LEFT = 2;
+ int BOTTOM_RIGHT = 3;
+ }
+
+ private final Canvas canvas;
+ private final Paint paint;
+ private final Paint maskPaint;
+ private final LruCache<Integer, RoundedCornerBitmaps> cache;
+
+ public RoundedCornerMaskCache() {
+ paint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
+ maskPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
+
+ canvas = new Canvas();
+ cache =
+ new LruCache<Integer, RoundedCornerBitmaps>(CACHE_SIZE) {
+ @Override
+ protected RoundedCornerBitmaps create(Integer key) {
+ return new RoundedCornerBitmaps(key, canvas, maskPaint);
+ }
+ };
+ }
+
+ Paint getPaint() {
+ return paint;
+ }
+
+ Paint getMaskPaint() {
+ return maskPaint;
+ }
+
+ RoundedCornerBitmaps getMasks(int radius) {
+ RoundedCornerBitmaps masks = cache.get(radius);
+ if (masks == null) {
+ masks = new RoundedCornerBitmaps(radius, canvas, maskPaint);
+ cache.put(radius, masks);
+ }
+ return masks;
+ }
+
+ public void purge() {
+ cache.evictAll();
+ }
+
+ static class RoundedCornerBitmaps {
+ @VisibleForTesting final Bitmap[] masks;
+ private final Canvas canvas;
+ private final int radius;
+ private final Paint maskPaint;
+
+ RoundedCornerBitmaps(int radius, Canvas canvas, Paint maskPaint) {
+ masks = new Bitmap[4];
+ this.canvas = canvas;
+ this.radius = radius;
+ this.maskPaint = maskPaint;
+ }
+
+ Bitmap get(@Corner int corner) {
+ switch (corner) {
+ case Corner.TOP_LEFT:
+ if (masks[Corner.TOP_LEFT] == null) {
+ masks[Corner.TOP_LEFT] = Bitmap.createBitmap(radius, radius, Config.ALPHA_8);
+ canvas.setBitmap(masks[Corner.TOP_LEFT]);
+ canvas.drawColor(Color.BLACK);
+ canvas.drawCircle(radius, radius, radius, maskPaint);
+ }
+ return masks[Corner.TOP_LEFT];
+
+ case Corner.TOP_RIGHT:
+ if (masks[Corner.TOP_RIGHT] == null) {
+ masks[Corner.TOP_RIGHT] = Bitmap.createBitmap(radius, radius, Config.ALPHA_8);
+ canvas.setBitmap(masks[Corner.TOP_RIGHT]);
+ canvas.drawColor(Color.BLACK);
+ canvas.drawCircle(0, radius, radius, maskPaint);
+ }
+ return masks[Corner.TOP_RIGHT];
+
+ case Corner.BOTTOM_LEFT:
+ if (masks[Corner.BOTTOM_LEFT] == null) {
+ masks[Corner.BOTTOM_LEFT] = Bitmap.createBitmap(radius, radius, Config.ALPHA_8);
+ canvas.setBitmap(masks[Corner.BOTTOM_LEFT]);
+ canvas.drawColor(Color.BLACK);
+ canvas.drawCircle(radius, 0, radius, maskPaint);
+ }
+ return masks[Corner.BOTTOM_LEFT];
+
+ case Corner.BOTTOM_RIGHT:
+ if (masks[Corner.BOTTOM_RIGHT] == null) {
+ masks[Corner.BOTTOM_RIGHT] = Bitmap.createBitmap(radius, radius, Config.ALPHA_8);
+ canvas.setBitmap(masks[Corner.BOTTOM_RIGHT]);
+ canvas.drawColor(Color.BLACK);
+ canvas.drawCircle(0, 0, radius, maskPaint);
+ }
+ return masks[Corner.BOTTOM_RIGHT];
+
+ default:
+ throw new IllegalArgumentException(String.format("Unrecognized corner: %s", corner));
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerViewHelper.java b/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerViewHelper.java
index dac148e..2cdade3 100644
--- a/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerViewHelper.java
+++ b/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerViewHelper.java
@@ -98,4 +98,7 @@
|| (roundedCorners.hasRadius() && (roundedCorners.getRadius() > 0))
|| (radiusOverride > 0));
}
+
+ // Prevent instantiation
+ private RoundedCornerViewHelper() {}
}
diff --git a/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerWrapperView.java b/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerWrapperView.java
index 850b252..94097de 100644
--- a/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerWrapperView.java
+++ b/src/main/java/com/google/android/libraries/feed/piet/ui/RoundedCornerWrapperView.java
@@ -14,15 +14,14 @@
package com.google.android.libraries.feed.piet.ui;
+import static com.google.android.libraries.feed.common.Validators.checkNotNull;
+
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.drawable.shapes.RoundRectShape;
import android.os.Build;
@@ -32,6 +31,8 @@
import android.widget.FrameLayout;
import com.google.android.libraries.feed.common.functional.Supplier;
import com.google.android.libraries.feed.common.ui.LayoutUtils;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache.Corner;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache.RoundedCornerBitmaps;
import com.google.search.now.ui.piet.RoundedCornersProto.RoundedCorners;
import com.google.search.now.ui.piet.RoundedCornersProto.RoundedCorners.Corners;
import com.google.search.now.ui.piet.RoundedCornersProto.RoundedCorners.RadiusOptionsCase;
@@ -47,7 +48,10 @@
private final Supplier<Boolean> isRtLSupplier;
private final Context context;
private final Borders borders;
+ private final RoundedCornerMaskCache maskCache;
+ private final Canvas offscreenCanvas;
+ /*@Nullable*/ private Bitmap offscreenBitmap = null;
/*@Nullable*/ private RoundRectShape outlineShape = null;
private int roundedCornerRadius;
@@ -59,8 +63,8 @@
// Keep track of current mask configuration so we can use cached values if nothing has changed.
private int lastRadius = -1;
- private int lastWidth = 0;
- private int lastHeight = 0;
+ private int lastWidth = -1;
+ private int lastHeight = -1;
private boolean lastRtL;
// Doesn't like the call to setOutlineProvider
@@ -68,21 +72,22 @@
public RoundedCornerWrapperView(
Context context,
RoundedCorners roundedCorners,
+ RoundedCornerMaskCache maskCache,
Supplier<Boolean> isRtLSupplier,
int radiusOverride,
Borders borders) {
super(context);
+ this.maskCache = maskCache;
this.isRtLSupplier = isRtLSupplier;
this.roundedCorners = roundedCorners;
this.radiusOverride = radiusOverride;
this.context = context;
this.borders = borders;
+ offscreenCanvas = new Canvas();
lastRtL = !isRtLSupplier.get(); // Flip this so we must update the layout on the first time.
- paint = new Paint(Paint.ANTI_ALIAS_FLAG);
-
- maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
- maskPaint.setXfermode(new PorterDuffXfermode(Mode.CLEAR));
+ this.paint = maskCache.getPaint();
+ this.maskPaint = maskCache.getMaskPaint();
if (hasRoundedCorners() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
super.setOutlineProvider(
@@ -109,6 +114,38 @@
setWillNotDraw(false);
}
+ private void initCornerMasks(int radius, boolean isRtL) {
+ RoundedCornerBitmaps masks = maskCache.getMasks(radius);
+
+ if ((shouldRoundCorner(Corners.TOP_START) && !isRtL)
+ || (shouldRoundCorner(Corners.TOP_END) && isRtL)) {
+ cornerTL = masks.get(Corner.TOP_LEFT);
+ } else {
+ cornerTL = null;
+ }
+
+ if ((shouldRoundCorner(Corners.TOP_END) && !isRtL)
+ || (shouldRoundCorner(Corners.TOP_START) && isRtL)) {
+ cornerTR = masks.get(Corner.TOP_RIGHT);
+ } else {
+ cornerTR = null;
+ }
+
+ if ((shouldRoundCorner(Corners.BOTTOM_START) && !isRtL)
+ || (shouldRoundCorner(Corners.BOTTOM_END) && isRtL)) {
+ cornerBL = masks.get(Corner.BOTTOM_LEFT);
+ } else {
+ cornerBL = null;
+ }
+
+ if ((shouldRoundCorner(Corners.BOTTOM_END) && !isRtL)
+ || (shouldRoundCorner(Corners.BOTTOM_START) && isRtL)) {
+ cornerBR = masks.get(Corner.BOTTOM_RIGHT);
+ } else {
+ cornerBR = null;
+ }
+ }
+
/**
* Creates corner masks (which cover the parts of the corners that should not be shown) and
* borders, as necessary. Both must be created after the radius is known. However, if the size of
@@ -123,7 +160,7 @@
return;
}
- makeCornerMasks(radius, isRtL);
+ initCornerMasks(radius, isRtL);
addBorders(radius);
lastRadius = radius;
@@ -131,47 +168,6 @@
}
/**
- * Make masks for each corner, as applicable.
- *
- * <p>This will no-op if radius and isRtL are the same as the last call.
- */
- private void makeCornerMasks(int radius, boolean isRtL) {
- Canvas canvas = new Canvas();
-
- if ((shouldRoundCorner(Corners.TOP_START) && !isRtL)
- || (shouldRoundCorner(Corners.TOP_END) && isRtL)) {
- cornerTL = Bitmap.createBitmap(radius, radius, Config.ALPHA_8);
- canvas.setBitmap(cornerTL);
- canvas.drawColor(Color.BLACK);
- canvas.drawCircle(radius, radius, radius, maskPaint);
- }
-
- if ((shouldRoundCorner(Corners.TOP_END) && !isRtL)
- || (shouldRoundCorner(Corners.TOP_START) && isRtL)) {
- cornerTR = Bitmap.createBitmap(radius, radius, Config.ALPHA_8);
- canvas.setBitmap(cornerTR);
- canvas.drawColor(Color.BLACK);
- canvas.drawCircle(0, radius, radius, maskPaint);
- }
-
- if ((shouldRoundCorner(Corners.BOTTOM_START) && !isRtL)
- || (shouldRoundCorner(Corners.BOTTOM_END) && isRtL)) {
- cornerBL = Bitmap.createBitmap(radius, radius, Config.ALPHA_8);
- canvas.setBitmap(cornerBL);
- canvas.drawColor(Color.BLACK);
- canvas.drawCircle(radius, 0, radius, maskPaint);
- }
-
- if ((shouldRoundCorner(Corners.BOTTOM_END) && !isRtL)
- || (shouldRoundCorner(Corners.BOTTOM_START) && isRtL)) {
- cornerBR = Bitmap.createBitmap(radius, radius, Config.ALPHA_8);
- canvas.setBitmap(cornerBR);
- canvas.drawColor(Color.BLACK);
- canvas.drawCircle(0, 0, radius, maskPaint);
- }
- }
-
- /**
* Creates a border drawable and adds it to this view's foreground. This is called from {@link
* RoundedCornerWrapperView} because when the corners are rounded, the borders also need to be
* rounded, and the radius can't properly be calculated until the view has been laid out and the
@@ -222,6 +218,38 @@
}
@Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ if (!changed) {
+ return;
+ }
+
+ int width = getWidth();
+ int height = getHeight();
+ if (width == 0 || height == 0) {
+ // The view is not visible; no further processing is needed.
+ return;
+ }
+
+ if (offscreenBitmap == null
+ || offscreenBitmap.getHeight() != height
+ || offscreenBitmap.getWidth() != width) {
+ // We need to use an offscreen bitmap because the default canvas doesn't have transparency (?)
+ if (offscreenBitmap != null) {
+ offscreenBitmap.recycle();
+ }
+ offscreenBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ offscreenCanvas.setBitmap(offscreenBitmap);
+ }
+
+ int radius = getRadius(width, height);
+
+ // Set up the corner masks and borders, both of which require knowing the radius.
+ // This should no-op if radius and isLtR have not changed.
+ setupCornerMasksAndBorders(radius);
+ }
+
+ @Override
public void draw(Canvas canvas) {
if (!hasRoundedCorners()) {
super.draw(canvas);
@@ -230,28 +258,22 @@
int width = getWidth();
int height = getHeight();
if (width == 0 || height == 0) {
- // Bitmap creation will fail in these cases
+ // The view is not visible, and offscreenBitmap creation will fail. Stop here.
return;
}
int radius = getRadius(width, height);
- // Set up the corner masks and borders, both of which require knowing the radius.
- // This should no-op if radius and isLtR have not changed.
- setupCornerMasksAndBorders(radius);
-
- // We need to create a new bitmap because the default canvas doesn't have transparency (?)
- Bitmap offscreenBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
- Canvas offscreenCanvas = new Canvas(offscreenBitmap);
-
// Draw the view without rounded corners on the offscreen canvas.
+ Bitmap localOffscreenBitmap = checkNotNull(offscreenBitmap);
+ localOffscreenBitmap.eraseColor(Color.TRANSPARENT);
super.draw(offscreenCanvas);
// Crop the corners off using masks
maskCorners(offscreenCanvas, width, height, radius, maskPaint);
// Draw the offscreen bitmap (view with rounded corners) to the target canvas.
- canvas.drawBitmap(offscreenBitmap, 0f, 0f, paint);
+ canvas.drawBitmap(localOffscreenBitmap, 0f, 0f, paint);
}
/**
@@ -277,7 +299,7 @@
return radius;
}
- /** Calculates the corner radius */
+ /** Calculates the corner radius, clipping to width or height when necessary. */
private int makeRadius(int width, int height) {
int radius = 0;
diff --git a/src/test/java/com/google/android/libraries/feed/piet/BUILD b/src/test/java/com/google/android/libraries/feed/piet/BUILD
index 2e8a76c..0cd6472 100644
--- a/src/test/java/com/google/android/libraries/feed/piet/BUILD
+++ b/src/test/java/com/google/android/libraries/feed/piet/BUILD
@@ -181,6 +181,7 @@
"//src/main/java/com/google/android/libraries/feed/host/config",
"//src/main/java/com/google/android/libraries/feed/piet",
"//src/main/java/com/google/android/libraries/feed/piet/host",
+ "//src/main/java/com/google/android/libraries/feed/piet/ui",
"//src/main/proto/search/now/ui/piet:piet_errors_java_proto_lite",
"//src/main/proto/search/now/ui/piet:piet_java_proto_lite",
"@com_google_protobuf_javalite//:protobuf_java_lite",
@@ -293,6 +294,7 @@
"//src/main/java/com/google/android/libraries/feed/common/time/testing",
"//src/main/java/com/google/android/libraries/feed/piet",
"//src/main/java/com/google/android/libraries/feed/piet/host",
+ "//src/main/java/com/google/android/libraries/feed/piet/ui",
"@com_google_protobuf_javalite//:protobuf_java_lite",
"@maven//:com_google_truth_truth",
"@maven//:org_mockito_mockito_all",
@@ -408,6 +410,7 @@
"//src/main/java/com/google/android/libraries/feed/host/config",
"//src/main/java/com/google/android/libraries/feed/piet",
"//src/main/java/com/google/android/libraries/feed/piet/host",
+ "//src/main/java/com/google/android/libraries/feed/piet/ui",
"//src/main/proto/search/now/ui/piet:piet_java_proto_lite",
"@com_google_protobuf_javalite//:protobuf_java_lite",
"@maven//:com_google_truth_truth",
diff --git a/src/test/java/com/google/android/libraries/feed/piet/ElementAdapterTest.java b/src/test/java/com/google/android/libraries/feed/piet/ElementAdapterTest.java
index 50ec51b..b8653d7 100644
--- a/src/test/java/com/google/android/libraries/feed/piet/ElementAdapterTest.java
+++ b/src/test/java/com/google/android/libraries/feed/piet/ElementAdapterTest.java
@@ -41,6 +41,7 @@
import com.google.android.libraries.feed.piet.AdapterFactory.SingletonKeySupplier;
import com.google.android.libraries.feed.piet.host.ActionHandler;
import com.google.android.libraries.feed.piet.host.ActionHandler.ActionType;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache;
import com.google.android.libraries.feed.piet.ui.RoundedCornerWrapperView;
import com.google.android.libraries.feed.testing.shadows.ExtendedShadowView;
import com.google.search.now.ui.piet.AccessibilityProto.Accessibility;
@@ -89,6 +90,7 @@
private Context context;
private AdapterParameters parameters;
private View view;
+ private RoundedCornerMaskCache maskCache;
private TestElementAdapter adapter;
@@ -99,11 +101,12 @@
parameters =
new AdapterParameters(
context, Suppliers.of((ViewGroup) null), hostProviders, new FakeClock());
+ maskCache = parameters.roundedCornerMaskCache;
when(frameContext.makeStyleFor(any(StyleIdsStack.class))).thenReturn(styleProvider);
when(frameContext.getActionHandler()).thenReturn(actionHandler);
when(styleProvider.hasRoundedCorners()).thenReturn(false);
when(styleProvider.getRoundedCorners()).thenReturn(RoundedCorners.getDefaultInstance());
- when(styleProvider.createWrapperView(context)).thenReturn(new FrameLayout(context));
+ when(styleProvider.createWrapperView(context, maskCache)).thenReturn(new FrameLayout(context));
view = new View(context);
adapter = new TestElementAdapter(context, parameters, view);
@@ -305,6 +308,8 @@
mockFactory,
mock(TemplateBinder.class),
new FakeClock());
+ when(styleProvider.createWrapperView(context, parameters.roundedCornerMaskCache))
+ .thenReturn(new FrameLayout(context));
adapter = new TestElementAdapter(context, parameters, view);
adapter.createAdapter(elementWithOverlays, elementWithOverlays, frameContext);
@@ -1272,11 +1277,12 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
when(styleProvider.hasRoundedCorners()).thenReturn(true);
- when(styleProvider.createWrapperView(context)).thenReturn(roundedCornerWrapperView);
+ when(styleProvider.createWrapperView(context, maskCache)).thenReturn(roundedCornerWrapperView);
adapter.createAdapter(Element.getDefaultInstance(), frameContext);
@@ -1290,11 +1296,12 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
when(styleProvider.hasRoundedCorners()).thenReturn(true);
- when(styleProvider.createWrapperView(context)).thenReturn(roundedCornerWrapperView);
+ when(styleProvider.createWrapperView(context, maskCache)).thenReturn(roundedCornerWrapperView);
VisibilityBindingRef visibilityBinding =
VisibilityBindingRef.newBuilder().setBindingId("visibility").build();
diff --git a/src/test/java/com/google/android/libraries/feed/piet/FrameAdapterImplTest.java b/src/test/java/com/google/android/libraries/feed/piet/FrameAdapterImplTest.java
index 7813cf0..c83f3d8 100644
--- a/src/test/java/com/google/android/libraries/feed/piet/FrameAdapterImplTest.java
+++ b/src/test/java/com/google/android/libraries/feed/piet/FrameAdapterImplTest.java
@@ -47,6 +47,7 @@
import com.google.android.libraries.feed.piet.host.CustomElementProvider;
import com.google.android.libraries.feed.piet.host.EventLogger;
import com.google.android.libraries.feed.piet.host.HostBindingProvider;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache;
import com.google.search.now.ui.piet.ActionsProto.Action;
import com.google.search.now.ui.piet.ActionsProto.Actions;
import com.google.search.now.ui.piet.ActionsProto.VisibilityAction;
@@ -116,6 +117,7 @@
@Mock private HostProviders hostProviders;
@Mock private EventLogger eventLogger;
@Mock private PietStylesHelperFactory stylesHelpers;
+ @Mock private RoundedCornerMaskCache maskCache;
@Captor private ArgumentCaptor<LayoutParams> layoutParamsCaptor;
@@ -138,7 +140,8 @@
adapterFactory,
templateBinder,
new FakeClock(),
- stylesHelpers);
+ stylesHelpers,
+ maskCache);
when(elementAdapter.getView()).thenReturn(new LinearLayout(context));
when(templateAdapter.getView()).thenReturn(new LinearLayout(context));
doReturn(elementAdapter)
diff --git a/src/test/java/com/google/android/libraries/feed/piet/ImageElementAdapterTest.java b/src/test/java/com/google/android/libraries/feed/piet/ImageElementAdapterTest.java
index e2c4680..7560d78 100644
--- a/src/test/java/com/google/android/libraries/feed/piet/ImageElementAdapterTest.java
+++ b/src/test/java/com/google/android/libraries/feed/piet/ImageElementAdapterTest.java
@@ -39,7 +39,9 @@
import com.google.android.libraries.feed.common.functional.Suppliers;
import com.google.android.libraries.feed.common.time.testing.FakeClock;
import com.google.android.libraries.feed.common.ui.LayoutUtils;
+import com.google.android.libraries.feed.piet.PietStylesHelper.PietStylesHelperFactory;
import com.google.android.libraries.feed.piet.host.AssetProvider;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache;
import com.google.android.libraries.feed.piet.ui.RoundedCornerWrapperView;
import com.google.search.now.ui.piet.BindingRefsProto.ImageBindingRef;
import com.google.search.now.ui.piet.BindingRefsProto.StyleBindingRef;
@@ -91,6 +93,7 @@
private int widthPx;
private ImageView imageView;
private final FakeClock clock = new FakeClock();
+ private RoundedCornerMaskCache maskCache;
private ImageElementAdapterForTest adapter;
@@ -100,9 +103,18 @@
context = Robolectric.buildActivity(Activity.class).get();
heightPx = (int) LayoutUtils.dpToPx(HEIGHT_DP, context);
widthPx = (int) LayoutUtils.dpToPx(WIDTH_DP, context);
+ maskCache = new RoundedCornerMaskCache();
AdapterParameters parameters =
new AdapterParameters(
- context, null, hostProviders, null, adapterFactory, templateBinder, clock);
+ context,
+ null,
+ hostProviders,
+ null,
+ adapterFactory,
+ templateBinder,
+ clock,
+ new PietStylesHelperFactory(),
+ maskCache);
when(frameContext.makeStyleFor(any(StyleIdsStack.class))).thenReturn(styleProvider);
when(frameContext.filterImageSourcesByMediaQueryCondition(any(Image.class)))
@@ -112,11 +124,12 @@
when(styleProvider.hasRoundedCorners()).thenReturn(true);
when(styleProvider.getRoundedCorners()).thenReturn(CORNERS);
when(styleProvider.getScaleType()).thenReturn(ScaleType.FIT_CENTER);
- when(styleProvider.createWrapperView(context))
+ when(styleProvider.createWrapperView(context, maskCache))
.thenReturn(
new RoundedCornerWrapperView(
context,
CORNERS,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
/* borders= */ null));
diff --git a/src/test/java/com/google/android/libraries/feed/piet/PietManagerImplTest.java b/src/test/java/com/google/android/libraries/feed/piet/PietManagerImplTest.java
index f42082d..b517852 100644
--- a/src/test/java/com/google/android/libraries/feed/piet/PietManagerImplTest.java
+++ b/src/test/java/com/google/android/libraries/feed/piet/PietManagerImplTest.java
@@ -43,6 +43,7 @@
import com.google.android.libraries.feed.piet.host.StringFormatter;
import com.google.android.libraries.feed.piet.host.ThrowingCustomElementProvider;
import com.google.android.libraries.feed.piet.host.TypefaceProvider;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache;
import com.google.search.now.ui.piet.ImagesProto.Image;
import com.google.search.now.ui.piet.PietProto.Frame;
import java.util.Collections;
@@ -223,6 +224,7 @@
ElementAdapterFactory mockFactory = mock(ElementAdapterFactory.class);
TemplateBinder mockTemplateBinder = mock(TemplateBinder.class);
HostProviders hostProviders = mock(HostProviders.class);
+ RoundedCornerMaskCache maskCache = mock(RoundedCornerMaskCache.class);
pietManager.adapterParameters =
new AdapterParameters(
context,
@@ -232,9 +234,11 @@
mockFactory,
mockTemplateBinder,
new FakeClock(),
- stylesHelpers);
+ stylesHelpers,
+ maskCache);
pietManager.purgeRecyclerPools();
verify(mockFactory).purgeRecyclerPools();
verify(stylesHelpers).purge();
+ verify(maskCache).purge();
}
}
diff --git a/src/test/java/com/google/android/libraries/feed/piet/StyleProviderTest.java b/src/test/java/com/google/android/libraries/feed/piet/StyleProviderTest.java
index 7902ed6..5674777 100644
--- a/src/test/java/com/google/android/libraries/feed/piet/StyleProviderTest.java
+++ b/src/test/java/com/google/android/libraries/feed/piet/StyleProviderTest.java
@@ -39,6 +39,7 @@
import com.google.android.libraries.feed.piet.host.AssetProvider;
import com.google.android.libraries.feed.piet.ui.BorderDrawable;
import com.google.android.libraries.feed.piet.ui.GradientDrawable;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache;
import com.google.android.libraries.feed.piet.ui.RoundedCornerWrapperView;
import com.google.search.now.ui.piet.GradientsProto.ColorStop;
import com.google.search.now.ui.piet.GradientsProto.Fill;
@@ -73,6 +74,7 @@
@Mock private AssetProvider mockAssetProvider;
@Mock private ElementAdapter<View, ?> adapter;
@Mock private TextElementAdapter textAdapter;
+ @Mock private RoundedCornerMaskCache maskCache;
private View view;
private View baseView;
@@ -397,7 +399,7 @@
.build(),
mockAssetProvider);
- assertThat(styleProvider.createWrapperView(context))
+ assertThat(styleProvider.createWrapperView(context, maskCache))
.isNotInstanceOf(RoundedCornerWrapperView.class);
}
@@ -410,7 +412,7 @@
.build(),
mockAssetProvider);
- assertThat(styleProvider.createWrapperView(context))
+ assertThat(styleProvider.createWrapperView(context, maskCache))
.isInstanceOf(RoundedCornerWrapperView.class);
}
diff --git a/src/test/java/com/google/android/libraries/feed/piet/ui/BUILD b/src/test/java/com/google/android/libraries/feed/piet/ui/BUILD
index ff91040..c6ee553 100644
--- a/src/test/java/com/google/android/libraries/feed/piet/ui/BUILD
+++ b/src/test/java/com/google/android/libraries/feed/piet/ui/BUILD
@@ -102,6 +102,22 @@
)
android_local_test(
+ name = "RoundedCornerMaskCacheTest",
+ size = "small",
+ timeout = "moderate",
+ srcs = ["RoundedCornerMaskCacheTest.java"],
+ aapt_version = "aapt2",
+ manifest_values = DEFAULT_ANDROID_LOCAL_TEST_MANIFEST,
+ deps = [
+ "//src/main/java/com/google/android/libraries/feed/common/testing",
+ "//src/main/java/com/google/android/libraries/feed/piet/ui",
+ "@com_google_protobuf_javalite//:protobuf_java_lite",
+ "@maven//:com_google_truth_truth",
+ "@robolectric//bazel:robolectric",
+ ],
+)
+
+android_local_test(
name = "RoundedCornerWrapperViewTest",
size = "small",
timeout = "moderate",
@@ -114,6 +130,7 @@
"//src/main/proto/search/now/ui/piet:piet_java_proto_lite",
"@com_google_protobuf_javalite//:protobuf_java_lite",
"@maven//:com_google_truth_truth",
+ "@maven//:org_mockito_mockito_all",
"@robolectric//bazel:robolectric",
],
)
diff --git a/src/test/java/com/google/android/libraries/feed/piet/ui/RoundedCornerMaskCacheTest.java b/src/test/java/com/google/android/libraries/feed/piet/ui/RoundedCornerMaskCacheTest.java
new file mode 100644
index 0000000..d0e22e1
--- /dev/null
+++ b/src/test/java/com/google/android/libraries/feed/piet/ui/RoundedCornerMaskCacheTest.java
@@ -0,0 +1,109 @@
+// Copyright 2018 The Feed Authors.
+//
+// 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.google.android.libraries.feed.piet.ui;
+
+import static com.google.android.libraries.feed.common.testing.RunnableSubject.assertThatRunnable;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Bitmap;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache.Corner;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache.RoundedCornerBitmaps;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for the {@link RoundedCornerMaskCache}. */
+@RunWith(RobolectricTestRunner.class)
+public class RoundedCornerMaskCacheTest {
+
+ private RoundedCornerMaskCache cache;
+
+ @Before
+ public void setUp() {
+ cache = new RoundedCornerMaskCache();
+ }
+
+ @Test
+ public void testGetBitmaps_createsNewInstance() {
+ RoundedCornerBitmaps masks = cache.getMasks(16);
+
+ for (int i = 0; i < 4; i++) {
+ Bitmap mask = masks.get(i);
+ assertThat(mask.getWidth()).isEqualTo(16);
+ assertThat(mask.getHeight()).isEqualTo(16);
+ }
+ }
+
+ @Test
+ public void testGetBitmaps_differentRadii() {
+ RoundedCornerBitmaps masksFive = cache.getMasks(5);
+ RoundedCornerBitmaps masksTen = cache.getMasks(10);
+
+ assertThat(masksFive).isNotEqualTo(masksTen);
+
+ Bitmap maskFive = masksFive.get(Corner.TOP_LEFT);
+ Bitmap maskTen = masksTen.get(Corner.TOP_LEFT);
+
+ assertThat(maskFive).isNotEqualTo(maskTen);
+ assertThat(maskFive.getWidth()).isEqualTo(5);
+ assertThat(maskTen.getWidth()).isEqualTo(10);
+ }
+
+ @Test
+ public void testGetBitmaps_cachesInstance() {
+ RoundedCornerBitmaps masks1 = cache.getMasks(16);
+
+ RoundedCornerBitmaps masks2 = cache.getMasks(16);
+
+ assertThat(masks1).isSameAs(masks2);
+ }
+
+ @Test
+ public void testPurge() {
+ RoundedCornerBitmaps masks1 = cache.getMasks(16);
+
+ cache.purge();
+
+ RoundedCornerBitmaps masks2 = cache.getMasks(16);
+
+ assertThat(masks1).isNotSameAs(masks2);
+ }
+
+ @Test
+ public void testBadCornerException() {
+ RoundedCornerBitmaps masks = cache.getMasks(16);
+
+ assertThatRunnable(() -> masks.get(999))
+ .throwsAnExceptionOfType(IllegalArgumentException.class);
+ }
+
+ @Test
+ public void testCreatesOnlyRequestedCorners() {
+ RoundedCornerBitmaps masks = cache.getMasks(123);
+
+ assertThat(masks.masks[Corner.TOP_LEFT]).isNull();
+ assertThat(masks.masks[Corner.TOP_RIGHT]).isNull();
+ assertThat(masks.masks[Corner.BOTTOM_LEFT]).isNull();
+ assertThat(masks.masks[Corner.BOTTOM_RIGHT]).isNull();
+
+ masks.get(Corner.TOP_RIGHT);
+
+ assertThat(masks.masks[Corner.TOP_LEFT]).isNull();
+ assertThat(masks.masks[Corner.TOP_RIGHT]).isNotNull();
+ assertThat(masks.masks[Corner.BOTTOM_LEFT]).isNull();
+ assertThat(masks.masks[Corner.BOTTOM_RIGHT]).isNull();
+ }
+}
diff --git a/src/test/java/com/google/android/libraries/feed/piet/ui/RoundedCornerWrapperViewTest.java b/src/test/java/com/google/android/libraries/feed/piet/ui/RoundedCornerWrapperViewTest.java
index ecd503e..bdcc77b 100644
--- a/src/test/java/com/google/android/libraries/feed/piet/ui/RoundedCornerWrapperViewTest.java
+++ b/src/test/java/com/google/android/libraries/feed/piet/ui/RoundedCornerWrapperViewTest.java
@@ -15,6 +15,11 @@
package com.google.android.libraries.feed.piet.ui;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
import android.app.Activity;
import android.content.Context;
@@ -22,6 +27,8 @@
import android.graphics.Canvas;
import com.google.android.libraries.feed.common.functional.Supplier;
import com.google.android.libraries.feed.common.functional.Suppliers;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache.Corner;
+import com.google.android.libraries.feed.piet.ui.RoundedCornerMaskCache.RoundedCornerBitmaps;
import com.google.search.now.ui.piet.RoundedCornersProto.RoundedCorners;
import com.google.search.now.ui.piet.StylesProto.Borders;
import org.junit.Before;
@@ -41,12 +48,14 @@
private Context context;
private Canvas canvas;
private Bitmap bitmap;
+ private RoundedCornerMaskCache maskCache;
@Before
public void setUp() {
context = Robolectric.buildActivity(Activity.class).get();
bitmap = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
+ maskCache = new RoundedCornerMaskCache();
}
@Test
@@ -55,6 +64,7 @@
new RoundedCornerWrapperView(
context,
RoundedCorners.getDefaultInstance(),
+ maskCache,
LEFT_TO_RIGHT,
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -84,6 +94,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
LEFT_TO_RIGHT,
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -113,7 +124,8 @@
RoundedCorners roundedCorners = RoundedCorners.newBuilder().setRadiusDp(10).build();
RoundedCornerWrapperView view =
- new RoundedCornerWrapperView(context, roundedCorners, Suppliers.of(false), 0, borders);
+ new RoundedCornerWrapperView(
+ context, roundedCorners, maskCache, Suppliers.of(false), 0, borders);
view.layout(0, 0, 100, 100);
view.draw(canvas);
@@ -130,7 +142,8 @@
RoundedCorners roundedCorners = RoundedCorners.newBuilder().setRadiusDp(10).build();
RoundedCornerWrapperView view =
- new RoundedCornerWrapperView(context, roundedCorners, Suppliers.of(false), 0, borders);
+ new RoundedCornerWrapperView(
+ context, roundedCorners, maskCache, Suppliers.of(false), 0, borders);
// Set a width and height on the view so that the only thing stopping borders from being created
// is the border width of 0.
@@ -152,6 +165,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
radiusOverride,
Borders.getDefaultInstance());
@@ -172,6 +186,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -192,6 +207,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -213,6 +229,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -233,6 +250,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -258,6 +276,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -279,6 +298,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -304,6 +324,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -326,6 +347,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -350,6 +372,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -375,6 +398,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -397,6 +421,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -422,6 +447,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -447,6 +473,7 @@
new RoundedCornerWrapperView(
context,
roundedCorners,
+ maskCache,
Suppliers.of(false),
/*radiusOverride= */ 0,
Borders.getDefaultInstance());
@@ -457,4 +484,33 @@
// need to shrink the radius.
assertThat(calculatedRadius).isEqualTo(40);
}
+
+ @Test
+ public void testGetsMasksFromCache() {
+ RoundedCorners roundedCorners =
+ RoundedCorners.newBuilder().setBitmask(LEFT_CORNERS_BITMASK).setRadiusDp(16).build();
+ RoundedCornerMaskCache mockMaskCache = mock(RoundedCornerMaskCache.class);
+ RoundedCornerBitmaps mockBitmaps = mock(RoundedCornerBitmaps.class);
+ Bitmap maskBitmap = maskCache.getMasks(16).get(Corner.TOP_LEFT);
+ when(mockMaskCache.getPaint()).thenReturn(maskCache.getPaint());
+ when(mockMaskCache.getMaskPaint()).thenReturn(maskCache.getMaskPaint());
+ when(mockMaskCache.getMasks(16)).thenReturn(mockBitmaps);
+ when(mockBitmaps.get(anyInt())).thenReturn(maskBitmap);
+ RoundedCornerWrapperView view =
+ new RoundedCornerWrapperView(
+ context,
+ roundedCorners,
+ mockMaskCache,
+ Suppliers.of(false),
+ /*radiusOverride= */ 0,
+ Borders.getDefaultInstance());
+
+ view.layout(0, 0, 100, 100);
+
+ verify(mockMaskCache).getMasks(16);
+
+ verify(mockBitmaps).get(Corner.TOP_LEFT);
+ verify(mockBitmaps).get(Corner.BOTTOM_LEFT);
+ verifyNoMoreInteractions(mockBitmaps);
+ }
}