[go: nahoru, domu]

Add enableZ and disableZ to Canvas.

Adds the ability to use shadows and draw reordering on
L and greater.

Test: New test on API 26, 27, 28
Change-Id: Ic3e1e55e0f371c151820eda2a7b3d3dea8a5785c
diff --git a/ui/ui-graphics/api/0.1.0-dev06.txt b/ui/ui-graphics/api/0.1.0-dev06.txt
index 8b13c44..27ddce0 100644
--- a/ui/ui-graphics/api/0.1.0-dev06.txt
+++ b/ui/ui-graphics/api/0.1.0-dev06.txt
@@ -81,6 +81,7 @@
     method public void clipRRect(androidx.ui.geometry.RRect rrect);
     method public void clipRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.ClipOp clipOp = androidx.ui.graphics.ClipOp.intersect);
     method public void concat(androidx.ui.graphics.vectormath.Matrix4 matrix4);
+    method public void disableZ();
     method public void drawArc(androidx.ui.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public default void drawArcRad(androidx.ui.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public void drawCircle(androidx.ui.geometry.Offset center, float radius, androidx.ui.graphics.Paint paint);
@@ -95,6 +96,7 @@
     method public void drawRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.Paint paint);
     method public void drawRoundRect(float left, float top, float right, float bottom, float radiusX, float radiusY, androidx.ui.graphics.Paint paint);
     method public void drawVertices(androidx.ui.graphics.Vertices vertices, androidx.ui.graphics.BlendMode blendMode, androidx.ui.graphics.Paint paint);
+    method public void enableZ();
     method public android.graphics.Canvas getNativeCanvas();
     method public void restore();
     method public void rotate(float degrees);
diff --git a/ui/ui-graphics/api/current.txt b/ui/ui-graphics/api/current.txt
index 8b13c44..27ddce0 100644
--- a/ui/ui-graphics/api/current.txt
+++ b/ui/ui-graphics/api/current.txt
@@ -81,6 +81,7 @@
     method public void clipRRect(androidx.ui.geometry.RRect rrect);
     method public void clipRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.ClipOp clipOp = androidx.ui.graphics.ClipOp.intersect);
     method public void concat(androidx.ui.graphics.vectormath.Matrix4 matrix4);
+    method public void disableZ();
     method public void drawArc(androidx.ui.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public default void drawArcRad(androidx.ui.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public void drawCircle(androidx.ui.geometry.Offset center, float radius, androidx.ui.graphics.Paint paint);
@@ -95,6 +96,7 @@
     method public void drawRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.Paint paint);
     method public void drawRoundRect(float left, float top, float right, float bottom, float radiusX, float radiusY, androidx.ui.graphics.Paint paint);
     method public void drawVertices(androidx.ui.graphics.Vertices vertices, androidx.ui.graphics.BlendMode blendMode, androidx.ui.graphics.Paint paint);
+    method public void enableZ();
     method public android.graphics.Canvas getNativeCanvas();
     method public void restore();
     method public void rotate(float degrees);
diff --git a/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev06.txt b/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev06.txt
index 8b13c44..27ddce0 100644
--- a/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev06.txt
+++ b/ui/ui-graphics/api/public_plus_experimental_0.1.0-dev06.txt
@@ -81,6 +81,7 @@
     method public void clipRRect(androidx.ui.geometry.RRect rrect);
     method public void clipRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.ClipOp clipOp = androidx.ui.graphics.ClipOp.intersect);
     method public void concat(androidx.ui.graphics.vectormath.Matrix4 matrix4);
+    method public void disableZ();
     method public void drawArc(androidx.ui.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public default void drawArcRad(androidx.ui.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public void drawCircle(androidx.ui.geometry.Offset center, float radius, androidx.ui.graphics.Paint paint);
@@ -95,6 +96,7 @@
     method public void drawRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.Paint paint);
     method public void drawRoundRect(float left, float top, float right, float bottom, float radiusX, float radiusY, androidx.ui.graphics.Paint paint);
     method public void drawVertices(androidx.ui.graphics.Vertices vertices, androidx.ui.graphics.BlendMode blendMode, androidx.ui.graphics.Paint paint);
+    method public void enableZ();
     method public android.graphics.Canvas getNativeCanvas();
     method public void restore();
     method public void rotate(float degrees);
diff --git a/ui/ui-graphics/api/public_plus_experimental_current.txt b/ui/ui-graphics/api/public_plus_experimental_current.txt
index 8b13c44..27ddce0 100644
--- a/ui/ui-graphics/api/public_plus_experimental_current.txt
+++ b/ui/ui-graphics/api/public_plus_experimental_current.txt
@@ -81,6 +81,7 @@
     method public void clipRRect(androidx.ui.geometry.RRect rrect);
     method public void clipRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.ClipOp clipOp = androidx.ui.graphics.ClipOp.intersect);
     method public void concat(androidx.ui.graphics.vectormath.Matrix4 matrix4);
+    method public void disableZ();
     method public void drawArc(androidx.ui.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public default void drawArcRad(androidx.ui.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public void drawCircle(androidx.ui.geometry.Offset center, float radius, androidx.ui.graphics.Paint paint);
@@ -95,6 +96,7 @@
     method public void drawRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.Paint paint);
     method public void drawRoundRect(float left, float top, float right, float bottom, float radiusX, float radiusY, androidx.ui.graphics.Paint paint);
     method public void drawVertices(androidx.ui.graphics.Vertices vertices, androidx.ui.graphics.BlendMode blendMode, androidx.ui.graphics.Paint paint);
+    method public void enableZ();
     method public android.graphics.Canvas getNativeCanvas();
     method public void restore();
     method public void rotate(float degrees);
diff --git a/ui/ui-graphics/api/restricted_0.1.0-dev06.txt b/ui/ui-graphics/api/restricted_0.1.0-dev06.txt
index 8b13c44..27ddce0 100644
--- a/ui/ui-graphics/api/restricted_0.1.0-dev06.txt
+++ b/ui/ui-graphics/api/restricted_0.1.0-dev06.txt
@@ -81,6 +81,7 @@
     method public void clipRRect(androidx.ui.geometry.RRect rrect);
     method public void clipRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.ClipOp clipOp = androidx.ui.graphics.ClipOp.intersect);
     method public void concat(androidx.ui.graphics.vectormath.Matrix4 matrix4);
+    method public void disableZ();
     method public void drawArc(androidx.ui.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public default void drawArcRad(androidx.ui.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public void drawCircle(androidx.ui.geometry.Offset center, float radius, androidx.ui.graphics.Paint paint);
@@ -95,6 +96,7 @@
     method public void drawRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.Paint paint);
     method public void drawRoundRect(float left, float top, float right, float bottom, float radiusX, float radiusY, androidx.ui.graphics.Paint paint);
     method public void drawVertices(androidx.ui.graphics.Vertices vertices, androidx.ui.graphics.BlendMode blendMode, androidx.ui.graphics.Paint paint);
+    method public void enableZ();
     method public android.graphics.Canvas getNativeCanvas();
     method public void restore();
     method public void rotate(float degrees);
diff --git a/ui/ui-graphics/api/restricted_current.txt b/ui/ui-graphics/api/restricted_current.txt
index 8b13c44..27ddce0 100644
--- a/ui/ui-graphics/api/restricted_current.txt
+++ b/ui/ui-graphics/api/restricted_current.txt
@@ -81,6 +81,7 @@
     method public void clipRRect(androidx.ui.geometry.RRect rrect);
     method public void clipRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.ClipOp clipOp = androidx.ui.graphics.ClipOp.intersect);
     method public void concat(androidx.ui.graphics.vectormath.Matrix4 matrix4);
+    method public void disableZ();
     method public void drawArc(androidx.ui.geometry.Rect rect, float startAngle, float sweepAngle, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public default void drawArcRad(androidx.ui.geometry.Rect rect, float startAngleRad, float sweepAngleRad, boolean useCenter, androidx.ui.graphics.Paint paint);
     method public void drawCircle(androidx.ui.geometry.Offset center, float radius, androidx.ui.graphics.Paint paint);
@@ -95,6 +96,7 @@
     method public void drawRect(androidx.ui.geometry.Rect rect, androidx.ui.graphics.Paint paint);
     method public void drawRoundRect(float left, float top, float right, float bottom, float radiusX, float radiusY, androidx.ui.graphics.Paint paint);
     method public void drawVertices(androidx.ui.graphics.Vertices vertices, androidx.ui.graphics.BlendMode blendMode, androidx.ui.graphics.Paint paint);
+    method public void enableZ();
     method public android.graphics.Canvas getNativeCanvas();
     method public void restore();
     method public void rotate(float degrees);
diff --git a/ui/ui-graphics/build.gradle b/ui/ui-graphics/build.gradle
index 1bef071..3cd3e77 100644
--- a/ui/ui-graphics/build.gradle
+++ b/ui/ui-graphics/build.gradle
@@ -46,6 +46,7 @@
     androidTestImplementation(ANDROIDX_TEST_RUNNER)
     androidTestImplementation(ESPRESSO_CORE)
     androidTestImplementation(JUNIT)
+    androidTestImplementation project(":ui:ui-test")
 }
 
 androidx {
diff --git a/ui/ui-graphics/src/androidTest/AndroidManifest.xml b/ui/ui-graphics/src/androidTest/AndroidManifest.xml
index 33c32a8..e0239e4 100644
--- a/ui/ui-graphics/src/androidTest/AndroidManifest.xml
+++ b/ui/ui-graphics/src/androidTest/AndroidManifest.xml
@@ -15,6 +15,9 @@
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="androidx.ui.graphics.test">
-    <!-- TODO(147665937): Remove android:process when b/147665937 is fixed -->
-    <application android:process="androidx.ui.graphics"/>
+    <application android:process="androidx.ui.graphics">
+        <activity
+            android:name="androidx.ui.graphics.TestActivity"
+            android:theme="@android:style/Theme.Material.NoActionBar.Fullscreen"/>
+    </application>
 </manifest>
diff --git a/ui/ui-graphics/src/androidTest/java/androidx/ui/graphics/AndroidCanvasTest.kt b/ui/ui-graphics/src/androidTest/java/androidx/ui/graphics/AndroidCanvasTest.kt
new file mode 100644
index 0000000..89496a1
--- /dev/null
+++ b/ui/ui-graphics/src/androidTest/java/androidx/ui/graphics/AndroidCanvasTest.kt
@@ -0,0 +1,96 @@
+/*
+ * 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.
+ */
+
+package androidx.ui.graphics
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Color
+import android.os.Build
+import android.util.AttributeSet
+import android.view.Gravity
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.rule.ActivityTestRule
+import androidx.ui.test.captureToBitmap
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@MediumTest
+@RunWith(JUnit4::class)
+class AndroidCanvasTest {
+    @get:Rule
+    val activityTestRule = ActivityTestRule<TestActivity>(
+        TestActivity::class.java
+    )
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun testEnableDisableZ() {
+        val activity = activityTestRule.activity
+        activity.hasFocusLatch.await(5, TimeUnit.SECONDS)
+        val drawLatch = CountDownLatch(1)
+        var groupView: ViewGroup? = null
+
+        activityTestRule.runOnUiThread {
+            val group = EnableDisableZViewGroup(drawLatch, activity)
+            groupView = group
+            group.setBackgroundColor(Color.WHITE)
+            group.layoutParams = ViewGroup.LayoutParams(12, 12)
+            val child = View(activity)
+            val childLayoutParams = FrameLayout.LayoutParams(10, 10)
+            childLayoutParams.gravity = Gravity.TOP or Gravity.LEFT
+            child.layoutParams = childLayoutParams
+            child.elevation = 4f
+            child.setBackgroundColor(Color.WHITE)
+            group.addView(child)
+            activity.setContentView(group)
+        }
+
+        assertTrue(drawLatch.await(1, TimeUnit.SECONDS))
+        val bitmap = groupView!!.captureToBitmap()
+        assertEquals(Color.WHITE, bitmap.getPixel(0, 0))
+        assertEquals(Color.WHITE, bitmap.getPixel(9, 9))
+        assertNotEquals(Color.WHITE, bitmap.getPixel(10, 10))
+    }
+
+    class EnableDisableZViewGroup @JvmOverloads constructor(
+        val drawLatch: CountDownLatch,
+        context: Context,
+        attrs: AttributeSet? = null,
+        defStyleAttr: Int = 0
+    ) : FrameLayout(context, attrs, defStyleAttr) {
+        override fun dispatchDraw(canvas: Canvas) {
+            val androidCanvas = Canvas(canvas)
+            androidCanvas.enableZ()
+            for (i in 0 until childCount) {
+                drawChild(androidCanvas.nativeCanvas, getChildAt(i), drawingTime)
+            }
+            androidCanvas.disableZ()
+            drawLatch.countDown()
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-graphics/src/androidTest/java/androidx/ui/graphics/TestActivity.kt b/ui/ui-graphics/src/androidTest/java/androidx/ui/graphics/TestActivity.kt
new file mode 100644
index 0000000..ac493ee
--- /dev/null
+++ b/ui/ui-graphics/src/androidTest/java/androidx/ui/graphics/TestActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+package androidx.ui.graphics
+
+import android.app.Activity
+import java.util.concurrent.CountDownLatch
+
+class TestActivity : Activity() {
+    var hasFocusLatch = CountDownLatch(1)
+
+    override fun onWindowFocusChanged(hasFocus: Boolean) {
+        super.onWindowFocusChanged(hasFocus)
+        if (hasFocus) {
+            hasFocusLatch.countDown()
+        }
+    }
+}
\ No newline at end of file
diff --git a/ui/ui-graphics/src/main/java/androidx/ui/graphics/AndroidCanvas.kt b/ui/ui-graphics/src/main/java/androidx/ui/graphics/AndroidCanvas.kt
index 3c2df07..c212706 100644
--- a/ui/ui-graphics/src/main/java/androidx/ui/graphics/AndroidCanvas.kt
+++ b/ui/ui-graphics/src/main/java/androidx/ui/graphics/AndroidCanvas.kt
@@ -316,6 +316,14 @@
         }
     }
 
+    override fun enableZ() {
+        CanvasUtils.enableZ(internalCanvas, true)
+    }
+
+    override fun disableZ() {
+        CanvasUtils.enableZ(internalCanvas, false)
+    }
+
     private fun drawPoints(points: List<Offset>, paint: Paint) {
         for (point in points) {
             internalCanvas.drawPoint(point.dx,
diff --git a/ui/ui-graphics/src/main/java/androidx/ui/graphics/Canvas.kt b/ui/ui-graphics/src/main/java/androidx/ui/graphics/Canvas.kt
index ae89994..13bb32b 100644
--- a/ui/ui-graphics/src/main/java/androidx/ui/graphics/Canvas.kt
+++ b/ui/ui-graphics/src/main/java/androidx/ui/graphics/Canvas.kt
@@ -464,4 +464,20 @@
     fun drawRawPoints(pointMode: PointMode, points: FloatArray, paint: Paint)
 
     fun drawVertices(vertices: Vertices, blendMode: BlendMode, paint: Paint)
+
+    /**
+     * Enables Z support which defaults to disabled. This allows layers drawn
+     * with different elevations to be rearranged based on their elevation. It
+     * also enables rendering of shadows.
+     * @see disableZ
+     */
+    fun enableZ()
+
+    /**
+     * Disables Z support, preventing any layers drawn after this point from being visually
+     * reordered or having shadows rendered. This is not impacted by any [save] or [restore]
+     * calls as it is not considered part of the matrix or clip.
+     * @see enableZ
+     */
+    fun disableZ()
 }
\ No newline at end of file
diff --git a/ui/ui-graphics/src/main/java/androidx/ui/graphics/CanvasUtils.kt b/ui/ui-graphics/src/main/java/androidx/ui/graphics/CanvasUtils.kt
new file mode 100644
index 0000000..3ddd3d6
--- /dev/null
+++ b/ui/ui-graphics/src/main/java/androidx/ui/graphics/CanvasUtils.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+package androidx.ui.graphics
+
+import android.annotation.SuppressLint
+import android.graphics.Canvas
+import android.os.Build
+import java.lang.reflect.InvocationTargetException
+import java.lang.reflect.Method
+
+internal object CanvasUtils {
+    private var reorderBarrierMethod: Method? = null
+    private var inorderBarrierMethod: Method? = null
+    private var orderMethodsFetched = false
+
+    /**
+     * Enables Z support for the Canvas.
+     *
+     * This is only supported on Lollipop and later.
+     */
+    @SuppressLint("SoonBlockedPrivateApi")
+    fun enableZ(canvas: Canvas, enable: Boolean) {
+        if (Build.VERSION.SDK_INT >= 29) {
+            if (enable) {
+                canvas.enableZ()
+            } else {
+                canvas.disableZ()
+            }
+        } else {
+            if (!orderMethodsFetched) {
+                try {
+                    if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
+                        // use double reflection to avoid grey list on P
+                        val getDeclaredMethod = Class::class.java.getDeclaredMethod(
+                            "getDeclaredMethod",
+                            String::class.java,
+                            arrayOf<Class<*>>()::class.java
+                        )
+                        reorderBarrierMethod = getDeclaredMethod.invoke(
+                            Canvas::class.java,
+                            "insertReorderBarrier",
+                            emptyArray<Class<*>>()
+                        ) as Method?
+                        inorderBarrierMethod = getDeclaredMethod.invoke(
+                            Canvas::class.java,
+                            "insertInorderBarrier",
+                            emptyArray<Class<*>>()
+                        ) as Method?
+                    } else {
+                        reorderBarrierMethod = Canvas::class.java.getDeclaredMethod(
+                            "insertReorderBarrier"
+                        )
+                        inorderBarrierMethod = Canvas::class.java.getDeclaredMethod(
+                            "insertInorderBarrier"
+                        )
+                    }
+                    reorderBarrierMethod?.isAccessible = true
+                    inorderBarrierMethod?.isAccessible = true
+                } catch (ignore: IllegalAccessException) { // Do nothing
+                } catch (ignore: InvocationTargetException) { // Do nothing
+                } catch (ignore: NoSuchMethodException) { // Do nothing
+                }
+                orderMethodsFetched = true
+            }
+            try {
+                if (enable && reorderBarrierMethod != null) {
+                    reorderBarrierMethod!!.invoke(canvas)
+                }
+                if (!enable && inorderBarrierMethod != null) {
+                    inorderBarrierMethod!!.invoke(canvas)
+                }
+            } catch (ignore: IllegalAccessException) { // Do nothing
+            } catch (ignore: InvocationTargetException) { // Do nothing
+            }
+        }
+    }
+}
diff --git a/ui/ui-test/api/0.1.0-dev06.txt b/ui/ui-test/api/0.1.0-dev06.txt
index 9b5a9a2..1944548 100644
--- a/ui/ui-test/api/0.1.0-dev06.txt
+++ b/ui/ui-test/api/0.1.0-dev06.txt
@@ -35,6 +35,7 @@
     method public static void assertPixels(android.graphics.Bitmap, androidx.ui.unit.IntPxSize? expectedSize = null, kotlin.jvm.functions.Function1<? super androidx.ui.unit.IntPxPosition,androidx.ui.graphics.Color> expectedColorProvider);
     method public static void assertShape(android.graphics.Bitmap, androidx.ui.unit.Density density, androidx.ui.graphics.Shape shape, androidx.ui.graphics.Color shapeColor, androidx.ui.graphics.Color backgroundColor, androidx.ui.graphics.Shape backgroundShape = RectangleShape, androidx.ui.unit.Px sizeX = width.toFloat().px, androidx.ui.unit.Px sizeY = height.toFloat().px, androidx.ui.unit.Px shapeSizeX = sizeX, androidx.ui.unit.Px shapeSizeY = sizeY, androidx.ui.unit.Px centerX = width.px / 2.0, androidx.ui.unit.Px centerY = height.px / 2.0, androidx.ui.unit.Px shapeOverlapPixelCount = 1.px);
     method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(androidx.ui.test.SemanticsNodeInteraction);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(android.view.View);
   }
 
   public final class CollectedSizes {
diff --git a/ui/ui-test/api/current.txt b/ui/ui-test/api/current.txt
index 9b5a9a2..1944548 100644
--- a/ui/ui-test/api/current.txt
+++ b/ui/ui-test/api/current.txt
@@ -35,6 +35,7 @@
     method public static void assertPixels(android.graphics.Bitmap, androidx.ui.unit.IntPxSize? expectedSize = null, kotlin.jvm.functions.Function1<? super androidx.ui.unit.IntPxPosition,androidx.ui.graphics.Color> expectedColorProvider);
     method public static void assertShape(android.graphics.Bitmap, androidx.ui.unit.Density density, androidx.ui.graphics.Shape shape, androidx.ui.graphics.Color shapeColor, androidx.ui.graphics.Color backgroundColor, androidx.ui.graphics.Shape backgroundShape = RectangleShape, androidx.ui.unit.Px sizeX = width.toFloat().px, androidx.ui.unit.Px sizeY = height.toFloat().px, androidx.ui.unit.Px shapeSizeX = sizeX, androidx.ui.unit.Px shapeSizeY = sizeY, androidx.ui.unit.Px centerX = width.px / 2.0, androidx.ui.unit.Px centerY = height.px / 2.0, androidx.ui.unit.Px shapeOverlapPixelCount = 1.px);
     method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(androidx.ui.test.SemanticsNodeInteraction);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(android.view.View);
   }
 
   public final class CollectedSizes {
diff --git a/ui/ui-test/api/public_plus_experimental_0.1.0-dev06.txt b/ui/ui-test/api/public_plus_experimental_0.1.0-dev06.txt
index 9b5a9a2..1944548 100644
--- a/ui/ui-test/api/public_plus_experimental_0.1.0-dev06.txt
+++ b/ui/ui-test/api/public_plus_experimental_0.1.0-dev06.txt
@@ -35,6 +35,7 @@
     method public static void assertPixels(android.graphics.Bitmap, androidx.ui.unit.IntPxSize? expectedSize = null, kotlin.jvm.functions.Function1<? super androidx.ui.unit.IntPxPosition,androidx.ui.graphics.Color> expectedColorProvider);
     method public static void assertShape(android.graphics.Bitmap, androidx.ui.unit.Density density, androidx.ui.graphics.Shape shape, androidx.ui.graphics.Color shapeColor, androidx.ui.graphics.Color backgroundColor, androidx.ui.graphics.Shape backgroundShape = RectangleShape, androidx.ui.unit.Px sizeX = width.toFloat().px, androidx.ui.unit.Px sizeY = height.toFloat().px, androidx.ui.unit.Px shapeSizeX = sizeX, androidx.ui.unit.Px shapeSizeY = sizeY, androidx.ui.unit.Px centerX = width.px / 2.0, androidx.ui.unit.Px centerY = height.px / 2.0, androidx.ui.unit.Px shapeOverlapPixelCount = 1.px);
     method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(androidx.ui.test.SemanticsNodeInteraction);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(android.view.View);
   }
 
   public final class CollectedSizes {
diff --git a/ui/ui-test/api/public_plus_experimental_current.txt b/ui/ui-test/api/public_plus_experimental_current.txt
index 9b5a9a2..1944548 100644
--- a/ui/ui-test/api/public_plus_experimental_current.txt
+++ b/ui/ui-test/api/public_plus_experimental_current.txt
@@ -35,6 +35,7 @@
     method public static void assertPixels(android.graphics.Bitmap, androidx.ui.unit.IntPxSize? expectedSize = null, kotlin.jvm.functions.Function1<? super androidx.ui.unit.IntPxPosition,androidx.ui.graphics.Color> expectedColorProvider);
     method public static void assertShape(android.graphics.Bitmap, androidx.ui.unit.Density density, androidx.ui.graphics.Shape shape, androidx.ui.graphics.Color shapeColor, androidx.ui.graphics.Color backgroundColor, androidx.ui.graphics.Shape backgroundShape = RectangleShape, androidx.ui.unit.Px sizeX = width.toFloat().px, androidx.ui.unit.Px sizeY = height.toFloat().px, androidx.ui.unit.Px shapeSizeX = sizeX, androidx.ui.unit.Px shapeSizeY = sizeY, androidx.ui.unit.Px centerX = width.px / 2.0, androidx.ui.unit.Px centerY = height.px / 2.0, androidx.ui.unit.Px shapeOverlapPixelCount = 1.px);
     method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(androidx.ui.test.SemanticsNodeInteraction);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(android.view.View);
   }
 
   public final class CollectedSizes {
diff --git a/ui/ui-test/api/restricted_0.1.0-dev06.txt b/ui/ui-test/api/restricted_0.1.0-dev06.txt
index 9b5a9a2..1944548 100644
--- a/ui/ui-test/api/restricted_0.1.0-dev06.txt
+++ b/ui/ui-test/api/restricted_0.1.0-dev06.txt
@@ -35,6 +35,7 @@
     method public static void assertPixels(android.graphics.Bitmap, androidx.ui.unit.IntPxSize? expectedSize = null, kotlin.jvm.functions.Function1<? super androidx.ui.unit.IntPxPosition,androidx.ui.graphics.Color> expectedColorProvider);
     method public static void assertShape(android.graphics.Bitmap, androidx.ui.unit.Density density, androidx.ui.graphics.Shape shape, androidx.ui.graphics.Color shapeColor, androidx.ui.graphics.Color backgroundColor, androidx.ui.graphics.Shape backgroundShape = RectangleShape, androidx.ui.unit.Px sizeX = width.toFloat().px, androidx.ui.unit.Px sizeY = height.toFloat().px, androidx.ui.unit.Px shapeSizeX = sizeX, androidx.ui.unit.Px shapeSizeY = sizeY, androidx.ui.unit.Px centerX = width.px / 2.0, androidx.ui.unit.Px centerY = height.px / 2.0, androidx.ui.unit.Px shapeOverlapPixelCount = 1.px);
     method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(androidx.ui.test.SemanticsNodeInteraction);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(android.view.View);
   }
 
   public final class CollectedSizes {
diff --git a/ui/ui-test/api/restricted_current.txt b/ui/ui-test/api/restricted_current.txt
index 9b5a9a2..1944548 100644
--- a/ui/ui-test/api/restricted_current.txt
+++ b/ui/ui-test/api/restricted_current.txt
@@ -35,6 +35,7 @@
     method public static void assertPixels(android.graphics.Bitmap, androidx.ui.unit.IntPxSize? expectedSize = null, kotlin.jvm.functions.Function1<? super androidx.ui.unit.IntPxPosition,androidx.ui.graphics.Color> expectedColorProvider);
     method public static void assertShape(android.graphics.Bitmap, androidx.ui.unit.Density density, androidx.ui.graphics.Shape shape, androidx.ui.graphics.Color shapeColor, androidx.ui.graphics.Color backgroundColor, androidx.ui.graphics.Shape backgroundShape = RectangleShape, androidx.ui.unit.Px sizeX = width.toFloat().px, androidx.ui.unit.Px sizeY = height.toFloat().px, androidx.ui.unit.Px shapeSizeX = sizeX, androidx.ui.unit.Px shapeSizeY = sizeY, androidx.ui.unit.Px centerX = width.px / 2.0, androidx.ui.unit.Px centerY = height.px / 2.0, androidx.ui.unit.Px shapeOverlapPixelCount = 1.px);
     method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(androidx.ui.test.SemanticsNodeInteraction);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static android.graphics.Bitmap captureToBitmap(android.view.View);
   }
 
   public final class CollectedSizes {
diff --git a/ui/ui-test/src/main/java/androidx/ui/test/BitmapHelpers.kt b/ui/ui-test/src/main/java/androidx/ui/test/BitmapHelpers.kt
index 5a276b1..98e08a4 100644
--- a/ui/ui-test/src/main/java/androidx/ui/test/BitmapHelpers.kt
+++ b/ui/ui-test/src/main/java/androidx/ui/test/BitmapHelpers.kt
@@ -16,15 +16,21 @@
 
 package androidx.ui.test
 
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
 import android.graphics.Bitmap
 import android.os.Build
+import android.view.View
 import androidx.annotation.RequiresApi
 import androidx.ui.foundation.shape.RectangleShape
 import androidx.ui.geometry.Offset
+import androidx.ui.geometry.Rect
 import androidx.ui.graphics.Color
 import androidx.ui.graphics.Path
 import androidx.ui.graphics.Shape
 import androidx.ui.graphics.addOutline
+import androidx.ui.test.android.captureRegionToBitmap
 import androidx.ui.unit.Density
 import androidx.ui.unit.IntPxPosition
 import androidx.ui.unit.IntPxSize
@@ -50,6 +56,33 @@
 }
 
 /**
+ * Captures the underlying view's surface into bitmap.
+ *
+ * This has currently several limitations. Currently we assume that the view is hosted in
+ * Activity's window. Also if there is another window covering part of the component if won't occur
+ * in the bitmap as this is taken from the component's window surface.
+ */
+@RequiresApi(Build.VERSION_CODES.O)
+fun View.captureToBitmap(): Bitmap {
+    val locationOnScreen = intArrayOf(0, 0)
+    getLocationOnScreen(locationOnScreen)
+    val x = locationOnScreen[0].toFloat()
+    val y = locationOnScreen[1].toFloat()
+    val bounds = Rect(x, y, x + width, y + height)
+
+    // Recursively search for the Activity context through (possible) ContextWrappers
+    fun Context.getActivity(): Activity? {
+        return when (this) {
+            is Activity -> this
+            is ContextWrapper -> this.baseContext.getActivity()
+            else -> null
+        }
+    }
+
+    return captureRegionToBitmap(bounds, handler, context.getActivity()!!.window)
+}
+
+/**
  * A helper function to run asserts on [Bitmap].
  *
  * @param expectedSize The expected size of the bitmap. Leave null to skip the check.