Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 15af561e authored by Andrei Stingaceanu's avatar Andrei Stingaceanu
Browse files

[Magnifier - 3] Reduce number of calls to PixelCopy

* Magnifier#show() takes snapshots of the content and displays
  them in the Magnifier bitmap
* calling show(...) consecutive times with the same arguments is
  a no-op if already showing (to cater with the miriad of motion
  events produced by touch which end up calling show(...))
* introduced Magnifier#invalidate(...) which, if currently
  showing, forces updating the content using the last configuration
* clamped the start horizontal value of the Rect which delimits the
  content to show in order to avoid distorting the rendering of the
  magnifier content
* fixed invalidating the magnifier (invalidate() does not
  automatically call invalidate(RectF) !)

Bug: 63531115
Bug: 67296158
Test: bit FrameworksCoreTests:android.widget.TextViewActivityTest
Test: bit CtsWidgetTestCases:android.widget.cts.TextViewTest
Test: manual test that shows the magnifier working
Change-Id: I8e53dfb6582d541922fe05b60311658fb07ca880
parent aab5a8a0
Loading
Loading
Loading
Loading
+13 −4
Original line number Diff line number Diff line
@@ -476,6 +476,17 @@ public class Editor {
        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++) {
@@ -4545,10 +4556,8 @@ public class Editor {
                    + mTextView.getLayout().getLineBottom(lineNumber)) / 2.0f;
            final int[] coordinatesOnScreen = new int[2];
            mTextView.getLocationOnScreen(coordinatesOnScreen);
            final float centerXOnScreen = xPosInView + mTextView.getTotalPaddingLeft()
                    - mTextView.getScrollX() + coordinatesOnScreen[0];
            final float centerYOnScreen = yPosInView + mTextView.getTotalPaddingTop()
                    - mTextView.getScrollY() + coordinatesOnScreen[1];
            final float centerXOnScreen = mTextView.convertViewToScreenCoord(xPosInView, true);
            final float centerYOnScreen = mTextView.convertViewToScreenCoord(yPosInView, false);

            suspendBlink();
            mMagnifier.show(centerXOnScreen, centerYOnScreen, MAGNIFIER_ZOOM);
+30 −0
Original line number Diff line number Diff line
@@ -9219,6 +9219,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        }
    }

    @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)
     *
+95 −25
Original line number Diff line number Diff line
@@ -22,7 +22,9 @@ 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.graphics.RectF;
import android.os.Handler;
import android.util.Log;
import android.view.Gravity;
@@ -41,8 +43,8 @@ import com.android.internal.util.Preconditions;
 */
public final class Magnifier {
    private static final String LOG_TAG = "magnifier";
    private static final int MINIMUM_MAGNIFIER_SCALE = 1;
    private static final int MAXIMUM_MAGNIFIER_SCALE = 4;
    // Use this to specify that a previous configuration value does not exist.
    private static final int INEXISTENT_PREVIOUS_CONFIG_VALUE = -1;
    // The view for which this magnifier is attached.
    private final View mView;
    // The window containing the magnifier.
@@ -61,6 +63,15 @@ public final class Magnifier {
    // 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;

    /**
     * Initializes a magnifier.
     *
@@ -90,19 +101,22 @@ public final class Magnifier {
    /**
     * Shows the magnifier on the screen.
     *
     * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source
     * @param centerYOnScreen vertical coordinate of the center point of the magnifier source
     * @param scale the scale at which the magnifier zooms on the source content
     * @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 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,
            @FloatRange(from=MINIMUM_MAGNIFIER_SCALE, to=MAXIMUM_MAGNIFIER_SCALE) float scale) {
        if (scale > MAXIMUM_MAGNIFIER_SCALE) {
            scale = MAXIMUM_MAGNIFIER_SCALE;
            @FloatRange(from=1, to=4) float scale) {
        if (scale > 4) {
            scale = 4;
        }

        if (scale < MINIMUM_MAGNIFIER_SCALE) {
            scale = MINIMUM_MAGNIFIER_SCALE;
        if (scale < 1) {
            scale = 1;
        }

        if (centerXOnScreen < 0) {
@@ -113,9 +127,19 @@ public final class Magnifier {
            centerYOnScreen = 0;
        }

        maybeResizeBitmap(scale);
        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) {
            resizeBitmap(scale);
            mPrevScale = scale;
        }
        configureCoordinates(centerXOnScreen, centerYOnScreen);
        performPixelCopy();
        maybePerformPixelCopy(scale, forceShow);

        if (mWindow.isShowing()) {
            mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
@@ -124,6 +148,9 @@ public final class Magnifier {
            mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
                    mWindowCoords.x, mWindowCoords.y);
        }

        mPrevCenterCoordsOnScreen.x = centerXOnScreen;
        mPrevCenterCoordsOnScreen.y = centerYOnScreen;
    }

    /**
@@ -131,6 +158,38 @@ public final class Magnifier {
     */
    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 */);
            }
        }
    }

    /**
@@ -147,14 +206,12 @@ public final class Magnifier {
        return mWindowWidth;
    }

    private void maybeResizeBitmap(float scale) {
    private void resizeBitmap(float scale) {
        final int bitmapWidth = (int) (mWindowWidth / scale);
        final int bitmapHeight = (int) (mWindowHeight / scale);
        if (mBitmap.getWidth() != bitmapWidth || mBitmap.getHeight() != bitmapHeight) {
        mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
        getImageView().setImageBitmap(mBitmap);
    }
    }

    private void configureCoordinates(float posXOnScreen, float posYOnScreen) {
        mCenterZoomCoords.x = (int) posXOnScreen;
@@ -166,16 +223,25 @@ public final class Magnifier {
        mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
    }

    private void performPixelCopy() {
        int startX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
    private void maybePerformPixelCopy(final float scale, final boolean forceShow) {
        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
        int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;

        // Clamp startX value to avoid distorting the rendering of the magnifier content.
        if (startX < 0) {
            startX = 0;
        } else if (startX + mBitmap.getWidth() > mView.getWidth()) {
            startX = mView.getWidth() - mBitmap.getWidth();
        if (rawStartX < 0) {
            rawStartX = 0;
        } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) {
            rawStartX = mView.getWidth() - mBitmap.getWidth();
        }

        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
        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();

        if (viewRootImpl != null && viewRootImpl.mSurface != null
@@ -185,7 +251,11 @@ public final class Magnifier {
                    new Rect(startX, startY, startX + mBitmap.getWidth(),
                            startY + mBitmap.getHeight()),
                    mBitmap,
                    result -> getImageView().invalidate(),
                    result -> {
                        getImageView().invalidate();
                        mPrevStartCoordsOnScreen.x = startX;
                        mPrevStartCoordsOnScreen.y = startY;
                    },
                    mPixelCopyHandler);
        } else {
            Log.d(LOG_TAG, "Could not perform PixelCopy request");