Merge "[Magnifier - 8] SurfaceView support and invalidate revival"
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d477ffd..d0c2d86 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -195,6 +195,27 @@
private final boolean mHapticTextHandleEnabled;
private final Magnifier mMagnifier;
+ private final Runnable mUpdateMagnifierRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mMagnifier.update();
+ }
+ };
+ // Update the magnifier contents whenever anything in the view hierarchy is updated.
+ // Note: this only captures UI thread-visible changes, so it's a known issue that an animating
+ // VectorDrawable or Ripple animation will not trigger capture, since they're owned by
+ // RenderThread.
+ private final ViewTreeObserver.OnDrawListener mMagnifierOnDrawListener =
+ new ViewTreeObserver.OnDrawListener() {
+ @Override
+ public void onDraw() {
+ if (mMagnifier != null) {
+ // Posting the method will ensure that updating the magnifier contents will
+ // happen right after the rendering of the current frame.
+ mTextView.post(mUpdateMagnifierRunnable);
+ }
+ }
+ };
// Used to highlight a word when it is corrected by the IME
private CorrectionHighlighter mCorrectionHighlighter;
@@ -415,15 +436,21 @@
}
final ViewTreeObserver observer = mTextView.getViewTreeObserver();
- // No need to create the controller.
- // The get method will add the listener on controller creation.
- if (mInsertionPointCursorController != null) {
- observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+ if (observer.isAlive()) {
+ // No need to create the controller.
+ // The get method will add the listener on controller creation.
+ if (mInsertionPointCursorController != null) {
+ observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+ }
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.resetTouchOffsets();
+ observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
+ }
+ if (FLAG_USE_MAGNIFIER) {
+ observer.addOnDrawListener(mMagnifierOnDrawListener);
+ }
}
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.resetTouchOffsets();
- observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
- }
+
updateSpellCheckSpans(0, mTextView.getText().length(),
true /* create the spell checker if needed */);
@@ -472,6 +499,13 @@
mSpellChecker = null;
}
+ if (FLAG_USE_MAGNIFIER) {
+ final ViewTreeObserver observer = mTextView.getViewTreeObserver();
+ if (observer.isAlive()) {
+ observer.removeOnDrawListener(mMagnifierOnDrawListener);
+ }
+ }
+
hideCursorAndSpanControllers();
stopTextActionModeWithPreservingSelection();
}
diff --git a/core/java/com/android/internal/widget/Magnifier.java b/core/java/com/android/internal/widget/Magnifier.java
index f6741c3..f79a7f5 100644
--- a/core/java/com/android/internal/widget/Magnifier.java
+++ b/core/java/com/android/internal/widget/Magnifier.java
@@ -18,34 +18,33 @@
import android.annotation.FloatRange;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.UiThread;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
-import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.PixelCopy;
+import android.view.Surface;
+import android.view.SurfaceView;
import android.view.View;
-import android.view.ViewRootImpl;
import android.widget.ImageView;
import android.widget.PopupWindow;
import com.android.internal.R;
import com.android.internal.util.Preconditions;
-import java.util.Timer;
-import java.util.TimerTask;
-
/**
* Android magnifier widget. Can be used by any view which is attached to window.
*/
public final class Magnifier {
- private static final String LOG_TAG = "magnifier";
- private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps
- // The view for which this magnifier is attached.
+ // Use this to specify that a previous configuration value does not exist.
+ private static final int NONEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
+ // The view to which this magnifier is attached.
private final View mView;
// The window containing the magnifier.
private final PopupWindow mWindow;
@@ -64,8 +63,12 @@
private final Handler mPixelCopyHandler = Handler.getMain();
// Current magnification scale.
private final float mZoomScale;
- // Timer used to schedule the copy task.
- private Timer mTimer;
+ // 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);
+ private final Rect mPixelCopyRequestRect = new Rect();
/**
* Initializes a magnifier.
@@ -91,8 +94,8 @@
mWindow.setTouchable(false);
mWindow.setBackgroundDrawable(null);
- final int bitmapWidth = (int) (mWindowWidth / mZoomScale);
- final int bitmapHeight = (int) (mWindowHeight / mZoomScale);
+ 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);
}
@@ -106,32 +109,29 @@
* relative to the view. The lower end is clamped to 0
*/
public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) {
- if (xPosInView < 0) {
- xPosInView = 0;
- }
-
- if (yPosInView < 0) {
- yPosInView = 0;
- }
+ xPosInView = Math.max(0, xPosInView);
+ yPosInView = Math.max(0, yPosInView);
configureCoordinates(xPosInView, yPosInView);
- if (mTimer == null) {
- mTimer = new Timer();
- mTimer.schedule(new TimerTask() {
- @Override
- public void run() {
- performPixelCopy();
- }
- }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS);
- }
+ // Clamp startX value to avoid distorting the rendering of the magnifier content.
+ final int startX = Math.max(0, Math.min(
+ mCenterZoomCoords.x - mBitmap.getWidth() / 2,
+ mView.getWidth() - mBitmap.getWidth()));
+ final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
- if (mWindow.isShowing()) {
- mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
- mWindow.getHeight());
- } else {
- mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
- mWindowCoords.x, mWindowCoords.y);
+ if (startX != mPrevStartCoordsInSurface.x || startY != mPrevStartCoordsInSurface.y) {
+ performPixelCopy(startX, startY);
+
+ 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);
+ }
}
}
@@ -140,11 +140,18 @@
*/
public void dismiss() {
mWindow.dismiss();
+ }
- if (mTimer != null) {
- mTimer.cancel();
- mTimer.purge();
- mTimer = null;
+ /**
+ * Forces the magnifier to update its content. It uses the previous coordinates passed to
+ * {@link #show(float, float)}. This only happens if the magnifier is currently showing.
+ *
+ * @hide
+ */
+ public void update() {
+ if (mWindow.isShowing()) {
+ // Update the contents shown in the magnifier.
+ performPixelCopy(mPrevStartCoordsInSurface.x, mPrevStartCoordsInSurface.y);
}
}
@@ -170,13 +177,22 @@
}
private void configureCoordinates(float xPosInView, float yPosInView) {
- final int[] coordinatesOnScreen = new int[2];
- mView.getLocationOnScreen(coordinatesOnScreen);
- final float posXOnScreen = xPosInView + coordinatesOnScreen[0];
- final float posYOnScreen = yPosInView + coordinatesOnScreen[1];
+ final float posX;
+ final float posY;
- mCenterZoomCoords.x = (int) posXOnScreen;
- mCenterZoomCoords.y = (int) posYOnScreen;
+ if (mView instanceof SurfaceView) {
+ // No offset required if the backing Surface matches the size of the SurfaceView.
+ posX = xPosInView;
+ posY = yPosInView;
+ } else {
+ final int[] coordinatesInSurface = new int[2];
+ mView.getLocationInSurface(coordinatesInSurface);
+ posX = xPosInView + coordinatesInSurface[0];
+ posY = yPosInView + coordinatesInSurface[1];
+ }
+
+ mCenterZoomCoords.x = Math.round(posX);
+ mCenterZoomCoords.y = Math.round(posY);
final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
R.dimen.magnifier_offset);
@@ -184,34 +200,36 @@
mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
}
- private void performPixelCopy() {
- final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
- int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
+ private void performPixelCopy(final int startXInSurface, final int startYInSurface) {
+ final Surface surface = getValidViewSurface();
+ if (surface != null) {
+ mPixelCopyRequestRect.set(startXInSurface, startYInSurface,
+ startXInSurface + mBitmap.getWidth(), startYInSurface + mBitmap.getHeight());
- // Clamp startX value to avoid distorting the rendering of the magnifier content.
- if (rawStartX < 0) {
- rawStartX = 0;
- } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) {
- rawStartX = mView.getWidth() - mBitmap.getWidth();
- }
-
- final int startX = rawStartX;
- final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
-
- if (viewRootImpl != null && viewRootImpl.mSurface != null
- && viewRootImpl.mSurface.isValid()) {
- PixelCopy.request(
- viewRootImpl.mSurface,
- new Rect(startX, startY, startX + mBitmap.getWidth(),
- startY + mBitmap.getHeight()),
- mBitmap,
- result -> getImageView().invalidate(),
+ PixelCopy.request(surface, mPixelCopyRequestRect, mBitmap,
+ result -> {
+ getImageView().invalidate();
+ mPrevStartCoordsInSurface.x = startXInSurface;
+ mPrevStartCoordsInSurface.y = startYInSurface;
+ },
mPixelCopyHandler);
- } else {
- Log.d(LOG_TAG, "Could not perform PixelCopy request");
}
}
+ @Nullable
+ private Surface getValidViewSurface() {
+ 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 ImageView getImageView() {
return mWindow.getContentView().findViewById(R.id.magnifier_image);
}