Merge "[Magnifier - 5] Auto invalidate and misc"
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index af09592..384f4f8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -476,17 +476,6 @@
         stopTextActionModeWithPreservingSelection();
     }
 
-    void invalidateMagnifier() {
-        final DisplayMetrics dm = mTextView.getResources().getDisplayMetrics();
-        invalidateMagnifier(0, 0, dm.widthPixels, dm.heightPixels);
-    }
-
-    void invalidateMagnifier(final float l, final float t, final float r, final float b) {
-        if (mMagnifier != null) {
-            mTextView.post(() -> mMagnifier.invalidate(new RectF(l, t, r, b)));
-        }
-    }
-
     private void discardTextDisplayLists() {
         if (mTextRenderNodes != null) {
             for (int i = 0; i < mTextRenderNodes.length; i++) {
@@ -4550,17 +4539,15 @@
             final Layout layout = mTextView.getLayout();
             final int lineNumber = layout.getLineForOffset(offset);
             // Horizontally snap to character offset.
-            final float xPosInView = getHorizontal(mTextView.getLayout(), offset);
+            final float xPosInView = getHorizontal(mTextView.getLayout(), offset)
+                    + mTextView.getTotalPaddingLeft() - mTextView.getScrollX();
             // Vertically snap to middle of current line.
             final float yPosInView = (mTextView.getLayout().getLineTop(lineNumber)
-                    + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f;
-            final int[] coordinatesOnScreen = new int[2];
-            mTextView.getLocationOnScreen(coordinatesOnScreen);
-            final float centerXOnScreen = mTextView.convertViewToScreenCoord(xPosInView, true);
-            final float centerYOnScreen = mTextView.convertViewToScreenCoord(yPosInView, false);
+                    + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f
+                    + mTextView.getTotalPaddingTop() - mTextView.getScrollY();
 
             suspendBlink();
-            mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
+            mMagnifier.show(xPosInView, yPosInView, MAGNIFIER_ZOOM);
         }
 
         protected final void dismissMagnifier() {
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ce80552..d9bc51f 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -9219,36 +9219,6 @@
         }
     }
 
-    @Override
-    public void invalidate() {
-        super.invalidate();
-
-        if (mEditor != null) {
-            mEditor.invalidateMagnifier();
-        }
-    }
-
-    @Override
-    public void invalidate(int l, int t, int r, int b) {
-        super.invalidate(l, t, r, b);
-
-        if (mEditor != null) {
-            mEditor.invalidateMagnifier(
-                    convertViewToScreenCoord(l, true /* isHorizontal */),
-                    convertViewToScreenCoord(t, false /* isHorizontal */),
-                    convertViewToScreenCoord(r, true /* isHorizontal */),
-                    convertViewToScreenCoord(b, false /* isHorizontal */));
-        }
-    }
-
-    float convertViewToScreenCoord(float viewCoord, boolean isHorizontal) {
-        final int[] coordinatesOnScreen = new int[2];
-        getLocationOnScreen(coordinatesOnScreen);
-        return isHorizontal
-                ? viewCoord + getTotalPaddingLeft() - getScrollX() + coordinatesOnScreen[0]
-                : viewCoord + getTotalPaddingTop() - getScrollY() + coordinatesOnScreen[1];
-    }
-
     /**
      * @return whether or not the cursor is visible (assuming this TextView is editable)
      *
diff --git a/core/java/com/android/internal/widget/Magnifier.java b/core/java/com/android/internal/widget/Magnifier.java
index 9bc0778..6d54d7b 100644
--- a/core/java/com/android/internal/widget/Magnifier.java
+++ b/core/java/com/android/internal/widget/Magnifier.java
@@ -22,9 +22,7 @@
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Point;
-import android.graphics.PointF;
 import android.graphics.Rect;
-import android.graphics.RectF;
 import android.os.Handler;
 import android.util.Log;
 import android.view.Gravity;
@@ -38,13 +36,15 @@
 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";
-    // Use this to specify that a previous configuration value does not exist.
-    private static final int INEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
+    private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps
     // The view for which this magnifier is attached.
     private final View mView;
     // The window containing the magnifier.
@@ -62,15 +62,10 @@
     // The callback of the pixel copy request will be invoked on this Handler when
     // the copy is finished.
     private final Handler mPixelCopyHandler = Handler.getMain();
-
-    private RectF mTmpRectF;
-
-    // Variables holding previous states, used for detecting redundant calls and invalidation.
-    private Point mPrevStartCoordsOnScreen = new Point(
-            INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE);
-    private PointF mPrevCenterCoordsOnScreen = new PointF(
-            INEXISTENT_PREVIOUS_CONFIG_VALUE, INEXISTENT_PREVIOUS_CONFIG_VALUE);
-    private float mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE;
+    // Current magnification scale.
+    private float mScale;
+    // Timer used to schedule the copy task.
+    private Timer mTimer;
 
     /**
      * Initializes a magnifier.
@@ -82,6 +77,7 @@
         mView = Preconditions.checkNotNull(view);
         final Context context = mView.getContext();
         final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
+        content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
         mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
         mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
         final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
@@ -101,15 +97,15 @@
     /**
      * Shows the magnifier on the screen.
      *
-     * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source. The
-     *        lower end is clamped to 0
-     * @param centerYOnScreen vertical coordinate of the center point of the magnifier source. The
-     *        lower end is clamped to 0
+     * @param xPosInView horizontal coordinate of the center point of the magnifier source relative
+     *        to the view. The lower end is clamped to 0
+     * @param yPosInView vertical coordinate of the center point of the magnifier source
+     *        relative to the view. The lower end is clamped to 0
      * @param scale the scale at which the magnifier zooms on the source content. The
      *        lower end is clamped to 1 and the higher end to 4
      */
-    public void show(@FloatRange(from=0) float centerXOnScreen,
-            @FloatRange(from=0) float centerYOnScreen,
+    public void show(@FloatRange(from=0) float xPosInView,
+            @FloatRange(from=0) float yPosInView,
             @FloatRange(from=1, to=4) float scale) {
         if (scale > 4) {
             scale = 4;
@@ -119,27 +115,29 @@
             scale = 1;
         }
 
-        if (centerXOnScreen < 0) {
-            centerXOnScreen = 0;
+        if (xPosInView < 0) {
+            xPosInView = 0;
         }
 
-        if (centerYOnScreen < 0) {
-            centerYOnScreen = 0;
+        if (yPosInView < 0) {
+            yPosInView = 0;
         }
 
-        showInternal(centerXOnScreen, centerYOnScreen, scale, false);
-    }
-
-    private void showInternal(@FloatRange(from=0) float centerXOnScreen,
-            @FloatRange(from=0) float centerYOnScreen,
-            @FloatRange(from=1, to=4) float scale,
-            boolean forceShow) {
-        if (mPrevScale != scale) {
+        if (mScale != scale) {
             resizeBitmap(scale);
-            mPrevScale = scale;
         }
-        configureCoordinates(centerXOnScreen, centerYOnScreen);
-        maybePerformPixelCopy(scale, forceShow);
+        mScale = scale;
+        configureCoordinates(xPosInView, yPosInView);
+
+        if (mTimer == null) {
+            mTimer = new Timer();
+            mTimer.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    performPixelCopy();
+                }
+            }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS);
+        }
 
         if (mWindow.isShowing()) {
             mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
@@ -148,9 +146,6 @@
             mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
                     mWindowCoords.x, mWindowCoords.y);
         }
-
-        mPrevCenterCoordsOnScreen.x = centerXOnScreen;
-        mPrevCenterCoordsOnScreen.y = centerYOnScreen;
     }
 
     /**
@@ -159,36 +154,10 @@
     public void dismiss() {
         mWindow.dismiss();
 
-        mPrevStartCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE;
-        mPrevStartCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE;
-        mPrevCenterCoordsOnScreen.x = INEXISTENT_PREVIOUS_CONFIG_VALUE;
-        mPrevCenterCoordsOnScreen.y = INEXISTENT_PREVIOUS_CONFIG_VALUE;
-        mPrevScale = INEXISTENT_PREVIOUS_CONFIG_VALUE;
-    }
-
-    /**
-     * Forces the magnifier to update content by taking and showing a new snapshot using the
-     * previous coordinates. It does this only if the magnifier is showing and the dirty rectangle
-     * intersects the rectangle which holds the content to be magnified.
-     *
-     * @param dirtyRectOnScreen the rectangle representing the screen bounds of the dirty region
-     */
-    public void invalidate(RectF dirtyRectOnScreen) {
-        if (mWindow.isShowing() && mPrevCenterCoordsOnScreen.x != INEXISTENT_PREVIOUS_CONFIG_VALUE
-                && mPrevCenterCoordsOnScreen.y != INEXISTENT_PREVIOUS_CONFIG_VALUE
-                && mPrevScale != INEXISTENT_PREVIOUS_CONFIG_VALUE) {
-            // Update the current showing RectF.
-            mTmpRectF = new RectF(mPrevStartCoordsOnScreen.x,
-                    mPrevStartCoordsOnScreen.y,
-                    mPrevStartCoordsOnScreen.x + mBitmap.getWidth(),
-                    mPrevStartCoordsOnScreen.y + mBitmap.getHeight());
-
-            // Update only if we are currently showing content that has been declared as invalid.
-            if (RectF.intersects(dirtyRectOnScreen, mTmpRectF)) {
-                // Update the contents shown in the magnifier.
-                showInternal(mPrevCenterCoordsOnScreen.x, mPrevCenterCoordsOnScreen.y, mPrevScale,
-                        true /* forceShow */);
-            }
+        if (mTimer != null) {
+            mTimer.cancel();
+            mTimer.purge();
+            mTimer = null;
         }
     }
 
@@ -213,7 +182,12 @@
         getImageView().setImageBitmap(mBitmap);
     }
 
-    private void configureCoordinates(float posXOnScreen, float posYOnScreen) {
+    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];
+
         mCenterZoomCoords.x = (int) posXOnScreen;
         mCenterZoomCoords.y = (int) posYOnScreen;
 
@@ -223,7 +197,7 @@
         mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
     }
 
-    private void maybePerformPixelCopy(final float scale, final boolean forceShow) {
+    private void performPixelCopy() {
         final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
         int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
 
@@ -234,13 +208,6 @@
             rawStartX = mView.getWidth() - mBitmap.getWidth();
         }
 
-        if (!forceShow && rawStartX == mPrevStartCoordsOnScreen.x
-                && startY == mPrevStartCoordsOnScreen.y
-                && scale == mPrevScale) {
-            // Skip, we are already showing the desired content.
-            return;
-        }
-
         final int startX = rawStartX;
         final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
 
@@ -251,11 +218,7 @@
                     new Rect(startX, startY, startX + mBitmap.getWidth(),
                             startY + mBitmap.getHeight()),
                     mBitmap,
-                    result -> {
-                        getImageView().invalidate();
-                        mPrevStartCoordsOnScreen.x = startX;
-                        mPrevStartCoordsOnScreen.y = startY;
-                    },
+                    result -> getImageView().invalidate(),
                     mPixelCopyHandler);
         } else {
             Log.d(LOG_TAG, "Could not perform PixelCopy request");
diff --git a/core/res/res/layout/magnifier.xml b/core/res/res/layout/magnifier.xml
index 181e5e5..d6cd8b4 100644
--- a/core/res/res/layout/magnifier.xml
+++ b/core/res/res/layout/magnifier.xml
@@ -18,10 +18,17 @@
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:background="?android:attr/floatingToolbarPopupBackgroundDrawable">
-    <ImageView
-        android:id="@+id/magnifier_image"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
+    android:layout_height="wrap_content">
+    <LinearLayout
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:id="@+id/magnifier_inner"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:background="?android:attr/floatingToolbarPopupBackgroundDrawable"
+        android:elevation="@android:dimen/magnifier_elevation">
+        <ImageView
+            android:id="@+id/magnifier_image"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent" />
+    </LinearLayout>
 </LinearLayout>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 14069e7..08e2233 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -521,7 +521,7 @@
     <dimen name="content_rect_bottom_clip_allowance">20dp</dimen>
 
     <!-- Magnifier dimensions -->
-    <dimen name="magnifier_width">200dp</dimen>
+    <dimen name="magnifier_width">164dp</dimen>
     <dimen name="magnifier_height">48dp</dimen>
     <dimen name="magnifier_elevation">2dp</dimen>
     <dimen name="magnifier_offset">42dp</dimen>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 46a6e6e..1627a0e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2479,6 +2479,7 @@
 
   <!-- Magnifier -->
   <java-symbol type="id" name="magnifier_image" />
+  <java-symbol type="id" name="magnifier_inner" />
   <java-symbol type="layout" name="magnifier" />
   <java-symbol type="dimen" name="magnifier_width" />
   <java-symbol type="dimen" name="magnifier_height" />