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

Commit fda3324f authored by Haoyu Zhang's avatar Haoyu Zhang Committed by Automerger Merge Worker
Browse files

Merge "Fix: handwriting is triggered even when the EditText is covered" into...

Merge "Fix: handwriting is triggered even when the EditText is covered" into udc-qpr-dev am: 09527e6c am: c1f184ca

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/24043415



Change-Id: I4de20f8afadae30ba3fd675a0ee3ef6edeeb328e
Signed-off-by: default avatarAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
parents b386ea02 c1f184ca
Loading
Loading
Loading
Loading
+55 −12
Original line number Diff line number Diff line
@@ -19,7 +19,10 @@ package android.view;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.view.inputmethod.InputMethodManager;
import android.widget.TextView;

@@ -78,11 +81,17 @@ public class HandwritingInitiator {
    private int mConnectionCount = 0;
    private final InputMethodManager mImm;

    private final RectF mTempRectF = new RectF();

    private final Region mTempRegion = new Region();

    private final Matrix mTempMatrix = new Matrix();

    /**
     * The handwrite-able View that is currently the target of a hovering stylus pointer. This is
     * used to help determine whether the handwriting PointerIcon should be shown in
     * {@link #onResolvePointerIcon(Context, MotionEvent)} so that we can reduce the number of calls
     * to {@link #findBestCandidateView(float, float)}.
     * to {@link #findBestCandidateView(float, float, boolean)}.
     */
    @Nullable
    private WeakReference<View> mCachedHoverTarget = null;
@@ -184,8 +193,8 @@ public class HandwritingInitiator {
                final float y = motionEvent.getY(pointerIndex);
                if (largerThanTouchSlop(x, y, mState.mStylusDownX, mState.mStylusDownY)) {
                    mState.mExceedHandwritingSlop = true;
                    View candidateView =
                            findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY);
                    View candidateView = findBestCandidateView(mState.mStylusDownX,
                            mState.mStylusDownY, /* isHover */ false);
                    if (candidateView != null) {
                        if (candidateView == getConnectedView()) {
                            if (!candidateView.hasFocus()) {
@@ -393,13 +402,14 @@ public class HandwritingInitiator {
            final View cachedHoverTarget = getCachedHoverTarget();
            if (cachedHoverTarget != null) {
                final Rect handwritingArea = getViewHandwritingArea(cachedHoverTarget);
                if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget)
                if (isInHandwritingArea(handwritingArea, hoverX, hoverY, cachedHoverTarget,
                        /* isHover */ true)
                        && shouldTriggerStylusHandwritingForView(cachedHoverTarget)) {
                    return cachedHoverTarget;
                }
            }

            final View candidateView = findBestCandidateView(hoverX, hoverY);
            final View candidateView = findBestCandidateView(hoverX, hoverY, /* isHover */ true);

            if (candidateView != null) {
                mCachedHoverTarget = new WeakReference<>(candidateView);
@@ -429,14 +439,14 @@ public class HandwritingInitiator {
     * @param y the y coordinates of the stylus event, in the coordinates of the window.
     */
    @Nullable
    private View findBestCandidateView(float x, float y) {
    private View findBestCandidateView(float x, float y, boolean isHover) {
        // 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) {
            Rect handwritingArea = getViewHandwritingArea(connectedView);
            if (isInHandwritingArea(handwritingArea, x, y, connectedView)
            if (isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
                    && shouldTriggerStylusHandwritingForView(connectedView)) {
                return connectedView;
            }
@@ -450,7 +460,7 @@ public class HandwritingInitiator {
        for (HandwritableViewInfo viewInfo : handwritableViewInfos) {
            final View view = viewInfo.getView();
            final Rect handwritingArea = viewInfo.getHandwritingArea();
            if (!isInHandwritingArea(handwritingArea, x, y, view)
            if (!isInHandwritingArea(handwritingArea, x, y, view, isHover)
                    || !shouldTriggerStylusHandwritingForView(view)) {
                continue;
            }
@@ -546,15 +556,48 @@ public class HandwritingInitiator {
     * Return true if the (x, y) is inside by the given {@link Rect} with the View's
     * handwriting bounds with offsets applied.
     */
    private static boolean isInHandwritingArea(@Nullable Rect handwritingArea,
            float x, float y, View view) {
    private boolean isInHandwritingArea(@Nullable Rect handwritingArea,
            float x, float y, View view, boolean isHover) {
        if (handwritingArea == null) return false;

        return contains(handwritingArea, x, y,
        if (!contains(handwritingArea, x, y,
                view.getHandwritingBoundsOffsetLeft(),
                view.getHandwritingBoundsOffsetTop(),
                view.getHandwritingBoundsOffsetRight(),
                view.getHandwritingBoundsOffsetBottom());
                view.getHandwritingBoundsOffsetBottom())) {
            return false;
        }

        // The returned handwritingArea computed by ViewParent#getChildVisibleRect didn't consider
        // the case where a view is stacking on top of the editor. (e.g. DrawerLayout, popup)
        // We must check the hit region of the editor again, and avoid the case where another
        // view on top of the editor is handling MotionEvents.
        ViewParent parent = view.getParent();
        if (parent == null) {
            return true;
        }

        Region region = mTempRegion;
        mTempRegion.set(0, 0, view.getWidth(), view.getHeight());
        Matrix matrix = mTempMatrix;
        matrix.reset();
        if (!parent.getChildLocalHitRegion(view, region, matrix, isHover)) {
            return false;
        }

        // It's not easy to extend the region by the given handwritingBoundsOffset. Instead, we
        // create a rectangle surrounding the motion event location and check if this rectangle
        // overlaps with the hit region of the editor.
        float left = x - view.getHandwritingBoundsOffsetRight();
        float top = y - view.getHandwritingBoundsOffsetBottom();
        float right = Math.max(x + view.getHandwritingBoundsOffsetLeft(), left + 1);
        float bottom =  Math.max(y + view.getHandwritingBoundsOffsetTop(), top + 1);
        RectF rectF = mTempRectF;
        rectF.set(left, top, right, bottom);
        matrix.mapRect(rectF);

        return region.op(Math.round(rectF.left), Math.round(rectF.top),
                Math.round(rectF.right), Math.round(rectF.bottom), Region.Op.INTERSECT);
    }

    /**
+84 −0
Original line number Diff line number Diff line
@@ -7361,6 +7361,90 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
        }
    }

    /**
     * @hide
     */
    @Override
    public boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
            @NonNull Matrix matrix, boolean isHover) {
        if (!child.hasIdentityMatrix()) {
            matrix.preConcat(child.getInverseMatrix());
        }

        final int dx = child.mLeft - mScrollX;
        final int dy = child.mTop - mScrollY;
        matrix.preTranslate(-dx, -dy);

        final int width = mRight - mLeft;
        final int height = mBottom - mTop;

        // Map the bounds of this view into the region's coordinates and clip the region.
        final RectF rect = mAttachInfo != null ? mAttachInfo.mTmpTransformRect : new RectF();
        rect.set(0, 0, width, height);
        matrix.mapRect(rect);

        boolean notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
                Math.round(rect.right), Math.round(rect.bottom), Region.Op.INTERSECT);

        if (isHover) {
            HoverTarget target = mFirstHoverTarget;
            boolean childIsHit = false;
            while (target != null) {
                final HoverTarget next = target.next;
                if (target.child == child) {
                    childIsHit = true;
                    break;
                }
                target = next;
            }
            if (!childIsHit) {
                target = mFirstHoverTarget;
                while (notEmpty && target != null) {
                    final HoverTarget next = target.next;
                    final View hoveredView = target.child;

                    rect.set(hoveredView.mLeft, hoveredView.mTop, hoveredView.mRight,
                            hoveredView.mBottom);
                    matrix.mapRect(rect);
                    notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
                            Math.round(rect.right), Math.round(rect.bottom), Region.Op.DIFFERENCE);
                    target = next;
                }
            }
        } else {
            TouchTarget target = mFirstTouchTarget;
            boolean childIsHit = false;
            while (target != null) {
                final TouchTarget next = target.next;
                if (target.child == child) {
                    childIsHit = true;
                    break;
                }
                target = next;
            }
            if (!childIsHit) {
                target = mFirstTouchTarget;
                while (notEmpty && target != null) {
                    final TouchTarget next = target.next;
                    final View touchedView = target.child;

                    rect.set(touchedView.mLeft, touchedView.mTop, touchedView.mRight,
                            touchedView.mBottom);
                    matrix.mapRect(rect);
                    notEmpty = region.op(Math.round(rect.left), Math.round(rect.top),
                            Math.round(rect.right), Math.round(rect.bottom), Region.Op.DIFFERENCE);
                    target = next;
                }
            }
        }

        if (notEmpty && mParent != null) {
            notEmpty = mParent.getChildLocalHitRegion(this, region, matrix, isHover);
        }
        return notEmpty;
    }


    private static void applyOpToRegionByBounds(Region region, View view, Region.Op op) {
        final int[] locationInWindow = new int[2];
        view.getLocationInWindow(locationInWindow);
+31 −0
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.view;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Bundle;
@@ -685,6 +686,36 @@ public interface ViewParent {
    default void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
    }

    /**
     * Compute the region where the child can receive the {@link MotionEvent}s from the root view.
     *
     * <p> Given region where the child will accept {@link MotionEvent}s.
     * Modify the region to the unblocked region where the child can receive the
     * {@link MotionEvent}s from the view root.
     * </p>
     *
     * <p> The given region is always clipped by the bounds of the parent views. When there are
     * on-going {@link MotionEvent}s, this method also makes use of the event dispatching results to
     * determine whether a sibling view will also block the child's hit region.
     * </p>
     *
     * @param child a child View, whose hit region we want to compute.
     * @param region the initial hit region where the child view will handle {@link MotionEvent}s,
     *              defined in the child coordinates. Will be overwritten to the result hit region.
     * @param matrix the matrix that maps the given child view's coordinates to the region
     *               coordinates. It will be modified to a matrix that maps window coordinates to
     *               the result region's coordinates.
     * @param isHover if true it will return the hover events' hit region, otherwise it will
     *               return the touch events' hit region.
     * @return true if the returned region is not empty.
     * @hide
     */
    default boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
            @NonNull Matrix matrix, boolean isHover) {
        region.setEmpty();
        return false;
    }

    /**
     * Unbuffered dispatch has been requested by a child of this view parent.
     * This method is called by the View hierarchy to signal ancestors that a View needs to
+17 −0
Original line number Diff line number Diff line
@@ -128,6 +128,7 @@ import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.RenderNode;
import android.graphics.drawable.Drawable;
@@ -2396,6 +2397,22 @@ public final class ViewRootImpl implements ViewParent,
        return r.intersect(0, 0, mWidth, mHeight);
    }

    @Override
    public boolean getChildLocalHitRegion(@NonNull View child, @NonNull Region region,
            @NonNull Matrix matrix, boolean isHover) {
        if (child != mView) {
            throw new IllegalArgumentException("child " + child + " is not the root view "
                    + mView + " managed by this ViewRootImpl");
        }

        RectF rectF = new RectF(0, 0, mWidth, mHeight);
        matrix.mapRect(rectF);
        // Note: don't apply scroll offset, because we want to know its
        // visibility in the virtual canvas being given to the view hierarchy.
        return region.op(Math.round(rectF.left), Math.round(rectF.top),
                Math.round(rectF.right), Math.round(rectF.bottom), Region.Op.INTERSECT);
    }

    @Override
    public void bringChildToFront(View child) {
    }
+9 −0
Original line number Diff line number Diff line
@@ -1749,6 +1749,15 @@
                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
            </intent-filter>
        </activity>

        <activity android:name="android.view.ViewGroupTestActivity"
                  android:label="ViewGroup Test"
                  android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
            </intent-filter>
        </activity>
    </application>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
Loading