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);
+  }
 }