Merge changes from topic "magnifier_sync_movement"
* changes: [Magnifier-24] Add completion callback TestApi [Magnifier-21] Rate-limit drawings to renderer [Magnifier-20] Raw Surface instead of PopupWindow
This commit is contained in:
@@ -1097,6 +1097,11 @@ package android.widget {
|
||||
method public android.graphics.Bitmap getContent();
|
||||
method public static android.graphics.PointF getMagnifierDefaultSize();
|
||||
method public android.graphics.Rect getWindowPositionOnScreen();
|
||||
method public void setOnOperationCompleteCallback(android.widget.Magnifier.Callback);
|
||||
}
|
||||
|
||||
public static abstract interface Magnifier.Callback {
|
||||
method public abstract void onOperationComplete();
|
||||
}
|
||||
|
||||
public class NumberPicker extends android.widget.LinearLayout {
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.os.Trace;
|
||||
import android.util.Log;
|
||||
import android.view.Surface.OutOfResourcesException;
|
||||
import android.view.View.AttachInfo;
|
||||
import android.view.animation.AnimationUtils;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.util.VirtualRefBasePtr;
|
||||
@@ -943,6 +944,109 @@ public final class ThreadedRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic synchronous renderer. Currently only used to render the Magnifier, so use with care.
|
||||
* TODO: deduplicate against ThreadedRenderer.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static class SimpleRenderer {
|
||||
private final RenderNode mRootNode;
|
||||
private long mNativeProxy;
|
||||
private final float mLightY, mLightZ;
|
||||
private Surface mSurface;
|
||||
private final FrameInfo mFrameInfo = new FrameInfo();
|
||||
|
||||
public SimpleRenderer(final Context context, final String name, final Surface surface) {
|
||||
final TypedArray a = context.obtainStyledAttributes(null, R.styleable.Lighting, 0, 0);
|
||||
mLightY = a.getDimension(R.styleable.Lighting_lightY, 0);
|
||||
mLightZ = a.getDimension(R.styleable.Lighting_lightZ, 0);
|
||||
final float lightRadius = a.getDimension(R.styleable.Lighting_lightRadius, 0);
|
||||
final int ambientShadowAlpha =
|
||||
(int) (255 * a.getFloat(R.styleable.Lighting_ambientShadowAlpha, 0) + 0.5f);
|
||||
final int spotShadowAlpha =
|
||||
(int) (255 * a.getFloat(R.styleable.Lighting_spotShadowAlpha, 0) + 0.5f);
|
||||
a.recycle();
|
||||
|
||||
final long rootNodePtr = nCreateRootRenderNode();
|
||||
mRootNode = RenderNode.adopt(rootNodePtr);
|
||||
mRootNode.setClipToBounds(false);
|
||||
mNativeProxy = nCreateProxy(true /* translucent */, rootNodePtr);
|
||||
nSetName(mNativeProxy, name);
|
||||
|
||||
ProcessInitializer.sInstance.init(context, mNativeProxy);
|
||||
nLoadSystemProperties(mNativeProxy);
|
||||
|
||||
nSetup(mNativeProxy, lightRadius, ambientShadowAlpha, spotShadowAlpha);
|
||||
|
||||
mSurface = surface;
|
||||
nUpdateSurface(mNativeProxy, surface);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the light center.
|
||||
*/
|
||||
public void setLightCenter(final Display display,
|
||||
final int windowLeft, final int windowTop) {
|
||||
// Adjust light position for window offsets.
|
||||
final Point displaySize = new Point();
|
||||
display.getRealSize(displaySize);
|
||||
final float lightX = displaySize.x / 2f - windowLeft;
|
||||
final float lightY = mLightY - windowTop;
|
||||
|
||||
nSetLightCenter(mNativeProxy, lightX, lightY, mLightZ);
|
||||
}
|
||||
|
||||
public RenderNode getRootNode() {
|
||||
return mRootNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw the surface.
|
||||
*/
|
||||
public void draw(final FrameDrawingCallback callback) {
|
||||
final long vsync = AnimationUtils.currentAnimationTimeMillis() * 1000000L;
|
||||
mFrameInfo.setVsync(vsync, vsync);
|
||||
mFrameInfo.addFlags(1 << 2 /* VSYNC */);
|
||||
// TODO: remove this fence
|
||||
nFence(mNativeProxy);
|
||||
if (callback != null) {
|
||||
callback.onFrameDraw(mSurface.getNextFrameNumber());
|
||||
}
|
||||
nSyncAndDrawFrame(mNativeProxy, mFrameInfo.mFrameInfo, mFrameInfo.mFrameInfo.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy the renderer.
|
||||
*/
|
||||
public void destroy() {
|
||||
mSurface = null;
|
||||
nDestroy(mNativeProxy, mRootNode.mNativeRenderNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
try {
|
||||
nDeleteProxy(mNativeProxy);
|
||||
mNativeProxy = 0;
|
||||
} finally {
|
||||
super.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface used to receive callbacks when a frame is being drawn.
|
||||
*/
|
||||
public interface FrameDrawingCallback {
|
||||
/**
|
||||
* Invoked during a frame drawing.
|
||||
*
|
||||
* @param frame The id of the frame being drawn.
|
||||
*/
|
||||
void onFrameDraw(long frame);
|
||||
}
|
||||
|
||||
private static class ProcessInitializer {
|
||||
static ProcessInitializer sInstance = new ProcessInitializer();
|
||||
|
||||
|
||||
@@ -18,21 +18,32 @@ package android.widget;
|
||||
|
||||
import android.annotation.FloatRange;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.annotation.UiThread;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.view.Gravity;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Message;
|
||||
import android.view.Display;
|
||||
import android.view.DisplayListCanvas;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.PixelCopy;
|
||||
import android.view.RenderNode;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceSession;
|
||||
import android.view.SurfaceView;
|
||||
import android.view.ThreadedRenderer;
|
||||
import android.view.View;
|
||||
import android.view.ViewParent;
|
||||
import android.view.ViewRootImpl;
|
||||
@@ -46,32 +57,37 @@ import com.android.internal.util.Preconditions;
|
||||
public final class Magnifier {
|
||||
// Use this to specify that a previous configuration value does not exist.
|
||||
private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
|
||||
// The callbacks of the pixel copy requests will be invoked on
|
||||
// the Handler of this Thread when the copy is finished.
|
||||
private static final HandlerThread sPixelCopyHandlerThread =
|
||||
new HandlerThread("magnifier pixel copy result handler");
|
||||
|
||||
// The view to which this magnifier is attached.
|
||||
private final View mView;
|
||||
// The coordinates of the view in the surface.
|
||||
private final int[] mViewCoordinatesInSurface;
|
||||
// The window containing the magnifier.
|
||||
private final PopupWindow mWindow;
|
||||
private InternalPopupWindow mWindow;
|
||||
// The center coordinates of the window containing the magnifier.
|
||||
private final Point mWindowCoords = new Point();
|
||||
// The width of the window containing the magnifier.
|
||||
private final int mWindowWidth;
|
||||
// The height of the window containing the magnifier.
|
||||
private final int mWindowHeight;
|
||||
// The bitmap used to display the contents of the magnifier.
|
||||
private final Bitmap mBitmap;
|
||||
// The width of the bitmaps where the magnifier content is copied.
|
||||
private final int mBitmapWidth;
|
||||
// The height of the bitmaps where the magnifier content is copied.
|
||||
private final int mBitmapHeight;
|
||||
// The elevation of the window containing the magnifier.
|
||||
private final float mWindowElevation;
|
||||
// The center coordinates of the content that is to be magnified.
|
||||
private final Point mCenterZoomCoords = new Point();
|
||||
// The callback of the pixel copy request will be invoked on this Handler when
|
||||
// the copy is finished.
|
||||
private final Handler mPixelCopyHandler = Handler.getMain();
|
||||
// Current magnification scale.
|
||||
private final float mZoomScale;
|
||||
// Variables holding previous states, used for detecting redundant calls and invalidation.
|
||||
private final Point mPrevStartCoordsInSurface = new Point(
|
||||
NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
|
||||
private final PointF mPrevPosInView = new PointF(
|
||||
NONEXISTENT_PREVIOUS_CONFIG_VALUE, NONEXISTENT_PREVIOUS_CONFIG_VALUE);
|
||||
// Rectangle defining the view surface area we pixel copy content from.
|
||||
private final Rect mPixelCopyRequestRect = new Rect();
|
||||
|
||||
/**
|
||||
@@ -82,8 +98,6 @@ public final class Magnifier {
|
||||
public Magnifier(@NonNull View view) {
|
||||
mView = Preconditions.checkNotNull(view);
|
||||
final Context context = mView.getContext();
|
||||
final float elevation = context.getResources().getDimension(
|
||||
com.android.internal.R.dimen.magnifier_elevation);
|
||||
final View content = LayoutInflater.from(context).inflate(
|
||||
com.android.internal.R.layout.magnifier, null);
|
||||
content.findViewById(com.android.internal.R.id.magnifier_inner).setClipToOutline(true);
|
||||
@@ -91,23 +105,18 @@ public final class Magnifier {
|
||||
com.android.internal.R.dimen.magnifier_width);
|
||||
mWindowHeight = context.getResources().getDimensionPixelSize(
|
||||
com.android.internal.R.dimen.magnifier_height);
|
||||
mZoomScale = context.getResources().getFloat(
|
||||
mWindowElevation = context.getResources().getDimension(
|
||||
com.android.internal.R.dimen.magnifier_elevation);
|
||||
final float zoomScale = context.getResources().getFloat(
|
||||
com.android.internal.R.dimen.magnifier_zoom_scale);
|
||||
mBitmapWidth = Math.round(mWindowWidth / zoomScale);
|
||||
mBitmapHeight = Math.round(mWindowHeight / zoomScale);
|
||||
// The view's surface coordinates will not be updated until the magnifier is first shown.
|
||||
mViewCoordinatesInSurface = new int[2];
|
||||
}
|
||||
|
||||
mWindow = new PopupWindow(context);
|
||||
mWindow.setContentView(content);
|
||||
mWindow.setWidth(mWindowWidth);
|
||||
mWindow.setHeight(mWindowHeight);
|
||||
mWindow.setElevation(elevation);
|
||||
mWindow.setTouchable(false);
|
||||
mWindow.setBackgroundDrawable(null);
|
||||
|
||||
final int bitmapWidth = Math.round(mWindowWidth / mZoomScale);
|
||||
final int bitmapHeight = Math.round(mWindowHeight / mZoomScale);
|
||||
mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
|
||||
getImageView().setImageBitmap(mBitmap);
|
||||
static {
|
||||
sPixelCopyHandlerThread.start();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,30 +164,45 @@ public final class Magnifier {
|
||||
}
|
||||
|
||||
final int startX = Math.max(zeroScrollXInSurface, Math.min(
|
||||
mCenterZoomCoords.x - mBitmap.getWidth() / 2,
|
||||
zeroScrollXInSurface + actualWidth - mBitmap.getWidth()));
|
||||
final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
|
||||
mCenterZoomCoords.x - mBitmapWidth / 2,
|
||||
zeroScrollXInSurface + actualWidth - mBitmapWidth));
|
||||
final int startY = mCenterZoomCoords.y - mBitmapHeight / 2;
|
||||
|
||||
if (xPosInView != mPrevPosInView.x || yPosInView != mPrevPosInView.y) {
|
||||
performPixelCopy(startX, startY);
|
||||
|
||||
if (mWindow == null) {
|
||||
mWindow = new InternalPopupWindow(mView.getContext(), mView.getDisplay(),
|
||||
getValidViewSurface(), mWindowWidth, mWindowHeight, mWindowElevation,
|
||||
Handler.getMain() /* draw the magnifier on the UI thread */, mCallback);
|
||||
}
|
||||
performPixelCopy(startX, startY, true /* update window position */);
|
||||
mPrevPosInView.x = xPosInView;
|
||||
mPrevPosInView.y = yPosInView;
|
||||
|
||||
if (mWindow.isShowing()) {
|
||||
mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
|
||||
mWindow.getHeight());
|
||||
} else {
|
||||
mWindow.showAtLocation(mView, Gravity.NO_GRAVITY, mWindowCoords.x, mWindowCoords.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Surface getValidViewSurface() {
|
||||
// TODO: deduplicate this against the first part of #performPixelCopy
|
||||
final Surface surface;
|
||||
if (mView instanceof SurfaceView) {
|
||||
surface = ((SurfaceView) mView).getHolder().getSurface();
|
||||
} else if (mView.getViewRootImpl() != null) {
|
||||
surface = mView.getViewRootImpl().mSurface;
|
||||
} else {
|
||||
surface = null;
|
||||
}
|
||||
|
||||
return (surface != null && surface.isValid()) ? surface : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismisses the magnifier from the screen. Calling this on a dismissed magnifier is a no-op.
|
||||
*/
|
||||
public void dismiss() {
|
||||
mWindow.dismiss();
|
||||
if (mWindow != null) {
|
||||
mWindow.destroy();
|
||||
mWindow = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,43 +210,40 @@ public final class Magnifier {
|
||||
* {@link #show(float, float)}. This only happens if the magnifier is currently showing.
|
||||
*/
|
||||
public void update() {
|
||||
if (mWindow.isShowing()) {
|
||||
// Update the contents shown in the magnifier.
|
||||
performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y);
|
||||
if (mWindow != null) {
|
||||
// Update the content shown in the magnifier.
|
||||
performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y,
|
||||
false /* update window position */);
|
||||
}
|
||||
}
|
||||
|
||||
private void configureCoordinates(final float xPosInView, final float yPosInView) {
|
||||
// Compute the coordinates of the center of the content going to be displayed in the
|
||||
// magnifier. These are relative to the surface the content is copied from.
|
||||
final float contentPosX;
|
||||
final float contentPosY;
|
||||
final float posX;
|
||||
final float posY;
|
||||
if (mView instanceof SurfaceView) {
|
||||
// No offset required if the backing Surface matches the size of the SurfaceView.
|
||||
contentPosX = xPosInView;
|
||||
contentPosY = yPosInView;
|
||||
posX = xPosInView;
|
||||
posY = yPosInView;
|
||||
} else {
|
||||
mView.getLocationInSurface(mViewCoordinatesInSurface);
|
||||
contentPosX = xPosInView + mViewCoordinatesInSurface[0];
|
||||
contentPosY = yPosInView + mViewCoordinatesInSurface[1];
|
||||
posX = xPosInView + mViewCoordinatesInSurface[0];
|
||||
posY = yPosInView + mViewCoordinatesInSurface[1];
|
||||
}
|
||||
mCenterZoomCoords.x = Math.round(contentPosX);
|
||||
mCenterZoomCoords.y = Math.round(contentPosY);
|
||||
mCenterZoomCoords.x = Math.round(posX);
|
||||
mCenterZoomCoords.y = Math.round(posY);
|
||||
|
||||
// Compute the position of the magnifier window. These have to be relative to the window
|
||||
// of the view the magnifier is attached to, as the magnifier popup is a panel window
|
||||
// attached to that window.
|
||||
final int[] viewCoordinatesInWindow = new int[2];
|
||||
mView.getLocationInWindow(viewCoordinatesInWindow);
|
||||
// Compute the position of the magnifier window. Again, this has to be relative to the
|
||||
// surface of the magnified view, as this surface is the parent of the magnifier surface.
|
||||
final int verticalOffset = mView.getContext().getResources().getDimensionPixelSize(
|
||||
com.android.internal.R.dimen.magnifier_offset);
|
||||
final float magnifierPosX = xPosInView + viewCoordinatesInWindow[0];
|
||||
final float magnifierPosY = yPosInView + viewCoordinatesInWindow[1] - verticalOffset;
|
||||
mWindowCoords.x = Math.round(magnifierPosX - mWindowWidth / 2);
|
||||
mWindowCoords.y = Math.round(magnifierPosY - mWindowHeight / 2);
|
||||
mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
|
||||
mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalOffset;
|
||||
}
|
||||
|
||||
private void performPixelCopy(final int startXInSurface, final int startYInSurface) {
|
||||
private void performPixelCopy(final int startXInSurface, final int startYInSurface,
|
||||
final boolean updateWindowPosition) {
|
||||
// Get the view surface where the content will be copied from.
|
||||
final Surface surface;
|
||||
final int surfaceWidth;
|
||||
@@ -256,20 +277,293 @@ public final class Magnifier {
|
||||
// Perform the pixel copy.
|
||||
mPixelCopyRequestRect.set(clampedStartXInSurface,
|
||||
clampedStartYInSurface,
|
||||
clampedStartXInSurface + mBitmap.getWidth(),
|
||||
clampedStartYInSurface + mBitmap.getHeight());
|
||||
PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap,
|
||||
clampedStartXInSurface + mBitmapWidth,
|
||||
clampedStartYInSurface + mBitmapHeight);
|
||||
final int windowCoordsX = mWindowCoords.x;
|
||||
final int windowCoordsY = mWindowCoords.y;
|
||||
|
||||
final Bitmap bitmap =
|
||||
Bitmap.createBitmap(mBitmapWidth, mBitmapHeight, Bitmap.Config.ARGB_8888);
|
||||
PixelCopy.request(surface, mPixelCopyRequestRect, bitmap,
|
||||
result -> {
|
||||
getImageView().invalidate();
|
||||
mPrevStartCoordsInSurface.x = startXInSurface;
|
||||
mPrevStartCoordsInSurface.y = startYInSurface;
|
||||
synchronized (mWindow.mLock) {
|
||||
if (mWindow != null) {
|
||||
if (updateWindowPosition) {
|
||||
// TODO: pull the position update outside #performPixelCopy
|
||||
mWindow.setContentPositionForNextDraw(windowCoordsX, windowCoordsY);
|
||||
}
|
||||
mWindow.updateContent(bitmap);
|
||||
}
|
||||
}
|
||||
},
|
||||
mPixelCopyHandler);
|
||||
sPixelCopyHandlerThread.getThreadHandler());
|
||||
mPrevStartCoordsInSurface.x = startXInSurface;
|
||||
mPrevStartCoordsInSurface.y = startYInSurface;
|
||||
}
|
||||
|
||||
private ImageView getImageView() {
|
||||
return mWindow.getContentView().findViewById(
|
||||
com.android.internal.R.id.magnifier_image);
|
||||
/**
|
||||
* Magnifier's own implementation of PopupWindow-similar floating window.
|
||||
* This exists to ensure frame-synchronization between window position updates and window
|
||||
* content updates. By using a PopupWindow, these events would happen in different frames,
|
||||
* producing a shakiness effect for the magnifier content.
|
||||
*/
|
||||
private static class InternalPopupWindow {
|
||||
// Display associated to the view the magnifier is attached to.
|
||||
private final Display mDisplay;
|
||||
// The size of the content of the magnifier.
|
||||
private final int mContentWidth;
|
||||
private final int mContentHeight;
|
||||
// The size of the allocated surface.
|
||||
private final int mSurfaceWidth;
|
||||
private final int mSurfaceHeight;
|
||||
// The insets of the content inside the allocated surface.
|
||||
private final int mOffsetX;
|
||||
private final int mOffsetY;
|
||||
// The surface we allocate for the magnifier content + shadow.
|
||||
private final SurfaceSession mSurfaceSession;
|
||||
private final SurfaceControl mSurfaceControl;
|
||||
private final Surface mSurface;
|
||||
// The renderer used for the allocated surface.
|
||||
private final ThreadedRenderer.SimpleRenderer mRenderer;
|
||||
// The RenderNode used to draw the magnifier content in the surface.
|
||||
private final RenderNode mBitmapRenderNode;
|
||||
// The job that will be post'd to apply the pending magnifier updates to the surface.
|
||||
private final Runnable mMagnifierUpdater;
|
||||
// The handler where the magnifier updater jobs will be post'd.
|
||||
private final Handler mHandler;
|
||||
// The callback to be run after the next draw. Only used for testing.
|
||||
private Callback mCallback;
|
||||
|
||||
// Members below describe the state of the magnifier. Reads/writes to them
|
||||
// have to be synchronized between the UI thread and the thread that handles
|
||||
// the pixel copy results. This is the purpose of mLock.
|
||||
private final Object mLock = new Object();
|
||||
// Whether a magnifier frame draw is currently pending in the UI thread queue.
|
||||
private boolean mFrameDrawScheduled;
|
||||
// The content bitmap.
|
||||
private Bitmap mBitmap;
|
||||
// Whether the next draw will be the first one for the current instance.
|
||||
private boolean mFirstDraw = true;
|
||||
// The window position in the parent surface. Might be applied during the next draw,
|
||||
// when mPendingWindowPositionUpdate is true.
|
||||
private int mWindowPositionX;
|
||||
private int mWindowPositionY;
|
||||
private boolean mPendingWindowPositionUpdate;
|
||||
|
||||
InternalPopupWindow(final Context context, final Display display,
|
||||
final Surface parentSurface,
|
||||
final int width, final int height, final float elevation,
|
||||
final Handler handler, final Callback callback) {
|
||||
mDisplay = display;
|
||||
mCallback = callback;
|
||||
|
||||
mContentWidth = width;
|
||||
mContentHeight = height;
|
||||
mOffsetX = (int) (0.1f * width);
|
||||
mOffsetY = (int) (0.1f * height);
|
||||
// Setup the surface we will use for drawing the content and shadow.
|
||||
mSurfaceWidth = mContentWidth + 2 * mOffsetX;
|
||||
mSurfaceHeight = mContentHeight + 2 * mOffsetY;
|
||||
mSurfaceSession = new SurfaceSession(parentSurface);
|
||||
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession)
|
||||
.setFormat(PixelFormat.TRANSLUCENT)
|
||||
.setSize(mSurfaceWidth, mSurfaceHeight)
|
||||
.setName("magnifier surface")
|
||||
.setFlags(SurfaceControl.HIDDEN)
|
||||
.build();
|
||||
mSurface = new Surface();
|
||||
mSurface.copyFrom(mSurfaceControl);
|
||||
|
||||
// Setup the RenderNode tree. The root has only one child, which contains the bitmap.
|
||||
mRenderer = new ThreadedRenderer.SimpleRenderer(
|
||||
context,
|
||||
"magnifier renderer",
|
||||
mSurface
|
||||
);
|
||||
mBitmapRenderNode = createRenderNodeForBitmap(
|
||||
"magnifier content",
|
||||
elevation
|
||||
);
|
||||
|
||||
final DisplayListCanvas canvas = mRenderer.getRootNode().start(width, height);
|
||||
try {
|
||||
canvas.insertReorderBarrier();
|
||||
canvas.drawRenderNode(mBitmapRenderNode);
|
||||
canvas.insertInorderBarrier();
|
||||
} finally {
|
||||
mRenderer.getRootNode().end(canvas);
|
||||
}
|
||||
|
||||
// Initialize the update job and the handler where this will be post'd.
|
||||
mHandler = handler;
|
||||
mMagnifierUpdater = this::doDraw;
|
||||
mFrameDrawScheduled = false;
|
||||
}
|
||||
|
||||
private RenderNode createRenderNodeForBitmap(final String name, final float elevation) {
|
||||
final RenderNode bitmapRenderNode = RenderNode.create(name, null);
|
||||
|
||||
// Define the position of the bitmap in the parent render node. The surface regions
|
||||
// outside the bitmap are used to draw elevation.
|
||||
bitmapRenderNode.setLeftTopRightBottom(mOffsetX, mOffsetY,
|
||||
mOffsetX + mContentWidth, mOffsetY + mContentHeight);
|
||||
bitmapRenderNode.setElevation(elevation);
|
||||
|
||||
final Outline outline = new Outline();
|
||||
outline.setRoundRect(0, 0, mContentWidth, mContentHeight, 3);
|
||||
outline.setAlpha(1.0f);
|
||||
bitmapRenderNode.setOutline(outline);
|
||||
bitmapRenderNode.setClipToOutline(true);
|
||||
|
||||
// Create a dummy draw, which will be replaced later with real drawing.
|
||||
final DisplayListCanvas canvas = bitmapRenderNode.start(mContentWidth, mContentHeight);
|
||||
try {
|
||||
canvas.drawColor(0xFF00FF00);
|
||||
} finally {
|
||||
bitmapRenderNode.end(canvas);
|
||||
}
|
||||
|
||||
return bitmapRenderNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the position of the magnifier content relative to the parent surface.
|
||||
* The position update will happen in the same frame with the next draw.
|
||||
* The method has to be called in a context that holds {@link #mLock}.
|
||||
*
|
||||
* @param contentX the x coordinate of the content
|
||||
* @param contentY the y coordinate of the content
|
||||
*/
|
||||
public void setContentPositionForNextDraw(final int contentX, final int contentY) {
|
||||
mWindowPositionX = contentX - mOffsetX;
|
||||
mWindowPositionY = contentY - mOffsetY;
|
||||
mPendingWindowPositionUpdate = true;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the content that should be displayed in the magnifier.
|
||||
* The update happens immediately, and possibly triggers a pending window movement set
|
||||
* by {@link #setContentPositionForNextDraw(int, int)}.
|
||||
* The method has to be called in a context that holds {@link #mLock}.
|
||||
*
|
||||
* @param bitmap the content bitmap
|
||||
*/
|
||||
public void updateContent(final @NonNull Bitmap bitmap) {
|
||||
if (mBitmap != null) {
|
||||
mBitmap.recycle();
|
||||
}
|
||||
mBitmap = bitmap;
|
||||
requestUpdate();
|
||||
}
|
||||
|
||||
private void requestUpdate() {
|
||||
if (mFrameDrawScheduled) {
|
||||
return;
|
||||
}
|
||||
final Message request = Message.obtain(mHandler, mMagnifierUpdater);
|
||||
request.setAsynchronous(true);
|
||||
request.sendToTarget();
|
||||
mFrameDrawScheduled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroys this instance.
|
||||
*/
|
||||
public void destroy() {
|
||||
mRenderer.destroy();
|
||||
mSurface.destroy();
|
||||
mSurfaceControl.destroy();
|
||||
mSurfaceSession.kill();
|
||||
mBitmapRenderNode.destroy();
|
||||
synchronized (mLock) {
|
||||
mHandler.removeCallbacks(mMagnifierUpdater);
|
||||
if (mBitmap != null) {
|
||||
mBitmap.recycle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void doDraw() {
|
||||
final ThreadedRenderer.FrameDrawingCallback callback;
|
||||
|
||||
// Draw the current bitmap to the surface, and prepare the callback which updates the
|
||||
// surface position. These have to be in the same synchronized block, in order to
|
||||
// guarantee the consistency between the bitmap content and the surface position.
|
||||
synchronized (mLock) {
|
||||
if (!mSurface.isValid()) {
|
||||
// Probably #destroy() was called for the current instance, so we skip the draw.
|
||||
return;
|
||||
}
|
||||
|
||||
final DisplayListCanvas canvas =
|
||||
mBitmapRenderNode.start(mContentWidth, mContentHeight);
|
||||
try {
|
||||
final Rect srcRect = new Rect(0, 0, mBitmap.getWidth(), mBitmap.getHeight());
|
||||
final Rect dstRect = new Rect(0, 0, mContentWidth, mContentHeight);
|
||||
final Paint paint = new Paint();
|
||||
paint.setFilterBitmap(true);
|
||||
canvas.drawBitmap(mBitmap, srcRect, dstRect, paint);
|
||||
} finally {
|
||||
mBitmapRenderNode.end(canvas);
|
||||
}
|
||||
|
||||
if (mPendingWindowPositionUpdate || mFirstDraw) {
|
||||
// If the window has to be shown or moved, defer this until the next draw.
|
||||
final boolean firstDraw = mFirstDraw;
|
||||
mFirstDraw = false;
|
||||
final boolean updateWindowPosition = mPendingWindowPositionUpdate;
|
||||
mPendingWindowPositionUpdate = false;
|
||||
final int pendingX = mWindowPositionX;
|
||||
final int pendingY = mWindowPositionY;
|
||||
|
||||
callback = frame -> {
|
||||
mRenderer.setLightCenter(mDisplay, pendingX, pendingY);
|
||||
// Show or move the window at the content draw frame.
|
||||
SurfaceControl.openTransaction();
|
||||
mSurfaceControl.deferTransactionUntil(mSurface, frame);
|
||||
if (updateWindowPosition) {
|
||||
mSurfaceControl.setPosition(pendingX, pendingY);
|
||||
}
|
||||
if (firstDraw) {
|
||||
mSurfaceControl.show();
|
||||
}
|
||||
SurfaceControl.closeTransaction();
|
||||
};
|
||||
} else {
|
||||
callback = null;
|
||||
}
|
||||
|
||||
mFrameDrawScheduled = false;
|
||||
}
|
||||
|
||||
mRenderer.draw(callback);
|
||||
if (mCallback != null) {
|
||||
mCallback.onOperationComplete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The rest of the file consists of test APIs.
|
||||
|
||||
/**
|
||||
* See {@link #setOnOperationCompleteCallback(Callback)}.
|
||||
*/
|
||||
@TestApi
|
||||
private Callback mCallback;
|
||||
|
||||
/**
|
||||
* Sets a callback which will be invoked at the end of the next
|
||||
* {@link #show(float, float)} or {@link #update()} operation.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public void setOnOperationCompleteCallback(final Callback callback) {
|
||||
mCallback = callback;
|
||||
if (mWindow != null) {
|
||||
mWindow.mCallback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,8 +572,13 @@ public final class Magnifier {
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public Bitmap getContent() {
|
||||
return mBitmap;
|
||||
public @Nullable Bitmap getContent() {
|
||||
if (mWindow == null) {
|
||||
return null;
|
||||
}
|
||||
synchronized (mWindow.mLock) {
|
||||
return mWindow.mBitmap;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,7 +595,7 @@ public final class Magnifier {
|
||||
|
||||
final int left = mWindowCoords.x + viewLocationOnScreen[0] - viewLocationInSurface[0];
|
||||
final int top = mWindowCoords.y + viewLocationOnScreen[1] - viewLocationInSurface[1];
|
||||
return new Rect(left, top, left + mWindow.getWidth(), top + mWindow.getHeight());
|
||||
return new Rect(left, top, left + mWindowWidth, top + mWindowHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -313,4 +612,15 @@ public final class Magnifier {
|
||||
size.y = resources.getDimension(com.android.internal.R.dimen.magnifier_height) / density;
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@TestApi
|
||||
public interface Callback {
|
||||
/**
|
||||
* Callback called after the drawing for a magnifier update has happened.
|
||||
*/
|
||||
void onOperationComplete();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user