Loading core/java/android/view/View.java +35 −58 Original line number Diff line number Diff line Loading @@ -18967,7 +18967,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) { public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) { int width = mRight - mLeft; int height = mBottom - mTop; Loading @@ -18976,37 +18976,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, width = (int) ((width * scale) + 0.5f); height = (int) ((height * scale) + 0.5f); Bitmap bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), width > 0 ? width : 1, height > 0 ? height : 1, quality); if (bitmap == null) { throw new OutOfMemoryError(); } Resources resources = getResources(); if (resources != null) { bitmap.setDensity(resources.getDisplayMetrics().densityDpi); } Canvas oldCanvas = null; try { Canvas canvas = canvasProvider.getCanvas(this, width > 0 ? width : 1, height > 0 ? height : 1); Canvas canvas; if (attachInfo != null) { canvas = attachInfo.mCanvas; if (canvas == null) { canvas = new Canvas(); } canvas.setBitmap(bitmap); oldCanvas = attachInfo.mCanvas; // Temporarily clobber the cached Canvas in case one of our children // is also using a drawing cache. Without this, the children would // steal the canvas by attaching their own bitmap to it and bad, bad // things would happen (invisible views, corrupted drawings, etc.) attachInfo.mCanvas = null; } else { // This case should hopefully never or seldom happen canvas = new Canvas(bitmap); } boolean enabledHwBitmapsInSwMode = canvas.isHwBitmapsInSwModeEnabled(); canvas.setHwBitmapsInSwModeEnabled(true); if ((backgroundColor & 0xff000000) != 0) { bitmap.eraseColor(backgroundColor); } computeScroll(); Loading @@ -19030,17 +19011,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mPrivateFlags = flags; canvas.restoreToCount(restoreCount); canvas.setBitmap(null); canvas.setHwBitmapsInSwModeEnabled(enabledHwBitmapsInSwMode); if (attachInfo != null) { // Restore the cached Canvas for our siblings attachInfo.mCanvas = canvas; return canvasProvider.createBitmap(); } finally { if (oldCanvas != null) { attachInfo.mCanvas = oldCanvas; } } return bitmap; } /** core/java/android/view/ViewDebug.java +92 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.os.Debug; import android.os.Handler; Loading Loading @@ -773,17 +774,16 @@ public class ViewDebug { final CountDownLatch latch = new CountDownLatch(1); final Bitmap[] cache = new Bitmap[1]; captureView.post(new Runnable() { public void run() { captureView.post(() -> { try { cache[0] = captureView.createSnapshot( Bitmap.Config.ARGB_8888, 0, skipChildren); CanvasProvider provider = captureView.isHardwareAccelerated() ? new HardwareCanvasProvider() : new SoftwareCanvasProvider(); cache[0] = captureView.createSnapshot(provider, skipChildren); } catch (OutOfMemoryError e) { Log.w("View", "Out of memory for bitmap"); } finally { latch.countDown(); } } }); try { Loading Loading @@ -1740,4 +1740,86 @@ public class ViewDebug { } }); } /** * @hide */ public static class SoftwareCanvasProvider implements CanvasProvider { private Canvas mCanvas; private Bitmap mBitmap; private boolean mEnabledHwBitmapsInSwMode; @Override public Canvas getCanvas(View view, int width, int height) { mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(), width, height, Bitmap.Config.ARGB_8888); if (mBitmap == null) { throw new OutOfMemoryError(); } mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi); if (view.mAttachInfo != null) { mCanvas = view.mAttachInfo.mCanvas; } if (mCanvas == null) { mCanvas = new Canvas(); } mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled(); mCanvas.setBitmap(mBitmap); return mCanvas; } @Override public Bitmap createBitmap() { mCanvas.setBitmap(null); mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode); return mBitmap; } } /** * @hide */ public static class HardwareCanvasProvider implements CanvasProvider { private View mView; private Point mSize; private RenderNode mNode; private DisplayListCanvas mCanvas; @Override public Canvas getCanvas(View view, int width, int height) { mView = view; mSize = new Point(width, height); mNode = RenderNode.create("ViewDebug", mView); mNode.setLeftTopRightBottom(0, 0, width, height); mNode.setClipToBounds(false); mCanvas = mNode.start(width, height); return mCanvas; } @Override public Bitmap createBitmap() { mNode.end(mCanvas); return ThreadedRenderer.createHardwareBitmap(mNode, mSize.x, mSize.y); } } /** * @hide */ public interface CanvasProvider { /** * Returns a canvas which can be used to draw {@param view} */ Canvas getCanvas(View view, int width, int height); /** * Creates a bitmap from previously returned canvas * @return */ Bitmap createBitmap(); } } core/java/android/view/ViewGroup.java +10 −10 Original line number Diff line number Diff line Loading @@ -3863,7 +3863,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) { public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) { int count = mChildrenCount; int[] visibilities = null; Loading @@ -3879,8 +3879,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } Bitmap b = super.createSnapshot(quality, backgroundColor, skipChildren); try { return super.createSnapshot(canvasProvider, skipChildren); } finally { if (skipChildren) { for (int i = 0; i < count; i++) { View child = getChildAt(i); Loading @@ -3888,8 +3889,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager | (visibilities[i] & View.VISIBILITY_MASK); } } return b; } } /** Return true if this ViewGroup is laying out using optical bounds. */ Loading core/tests/coretests/src/android/view/ViewCaptureTest.java +29 −6 Original line number Diff line number Diff line Loading @@ -25,10 +25,14 @@ import android.support.test.filters.SmallTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.util.SparseIntArray; import android.view.ViewDebug.CanvasProvider; import android.view.ViewDebug.HardwareCanvasProvider; import android.view.ViewDebug.SoftwareCanvasProvider; import com.android.frameworks.coretests.R; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; Loading @@ -55,23 +59,42 @@ public class ViewCaptureTest { @Before public void setUp() throws Exception { mActivity = mActivityRule.getActivity(); mViewToCapture = (ViewGroup) mActivity.findViewById(R.id.capture); mViewToCapture = mActivity.findViewById(R.id.capture); } @Test @SmallTest public void testCreateSnapshot() { public void testCreateSnapshot_software() { assertChildrenVisibility(); testCreateSnapshot(true, R.drawable.view_capture_test_no_children_golden); testCreateSnapshot(new SoftwareCanvasProvider(), true, R.drawable.view_capture_test_no_children_golden); assertChildrenVisibility(); testCreateSnapshot(false, R.drawable.view_capture_test_with_children_golden); testCreateSnapshot(new SoftwareCanvasProvider(), false, R.drawable.view_capture_test_with_children_golden); assertChildrenVisibility(); } private void testCreateSnapshot(boolean skipChildren, int goldenResId) { Bitmap result = mViewToCapture.createSnapshot(Bitmap.Config.ARGB_8888, 0, skipChildren); @Test @SmallTest public void testCreateSnapshot_hardware() { Assume.assumeTrue(mViewToCapture.isHardwareAccelerated()); assertChildrenVisibility(); testCreateSnapshot(new HardwareCanvasProvider(), true, R.drawable.view_capture_test_no_children_golden); assertChildrenVisibility(); testCreateSnapshot(new HardwareCanvasProvider(), false, R.drawable.view_capture_test_with_children_golden); assertChildrenVisibility(); } private void testCreateSnapshot( CanvasProvider canvasProvider, boolean skipChildren, int goldenResId) { Bitmap result = mViewToCapture.createSnapshot(canvasProvider, skipChildren); result.setHasAlpha(false); // resource will have no alpha, since content is opaque Bitmap golden = BitmapFactory.decodeResource(mActivity.getResources(), goldenResId); // We dont care about the config of the bitmap, so convert to same config before comparing result = result.copy(golden.getConfig(), false); assertTrue(golden.sameAs(result)); } Loading Loading
core/java/android/view/View.java +35 −58 Original line number Diff line number Diff line Loading @@ -18967,7 +18967,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @hide */ public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) { public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) { int width = mRight - mLeft; int height = mBottom - mTop; Loading @@ -18976,37 +18976,18 @@ public class View implements Drawable.Callback, KeyEvent.Callback, width = (int) ((width * scale) + 0.5f); height = (int) ((height * scale) + 0.5f); Bitmap bitmap = Bitmap.createBitmap(mResources.getDisplayMetrics(), width > 0 ? width : 1, height > 0 ? height : 1, quality); if (bitmap == null) { throw new OutOfMemoryError(); } Resources resources = getResources(); if (resources != null) { bitmap.setDensity(resources.getDisplayMetrics().densityDpi); } Canvas oldCanvas = null; try { Canvas canvas = canvasProvider.getCanvas(this, width > 0 ? width : 1, height > 0 ? height : 1); Canvas canvas; if (attachInfo != null) { canvas = attachInfo.mCanvas; if (canvas == null) { canvas = new Canvas(); } canvas.setBitmap(bitmap); oldCanvas = attachInfo.mCanvas; // Temporarily clobber the cached Canvas in case one of our children // is also using a drawing cache. Without this, the children would // steal the canvas by attaching their own bitmap to it and bad, bad // things would happen (invisible views, corrupted drawings, etc.) attachInfo.mCanvas = null; } else { // This case should hopefully never or seldom happen canvas = new Canvas(bitmap); } boolean enabledHwBitmapsInSwMode = canvas.isHwBitmapsInSwModeEnabled(); canvas.setHwBitmapsInSwModeEnabled(true); if ((backgroundColor & 0xff000000) != 0) { bitmap.eraseColor(backgroundColor); } computeScroll(); Loading @@ -19030,17 +19011,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } mPrivateFlags = flags; canvas.restoreToCount(restoreCount); canvas.setBitmap(null); canvas.setHwBitmapsInSwModeEnabled(enabledHwBitmapsInSwMode); if (attachInfo != null) { // Restore the cached Canvas for our siblings attachInfo.mCanvas = canvas; return canvasProvider.createBitmap(); } finally { if (oldCanvas != null) { attachInfo.mCanvas = oldCanvas; } } return bitmap; } /**
core/java/android/view/ViewDebug.java +92 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; import android.os.Debug; import android.os.Handler; Loading Loading @@ -773,17 +774,16 @@ public class ViewDebug { final CountDownLatch latch = new CountDownLatch(1); final Bitmap[] cache = new Bitmap[1]; captureView.post(new Runnable() { public void run() { captureView.post(() -> { try { cache[0] = captureView.createSnapshot( Bitmap.Config.ARGB_8888, 0, skipChildren); CanvasProvider provider = captureView.isHardwareAccelerated() ? new HardwareCanvasProvider() : new SoftwareCanvasProvider(); cache[0] = captureView.createSnapshot(provider, skipChildren); } catch (OutOfMemoryError e) { Log.w("View", "Out of memory for bitmap"); } finally { latch.countDown(); } } }); try { Loading Loading @@ -1740,4 +1740,86 @@ public class ViewDebug { } }); } /** * @hide */ public static class SoftwareCanvasProvider implements CanvasProvider { private Canvas mCanvas; private Bitmap mBitmap; private boolean mEnabledHwBitmapsInSwMode; @Override public Canvas getCanvas(View view, int width, int height) { mBitmap = Bitmap.createBitmap(view.getResources().getDisplayMetrics(), width, height, Bitmap.Config.ARGB_8888); if (mBitmap == null) { throw new OutOfMemoryError(); } mBitmap.setDensity(view.getResources().getDisplayMetrics().densityDpi); if (view.mAttachInfo != null) { mCanvas = view.mAttachInfo.mCanvas; } if (mCanvas == null) { mCanvas = new Canvas(); } mEnabledHwBitmapsInSwMode = mCanvas.isHwBitmapsInSwModeEnabled(); mCanvas.setBitmap(mBitmap); return mCanvas; } @Override public Bitmap createBitmap() { mCanvas.setBitmap(null); mCanvas.setHwBitmapsInSwModeEnabled(mEnabledHwBitmapsInSwMode); return mBitmap; } } /** * @hide */ public static class HardwareCanvasProvider implements CanvasProvider { private View mView; private Point mSize; private RenderNode mNode; private DisplayListCanvas mCanvas; @Override public Canvas getCanvas(View view, int width, int height) { mView = view; mSize = new Point(width, height); mNode = RenderNode.create("ViewDebug", mView); mNode.setLeftTopRightBottom(0, 0, width, height); mNode.setClipToBounds(false); mCanvas = mNode.start(width, height); return mCanvas; } @Override public Bitmap createBitmap() { mNode.end(mCanvas); return ThreadedRenderer.createHardwareBitmap(mNode, mSize.x, mSize.y); } } /** * @hide */ public interface CanvasProvider { /** * Returns a canvas which can be used to draw {@param view} */ Canvas getCanvas(View view, int width, int height); /** * Creates a bitmap from previously returned canvas * @return */ Bitmap createBitmap(); } }
core/java/android/view/ViewGroup.java +10 −10 Original line number Diff line number Diff line Loading @@ -3863,7 +3863,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager * @hide */ @Override public Bitmap createSnapshot(Bitmap.Config quality, int backgroundColor, boolean skipChildren) { public Bitmap createSnapshot(ViewDebug.CanvasProvider canvasProvider, boolean skipChildren) { int count = mChildrenCount; int[] visibilities = null; Loading @@ -3879,8 +3879,9 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager } } Bitmap b = super.createSnapshot(quality, backgroundColor, skipChildren); try { return super.createSnapshot(canvasProvider, skipChildren); } finally { if (skipChildren) { for (int i = 0; i < count; i++) { View child = getChildAt(i); Loading @@ -3888,8 +3889,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager | (visibilities[i] & View.VISIBILITY_MASK); } } return b; } } /** Return true if this ViewGroup is laying out using optical bounds. */ Loading
core/tests/coretests/src/android/view/ViewCaptureTest.java +29 −6 Original line number Diff line number Diff line Loading @@ -25,10 +25,14 @@ import android.support.test.filters.SmallTest; import android.support.test.rule.ActivityTestRule; import android.support.test.runner.AndroidJUnit4; import android.util.SparseIntArray; import android.view.ViewDebug.CanvasProvider; import android.view.ViewDebug.HardwareCanvasProvider; import android.view.ViewDebug.SoftwareCanvasProvider; import com.android.frameworks.coretests.R; import org.junit.Assert; import org.junit.Assume; import org.junit.Before; import org.junit.Rule; import org.junit.Test; Loading @@ -55,23 +59,42 @@ public class ViewCaptureTest { @Before public void setUp() throws Exception { mActivity = mActivityRule.getActivity(); mViewToCapture = (ViewGroup) mActivity.findViewById(R.id.capture); mViewToCapture = mActivity.findViewById(R.id.capture); } @Test @SmallTest public void testCreateSnapshot() { public void testCreateSnapshot_software() { assertChildrenVisibility(); testCreateSnapshot(true, R.drawable.view_capture_test_no_children_golden); testCreateSnapshot(new SoftwareCanvasProvider(), true, R.drawable.view_capture_test_no_children_golden); assertChildrenVisibility(); testCreateSnapshot(false, R.drawable.view_capture_test_with_children_golden); testCreateSnapshot(new SoftwareCanvasProvider(), false, R.drawable.view_capture_test_with_children_golden); assertChildrenVisibility(); } private void testCreateSnapshot(boolean skipChildren, int goldenResId) { Bitmap result = mViewToCapture.createSnapshot(Bitmap.Config.ARGB_8888, 0, skipChildren); @Test @SmallTest public void testCreateSnapshot_hardware() { Assume.assumeTrue(mViewToCapture.isHardwareAccelerated()); assertChildrenVisibility(); testCreateSnapshot(new HardwareCanvasProvider(), true, R.drawable.view_capture_test_no_children_golden); assertChildrenVisibility(); testCreateSnapshot(new HardwareCanvasProvider(), false, R.drawable.view_capture_test_with_children_golden); assertChildrenVisibility(); } private void testCreateSnapshot( CanvasProvider canvasProvider, boolean skipChildren, int goldenResId) { Bitmap result = mViewToCapture.createSnapshot(canvasProvider, skipChildren); result.setHasAlpha(false); // resource will have no alpha, since content is opaque Bitmap golden = BitmapFactory.decodeResource(mActivity.getResources(), goldenResId); // We dont care about the config of the bitmap, so convert to same config before comparing result = result.copy(golden.getConfig(), false); assertTrue(golden.sameAs(result)); } Loading