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

Commit d0f213fb authored by Haoyu Zhang's avatar Haoyu Zhang Committed by Android (Google) Code Review
Browse files

Merge "[Scribe] Support extended handwriting boundary"

parents 3c1267d7 db2f631a
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -27,6 +27,7 @@ import android.app.Instrumentation;
import android.content.Context;
import android.perftests.utils.BenchmarkState;
import android.perftests.utils.PerfStatusReporter;
import android.util.DisplayMetrics;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;

@@ -59,10 +60,13 @@ public class HandwritingInitiatorPerfTest {
    public void setup() {
        final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
        mContext = mInstrumentation.getTargetContext();
        ViewConfiguration viewConfiguration = ViewConfiguration.get(mContext);
        final ViewConfiguration viewConfiguration = ViewConfiguration.get(mContext);
        mTouchSlop = viewConfiguration.getScaledTouchSlop();
        InputMethodManager inputMethodManager = mContext.getSystemService(InputMethodManager.class);
        mHandwritingInitiator = new HandwritingInitiator(viewConfiguration, inputMethodManager);
        final InputMethodManager inputMethodManager =
                mContext.getSystemService(InputMethodManager.class);
        final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
        mHandwritingInitiator = new HandwritingInitiator(viewConfiguration, inputMethodManager,
                displayMetrics);
    }

    @Test
+106 −13
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.annotations.VisibleForTesting;
@@ -49,6 +51,12 @@ import java.util.List;
 * @hide
 */
public class HandwritingInitiator {
    /** The amount of extra space added to handwriting in dip. */
    private static final int HANDWRITING_AREA_PADDING_DIP = 20;

    /** The amount of extra space added to handwriting in px. */
    private final float mHandwritingAreaPaddingPx;

    /**
     * The touchSlop from {@link ViewConfiguration} used to decide whether a pointer is considered
     * moving or stationary.
@@ -88,9 +96,13 @@ public class HandwritingInitiator {

    @VisibleForTesting
    public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration,
            @NonNull InputMethodManager inputMethodManager) {
            @NonNull InputMethodManager inputMethodManager, DisplayMetrics displayMetrics) {
        mTouchSlop = viewConfiguration.getScaledTouchSlop();
        mHandwritingTimeoutInMillis = ViewConfiguration.getLongPressTimeout();
        mHandwritingAreaPaddingPx = TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                HANDWRITING_AREA_PADDING_DIP,
                displayMetrics);
        mImm = inputMethodManager;
    }

@@ -250,7 +262,8 @@ public class HandwritingInitiator {
        }

        final Rect handwritingArea = getViewHandwritingArea(connectedView);
        if (contains(handwritingArea, mState.mStylusDownX, mState.mStylusDownY)) {
        if (containsInExtendedHandwritingArea(handwritingArea,
                mState.mStylusDownX, mState.mStylusDownY)) {
            startHandwriting(connectedView);
        } else {
            reset();
@@ -281,14 +294,21 @@ public class HandwritingInitiator {
     */
    @Nullable
    private View findBestCandidateView(float x, float y) {
        float minDistance = Float.MAX_VALUE;
        View bestCandidate = null;

        // If the connectedView is not null and do not set any handwriting area, it will check
        // whether the connectedView's boundary contains the initial stylus position. If true,
        // directly return the connectedView.
        final View connectedView = getConnectedView();
        if (connectedView != null && connectedView.isAutoHandwritingEnabled()) {
            final Rect handwritingArea = getViewHandwritingArea(connectedView);
            if (contains(handwritingArea, x, y)) {
                return connectedView;
            Rect handwritingArea = getViewHandwritingArea(connectedView);
            if (containsInExtendedHandwritingArea(handwritingArea, x, y)) {
                final float distance = distance(handwritingArea, x, y);
                if (distance == 0f) return connectedView;

                bestCandidate = connectedView;
                minDistance = distance;
            }
        }

@@ -297,18 +317,78 @@ public class HandwritingInitiator {
                mHandwritingAreasTracker.computeViewInfos();
        for (HandwritableViewInfo viewInfo : handwritableViewInfos) {
            final View view = viewInfo.getView();
            if (!view.isAutoHandwritingEnabled()) continue;
            if (contains(viewInfo.getHandwritingArea(), x, y)) {
                return viewInfo.getView();
            final Rect handwritingArea = viewInfo.getHandwritingArea();
            if (!containsInExtendedHandwritingArea(handwritingArea, x, y)) continue;

            final float distance = distance(handwritingArea, x, y);

            if (distance == 0f) return view;
            if (distance < minDistance) {
                minDistance = distance;
                bestCandidate = view;
            }
        }
        return null;
        return bestCandidate;
    }

    /**
     *  Return the square of the distance from point (x, y) to the given rect, which is mainly used
     *  for comparison. The distance is defined to be: the shortest distance between (x, y) to any
     *  point on rect. When (x, y) is contained by the rect, return 0f.
     */
    private static float distance(@NonNull Rect rect, float x, float y) {
        if (contains(rect, x, y, 0f, 0f, 0f, 0f)) {
            return 0f;
        }

        /* The distance between point (x, y) and rect, there are 2 basic cases:
         * a) The distance is the distance from (x, y) to the closest corner on rect.
         *                    o |     |
         *         ---+-----+---
         *            |     |
         *         ---+-----+---
         *            |     |
         * b) The distance is the distance from (x, y) to the closest edge on rect.
         *                      |  o  |
         *         ---+-----+---
         *            |     |
         *         ---+-----+---
         *            |     |
         * We define xDistance as following(similar for yDistance):
         *   If x is in [left, right) 0, else min(abs(x - left), abs(x - y))
         * For case a, sqrt(xDistance^2 + yDistance^2) is the final distance.
         * For case b, distance should be yDistance, which is also equal to
         * sqrt(xDistance^2 + yDistance^2) because xDistance is 0.
         */
        final float xDistance;
        if (x >= rect.left && x < rect.right) {
            xDistance = 0f;
        } else if (x < rect.left) {
            xDistance = rect.left - x;
        } else {
            xDistance = x - rect.right;
        }

        final float yDistance;
        if (y >= rect.top && y < rect.bottom) {
            yDistance = 0f;
        } else if (y < rect.top) {
            yDistance = rect.top - y;
        } else {
            yDistance = y - rect.bottom;
        }
        // We can omit sqrt here because we only need the distance for comparison.
        return xDistance * xDistance + yDistance * yDistance;
    }

    /**
     * Return the handwriting area of the given view, represented in the window's coordinate.
     * If the view didn't set any handwriting area, it will return the view's boundary.
     * It will return null if the view or its handwriting area is not visible.
     *
     * The handwriting area is clipped to its visible part.
     * Notice that the returned rectangle is the view's original handwriting area without the
     * view's handwriting area extends.
     */
    @Nullable
    private static Rect getViewHandwritingArea(@NonNull View view) {
@@ -329,11 +409,24 @@ public class HandwritingInitiator {
    }

    /**
     * Return true if the (x, y) is inside by the given {@link Rect}.
     * Return true if the (x, y) is inside by the given {@link Rect} extended by the View's
     * handwriting extends settings.
     */
    private boolean containsInExtendedHandwritingArea(@Nullable Rect handwritingArea,
            float x, float y) {
        if (handwritingArea == null) return false;
        return contains(handwritingArea, x, y, mHandwritingAreaPaddingPx, mHandwritingAreaPaddingPx,
                mHandwritingAreaPaddingPx, mHandwritingAreaPaddingPx);
    }

    /**
     * Return true if the (x, y) is inside by the given {@link Rect} extended by the given
     * extendLeft, extendTop, extendRight and extendBottom.
     */
    private boolean contains(@Nullable Rect rect, float x, float y) {
        if (rect == null) return false;
        return x >= rect.left && x < rect.right && y >= rect.top && y < rect.bottom;
    private static boolean contains(@NonNull Rect rect, float x, float y,
            float extendLeft, float extendTop, float extendRight, float extendBottom) {
        return x >= rect.left - extendLeft && x < rect.right  + extendRight
                && y >= rect.top - extendTop && y < rect.bottom + extendBottom;
    }

    private boolean largerThanTouchSlop(float x1, float y1, float x2, float y2) {
+4 −2
Original line number Diff line number Diff line
@@ -902,8 +902,10 @@ public final class ViewRootImpl implements ViewParent,
                ? Choreographer.getSfInstance() : Choreographer.getInstance();
        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
        mInsetsController = new InsetsController(new ViewRootInsetsControllerHost(this));
        mHandwritingInitiator = new HandwritingInitiator(mViewConfiguration,
                mContext.getSystemService(InputMethodManager.class));
        mHandwritingInitiator = new HandwritingInitiator(
                mViewConfiguration,
                mContext.getSystemService(InputMethodManager.class),
                context.getResources().getDisplayMetrics());

        String processorOverrideName = context.getResources().getString(
                                    R.string.config_inputEventCompatProcessorOverrideClassName);
+68 −4
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.HandwritingInitiator;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -61,23 +63,32 @@ import org.junit.runner.RunWith;
public class HandwritingInitiatorTest {
    private static final int TOUCH_SLOP = 8;
    private static final long TIMEOUT = ViewConfiguration.getLongPressTimeout();
    private static final int HANDWRITING_AREA_PADDING_DIP = 20;

    private static final Rect sHwArea = new Rect(100, 200, 500, 500);

    private HandwritingInitiator mHandwritingInitiator;
    private View mTestView;
    private Context mContext;
    private int mHandwritingAreaPaddingPx;

    @Before
    public void setup() {
        final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
        mContext = mInstrumentation.getTargetContext();
        ViewConfiguration viewConfiguration = mock(ViewConfiguration.class);
        final ViewConfiguration viewConfiguration = mock(ViewConfiguration.class);
        when(viewConfiguration.getScaledTouchSlop()).thenReturn(TOUCH_SLOP);


        final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics();
        InputMethodManager inputMethodManager = mContext.getSystemService(InputMethodManager.class);
        mHandwritingInitiator =
                spy(new HandwritingInitiator(viewConfiguration, inputMethodManager));
        mHandwritingInitiator = spy(new HandwritingInitiator(viewConfiguration, inputMethodManager,
                displayMetrics));

        mHandwritingAreaPaddingPx = Math.round(TypedValue.applyDimension(
                TypedValue.COMPLEX_UNIT_DIP,
                HANDWRITING_AREA_PADDING_DIP,
                displayMetrics));
        mTestView = createView(sHwArea, true);
        mHandwritingInitiator.updateHandwritingAreasForView(mTestView);
    }
@@ -126,6 +137,24 @@ public class HandwritingInitiatorTest {
        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
    }

    @Test
    public void onTouchEvent_startHandwriting_when_stylusMove_withinExtendedHWArea() {
        mHandwritingInitiator.onInputConnectionCreated(mTestView);
        final int x1 = sHwArea.left - mHandwritingAreaPaddingPx / 2;
        final int y1 = sHwArea.top - mHandwritingAreaPaddingPx / 2;
        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
        mHandwritingInitiator.onTouchEvent(stylusEvent1);

        final int x2 = x1 + TOUCH_SLOP * 2;
        final int y2 = y1;

        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
        mHandwritingInitiator.onTouchEvent(stylusEvent2);

        // Stylus movement within extended HandwritingArea should trigger IMM.startHandwriting once.
        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
    }

    @Test
    public void onTouchEvent_startHandwriting_inputConnectionBuiltAfterStylusMove() {
        final int x1 = (sHwArea.left + sHwArea.right) / 2;
@@ -144,6 +173,24 @@ public class HandwritingInitiatorTest {
        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
    }

    @Test
    public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() {
        final int x1 = sHwArea.right + mHandwritingAreaPaddingPx / 2;
        final int y1 = sHwArea.bottom + mHandwritingAreaPaddingPx / 2;
        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
        mHandwritingInitiator.onTouchEvent(stylusEvent1);

        final int x2 = x1 + TOUCH_SLOP * 2;
        final int y2 = y1;
        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
        mHandwritingInitiator.onTouchEvent(stylusEvent2);

        // InputConnection is created after stylus movement.
        mHandwritingInitiator.onInputConnectionCreated(mTestView);

        verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView);
    }

    @Test
    public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() {
        mHandwritingInitiator.onInputConnectionCreated(mTestView);
@@ -212,6 +259,23 @@ public class HandwritingInitiatorTest {
        verify(mTestView, times(1)).requestFocus();
    }

    @Test
    public void onTouchEvent_focusView_stylusMoveOnce_withinExtendedHWArea() {
        final int x1 = sHwArea.left - mHandwritingAreaPaddingPx / 2;
        final int y1 = sHwArea.top - mHandwritingAreaPaddingPx / 2;
        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
        mHandwritingInitiator.onTouchEvent(stylusEvent1);

        final int x2 = x1 + TOUCH_SLOP * 2;
        final int y2 = y1;

        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
        mHandwritingInitiator.onTouchEvent(stylusEvent2);

        // HandwritingInitiator will request focus for the registered view.
        verify(mTestView, times(1)).requestFocus();
    }

    @Test
    public void autoHandwriting_whenDisabled_wontStartHW() {
        View mockView = createView(sHwArea, false);