From 17ea30584a579a442809f1070e9a67f33b1afb65 Mon Sep 17 00:00:00 2001 From: Mihai Popa Date: Tue, 6 Mar 2018 14:24:07 +0000 Subject: [PATCH 1/2] [Magnifier-29] Expose size and zoom in the API The CL exposes the size and the zoom of the magnifier in the public API. These are required for implementing a number of UX requests in WebView and Chrome - see the two bugs referenced. Also, the CL fixes a bug in the #getContent() TestApi, which was returning the bitmap before (instead of after) scaling. Bug: 70608551 Bug: 72314536 Test: atest CtsWidgetTestCases:android.widget.cts.MagnifierTest Change-Id: Idc583b923010d7dca075b05b6f4dbafa74cfec1f (cherry picked from commit e1b93ddcbd40334664d6360d60525981cf3a3981) Merged-In: Idc583b923010d7dca075b05b6f4dbafa74cfec1f --- api/current.txt | 3 ++ core/java/android/widget/Magnifier.java | 63 +++++++++++++++++-------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/api/current.txt b/api/current.txt index c2f1bc58bf610..e4a629c42937c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -53391,6 +53391,9 @@ package android.widget { public final class Magnifier { ctor public Magnifier(android.view.View); method public void dismiss(); + method public int getHeight(); + method public int getWidth(); + method public float getZoom(); method public void show(float, float); method public void update(); } diff --git a/core/java/android/widget/Magnifier.java b/core/java/android/widget/Magnifier.java index 85f68d7c29f40..3db149a442de6 100644 --- a/core/java/android/widget/Magnifier.java +++ b/core/java/android/widget/Magnifier.java @@ -75,6 +75,8 @@ public final class Magnifier { private final int mWindowWidth; // The height of the window containing the magnifier. private final int mWindowHeight; + // The zoom applied to the view region copied to the magnifier window. + private final float mZoom; // 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. @@ -111,10 +113,10 @@ public final class Magnifier { com.android.internal.R.dimen.magnifier_height); mWindowElevation = context.getResources().getDimension( com.android.internal.R.dimen.magnifier_elevation); - final float zoomScale = context.getResources().getFloat( + mZoom = context.getResources().getFloat( com.android.internal.R.dimen.magnifier_zoom_scale); - mBitmapWidth = Math.round(mWindowWidth / zoomScale); - mBitmapHeight = Math.round(mWindowHeight / zoomScale); + mBitmapWidth = Math.round(mWindowWidth / mZoom); + mBitmapHeight = Math.round(mWindowHeight / mZoom); // The view's surface coordinates will not be updated until the magnifier is first shown. mViewCoordinatesInSurface = new int[2]; } @@ -187,21 +189,6 @@ public final class Magnifier { } } - @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. */ @@ -226,6 +213,44 @@ public final class Magnifier { } } + /** + * @return The width of the magnifier window, in pixels. + */ + public int getWidth() { + return mWindowWidth; + } + + /** + * @return The height of the magnifier window, in pixels. + */ + public int getHeight() { + return mWindowHeight; + } + + /** + * @return The zoom applied to the magnified view region copied to the magnifier window. + * If the zoom is x and the magnifier window size is (width, height), the original size + * of the content copied in the magnifier will be (width / x, height / x). + */ + public float getZoom() { + return mZoom; + } + + @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; + } + 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. @@ -602,7 +627,7 @@ public final class Magnifier { return null; } synchronized (mWindow.mLock) { - return mWindow.mBitmap; + return Bitmap.createScaledBitmap(mWindow.mBitmap, mWindowWidth, mWindowHeight, true); } } From e301746a0e393bdb1a8042e36c368fb03c49ae71 Mon Sep 17 00:00:00 2001 From: Mihai Popa Date: Wed, 7 Mar 2018 12:25:21 +0000 Subject: [PATCH 2/2] [Magnifier-31] Do not magnify outside current line Currently, after the cursor is reaching the end of a line, the magnifier keeps following the finger even if the cursor cannot move anymore. This CL limits the movement of the magnifier, ensuring it stays between the bounds of the text line. Also, when the finger gets too far from the end of the line, we dismiss the magnifier. We consider it went too far when the cursor is not visible anymore inside the magnifier. Bug: 72314536 Test: manual testing (both English and Arabic) Test: atest FrameworksCoreTests:android.widget.TextViewActivityTest Test: atest CtsWidgetTestCases:android.widget.cts.TextViewTest Change-Id: I8dafba1fc8e7b8e482526e818831ece2ee20ac27 (cherry picked from commit dfc752bc745ca272234be41f2b54d49eccece84d) Merged-In: I8dafba1fc8e7b8e482526e818831ece2ee20ac27 --- core/java/android/widget/Editor.java | 65 ++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java index fe49c02d91452..27dd39b3224f8 100644 --- a/core/java/android/widget/Editor.java +++ b/core/java/android/widget/Editor.java @@ -37,6 +37,7 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Path; +import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.ColorDrawable; @@ -4648,10 +4649,14 @@ public class Editor { return 0; } - protected final void showMagnifier(@NonNull final MotionEvent event) { - if (mMagnifier == null) { - return; - } + /** + * Computes the position where the magnifier should be shown, relative to + * {@code mTextView}, and writes them to {@code showPosInView}. Also decides + * whether the magnifier should be shown or dismissed after this touch event. + * @return Whether the magnifier should be shown at the computed coordinates or dismissed. + */ + private boolean obtainMagnifierShowCoordinates(@NonNull final MotionEvent event, + final PointF showPosInView) { final int trigger = getMagnifierHandleTrigger(); final int offset; @@ -4669,26 +4674,52 @@ public class Editor { } if (offset == -1) { - dismissMagnifier(); + return false; } final Layout layout = mTextView.getLayout(); final int lineNumber = layout.getLineForOffset(offset); - // Horizontally move the magnifier smoothly. + + // Horizontally move the magnifier smoothly but clamp inside the current line. final int[] textViewLocationOnScreen = new int[2]; mTextView.getLocationOnScreen(textViewLocationOnScreen); - final float xPosInView = event.getRawX() - textViewLocationOnScreen[0]; + final float touchXInView = event.getRawX() - textViewLocationOnScreen[0]; + final float lineLeft = mTextView.getLayout().getLineLeft(lineNumber) + + mTextView.getTotalPaddingLeft() - mTextView.getScrollX(); + final float lineRight = mTextView.getLayout().getLineRight(lineNumber) + + mTextView.getTotalPaddingLeft() - mTextView.getScrollX(); + final float contentWidth = Math.round(mMagnifier.getWidth() / mMagnifier.getZoom()); + if (touchXInView < lineLeft - contentWidth / 2 + || touchXInView > lineRight + contentWidth / 2) { + // The touch is too out of the bounds of the current line, so hide the magnifier. + return false; + } + showPosInView.x = Math.max(lineLeft, Math.min(lineRight, touchXInView)); + // Vertically snap to middle of current line. - final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber) + showPosInView.y = (mTextView.getLayout().getLineTop(lineNumber) + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f + mTextView.getTotalPaddingTop() - mTextView.getScrollY(); - // Make the cursor visible and stop blinking. - mRenderCursorRegardlessTiming = true; - mTextView.invalidateCursorPath(); - suspendBlink(); + return true; + } - mMagnifier.show(xPosInView, yPosInView); + protected final void updateMagnifier(@NonNull final MotionEvent event) { + if (mMagnifier == null) { + return; + } + + final PointF showPosInView = new PointF(); + final boolean shouldShow = obtainMagnifierShowCoordinates(event, showPosInView); + if (shouldShow) { + // Make the cursor visible and stop blinking. + mRenderCursorRegardlessTiming = true; + mTextView.invalidateCursorPath(); + suspendBlink(); + mMagnifier.show(showPosInView.x, showPosInView.y); + } else { + dismissMagnifier(); + } } protected final void dismissMagnifier() { @@ -4877,11 +4908,11 @@ public class Editor { case MotionEvent.ACTION_DOWN: mDownPositionX = ev.getRawX(); mDownPositionY = ev.getRawY(); - showMagnifier(ev); + updateMagnifier(ev); break; case MotionEvent.ACTION_MOVE: - showMagnifier(ev); + updateMagnifier(ev); break; case MotionEvent.ACTION_UP: @@ -5235,11 +5266,11 @@ public class Editor { // re-engages the handle. mTouchWordDelta = 0.0f; mPrevX = UNSET_X_VALUE; - showMagnifier(event); + updateMagnifier(event); break; case MotionEvent.ACTION_MOVE: - showMagnifier(event); + updateMagnifier(event); break; case MotionEvent.ACTION_UP: