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

Commit b79679f5 authored by Justin Ghan's avatar Justin Ghan Committed by Android (Google) Code Review
Browse files

Merge "Support handwriting delegation across different view trees" into udc-dev

parents 2cbf9414 2680cc74
Loading
Loading
Loading
Loading
+8 −8
Original line number Diff line number Diff line
@@ -50381,12 +50381,6 @@ package android.view {
    field public static final int VERTICAL_GRAVITY_MASK = 112; // 0x70
  }
  public class HandwritingDelegateConfiguration {
    ctor public HandwritingDelegateConfiguration(@IdRes int, @NonNull Runnable);
    method public int getDelegatorViewId();
    method @NonNull public Runnable getInitiationCallback();
  }
  public class HapticFeedbackConstants {
    field public static final int CLOCK_TICK = 4; // 0x4
    field public static final int CONFIRM = 16; // 0x10
@@ -51984,6 +51978,8 @@ package android.view {
    method @Nullable public CharSequence getAccessibilityPaneTitle();
    method @IdRes public int getAccessibilityTraversalAfter();
    method @IdRes public int getAccessibilityTraversalBefore();
    method @NonNull public String getAllowedHandwritingDelegatePackageName();
    method @NonNull public String getAllowedHandwritingDelegatorPackageName();
    method public float getAlpha();
    method public android.view.animation.Animation getAnimation();
    method @Nullable public android.graphics.Matrix getAnimationMatrix();
@@ -52039,7 +52035,7 @@ package android.view {
    method public float getHandwritingBoundsOffsetLeft();
    method public float getHandwritingBoundsOffsetRight();
    method public float getHandwritingBoundsOffsetTop();
    method @Nullable public android.view.HandwritingDelegateConfiguration getHandwritingDelegateConfiguration();
    method @Nullable public Runnable getHandwritingDelegatorCallback();
    method public final boolean getHasOverlappingRendering();
    method public final int getHeight();
    method public void getHitRect(android.graphics.Rect);
@@ -52195,6 +52191,7 @@ package android.view {
    method public boolean isFocused();
    method public final boolean isFocusedByDefault();
    method public boolean isForceDarkAllowed();
    method public boolean isHandwritingDelegate();
    method public boolean isHapticFeedbackEnabled();
    method public boolean isHardwareAccelerated();
    method public boolean isHorizontalFadingEdgeEnabled();
@@ -52365,6 +52362,8 @@ package android.view {
    method public void setAccessibilityTraversalBefore(@IdRes int);
    method public void setActivated(boolean);
    method public void setAllowClickWhenDisabled(boolean);
    method public void setAllowedHandwritingDelegatePackage(@NonNull String);
    method public void setAllowedHandwritingDelegatorPackage(@NonNull String);
    method public void setAlpha(@FloatRange(from=0.0, to=1.0) float);
    method public void setAnimation(android.view.animation.Animation);
    method public void setAnimationMatrix(@Nullable android.graphics.Matrix);
@@ -52407,7 +52406,7 @@ package android.view {
    method public void setForegroundTintList(@Nullable android.content.res.ColorStateList);
    method public void setForegroundTintMode(@Nullable android.graphics.PorterDuff.Mode);
    method public void setHandwritingBoundsOffsets(float, float, float, float);
    method public void setHandwritingDelegateConfiguration(@Nullable android.view.HandwritingDelegateConfiguration);
    method public void setHandwritingDelegatorCallback(@Nullable Runnable);
    method public void setHapticFeedbackEnabled(boolean);
    method public void setHasTransientState(boolean);
    method public void setHorizontalFadingEdgeEnabled(boolean);
@@ -52420,6 +52419,7 @@ package android.view {
    method public void setImportantForAutofill(int);
    method public void setImportantForContentCapture(int);
    method public void setIsCredential(boolean);
    method public void setIsHandwritingDelegate(boolean);
    method public void setKeepScreenOn(boolean);
    method public void setKeyboardNavigationCluster(boolean);
    method public void setLabelFor(@IdRes int);
+0 −74
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

import android.annotation.IdRes;
import android.annotation.NonNull;

/**
 * Configuration for a view to act as a handwriting initiation delegate. This allows handwriting
 * mode for a delegator editor view to be initiated by stylus movement on the delegate view.
 *
 * <p>If a stylus {@link MotionEvent} occurs within the delegate view's bounds, the callback
 * returned by {@link #getInitiationCallback()} will be called. The callback implementation is
 * expected to show and focus the delegator editor view. If a view with identifier matching {@link
 * #getDelegatorViewId()} creates an input connection while the same stylus {@link MotionEvent}
 * sequence is ongoing, handwriting mode will be initiated for that view.
 *
 * <p>A common use case is a custom view which looks like a text editor but does not actually
 * support text editing itself, and clicking on the custom view causes an EditText to be shown. To
 * support handwriting initiation in this case, {@link View#setHandwritingDelegateConfiguration} can
 * be called on the custom view to configure it as a delegate, and set the EditText as the delegator
 * by passing the EditText's identifier as the {@code delegatorViewId}. The {@code
 * initiationCallback} implementation is typically the same as the click listener implementation
 * which shows the EditText.
 */
public class HandwritingDelegateConfiguration {
    @IdRes private final int mDelegatorViewId;
    @NonNull private final Runnable mInitiationCallback;

    /**
     * Constructs a HandwritingDelegateConfiguration instance.
     *
     * @param delegatorViewId identifier of the delegator editor view for which handwriting mode
     *     should be initiated
     * @param initiationCallback callback called when a stylus {@link MotionEvent} occurs within
     *     this view's bounds. This will be called from the UI thread.
     */
    public HandwritingDelegateConfiguration(
            @IdRes int delegatorViewId, @NonNull Runnable initiationCallback) {
        mDelegatorViewId = delegatorViewId;
        mInitiationCallback = initiationCallback;
    }

    /**
     * Returns the identifier of the delegator editor view for which handwriting mode should be
     * initiated.
     */
    public int getDelegatorViewId() {
        return mDelegatorViewId;
    }

    /**
     * Returns the callback which should be called when a stylus {@link MotionEvent} occurs within
     * the delegate view's bounds. The callback should only be called from the UI thread.
     */
    @NonNull
    public Runnable getInitiationCallback() {
        return mInitiationCallback;
    }
}
+28 −22
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package android.view;

import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -172,15 +171,11 @@ public class HandwritingInitiator {
                    if (candidateView != null) {
                        if (candidateView == getConnectedView()) {
                            startHandwriting(candidateView);
                        } else if (candidateView.getHandwritingDelegateConfiguration() != null) {
                            mState.mDelegatorViewId =
                                    candidateView
                                            .getHandwritingDelegateConfiguration()
                                            .getDelegatorViewId();
                            candidateView
                                    .getHandwritingDelegateConfiguration()
                                    .getInitiationCallback()
                                    .run();
                        } else if (candidateView.getHandwritingDelegatorCallback() != null) {
                            mImm.prepareStylusHandwritingDelegation(
                                    candidateView,
                                    candidateView.getAllowedHandwritingDelegatePackageName());
                            candidateView.getHandwritingDelegatorCallback().run();
                        } else {
                            if (candidateView.getRevealOnFocusHint()) {
                                candidateView.setRevealOnFocusHint(false);
@@ -227,6 +222,9 @@ public class HandwritingInitiator {
        } else {
            mConnectedView = new WeakReference<>(view);
            mConnectionCount = 1;
            if (view.isHandwritingDelegate() && tryAcceptStylusHandwritingDelegation(view)) {
                return;
            }
            if (mState != null && mState.mShouldInitHandwriting) {
                tryStartHandwriting();
            }
@@ -279,9 +277,7 @@ public class HandwritingInitiator {
        }

        final Rect handwritingArea = getViewHandwritingArea(connectedView);
        if ((mState.mDelegatorViewId != View.NO_ID
                        && mState.mDelegatorViewId == connectedView.getId())
                || isInHandwritingArea(
        if (isInHandwritingArea(
                handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
            startHandwriting(connectedView);
        } else {
@@ -289,7 +285,7 @@ public class HandwritingInitiator {
        }
    }

    /** For test only. */
    /** Starts a stylus handwriting session for the view. */
    @VisibleForTesting
    public void startHandwriting(@NonNull View view) {
        mImm.startStylusHandwriting(view);
@@ -297,6 +293,23 @@ public class HandwritingInitiator {
        mState.mShouldInitHandwriting = false;
    }

    /**
     * Starts a stylus handwriting session for the delegate view, if {@link
     * InputMethodManager#prepareStylusHandwritingDelegation} was previously called.
     */
    @VisibleForTesting
    public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) {
        if (mImm.acceptStylusHandwritingDelegation(
                view, view.getAllowedHandwritingDelegatorPackageName())) {
            if (mState != null) {
                mState.mHasInitiatedHandwriting = true;
                mState.mShouldInitHandwriting = false;
            }
            return true;
        }
        return false;
    }

    /**
     * Notify that the handwriting area for the given view might be updated.
     * @param view the view whose handwriting area might be updated.
@@ -542,13 +555,6 @@ public class HandwritingInitiator {
         * built InputConnection.
         */
        private boolean mExceedHandwritingSlop;
        /**
         * If the current ongoing stylus MotionEvent sequence started over a handwriting initiation
         * delegate view, then this is the view identifier of the corresponding delegator view. If
         * the delegator view creates an input connection while the MotionEvent sequence is still
         * ongoing, then handwriting mode will be initiated for the delegator view.
         */
        @IdRes private int mDelegatorViewId = View.NO_ID;

        /** The pointer id of the stylus pointer that is being tracked. */
        private final int mStylusPointerId;
+160 −18
Original line number Diff line number Diff line
@@ -5101,12 +5101,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
     */
    private boolean mHoveringTouchDelegate = false;
    /**
     * Configuration for this view to act as a handwriting initiation delegate. This allows
     * handwriting mode for a delegator editor view to be initiated by stylus movement on this
     * delegate view.
     */
    private HandwritingDelegateConfiguration mHandwritingDelegateConfiguration;
    // These two fields are set if the view is a handwriting delegator.
    private Runnable mHandwritingDelegatorCallback;
    private String mAllowedHandwritingDelegatePackageName;
    // These two fields are set if the view is a handwriting delegate.
    private boolean mIsHandwritingDelegate;
    private String mAllowedHandwritingDelegatorPackageName;
    /**
     * Solid color to use as a background when creating the drawing cache. Enables
@@ -12410,27 +12411,168 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    }
    /**
     * Configures this view to act as a handwriting initiation delegate. This allows handwriting
     * mode for a delegator editor view to be initiated by stylus movement on this delegate view.
     * Sets a callback which should be called when a stylus {@link MotionEvent} occurs within this
     * view's bounds. The callback will be called from the UI thread.
     *
     * <p>Setting a callback allows this view to act as a handwriting delegator, so that handwriting
     * mode for a delegate editor view can be initiated by stylus movement on this delegator view.
     * The callback implementation is expected to show and focus the delegate editor view. If a view
     * which returns {@code true} for {@link #isHandwritingDelegate()} creates an input connection
     * while the same stylus {@link MotionEvent} sequence is ongoing, handwriting mode will be
     * initiated for that view.
     *
     * <p>A common use case is a custom view which looks like a text editor but does not actually
     * support text editing itself, and clicking on the custom view causes an EditText to be shown.
     * To support handwriting initiation in this case, this method can be called on the custom view
     * to configure it as a delegator. The EditText should call {@link #setIsHandwritingDelegate} to
     * set it as a delegate. The {@code callback} implementation is typically the same as the click
     * listener implementation which shows the EditText.
     *
     * <p>If {@code null} is passed, this view will no longer act as a handwriting initiation
     * delegate.
     * delegator.
     *
     * @param callback a callback which should be called when a stylus {@link MotionEvent} occurs
     *     within this view's bounds
     */
    public void setHandwritingDelegateConfiguration(
            @Nullable HandwritingDelegateConfiguration configuration) {
        mHandwritingDelegateConfiguration = configuration;
        if (configuration != null) {
    public void setHandwritingDelegatorCallback(@Nullable Runnable callback) {
        mHandwritingDelegatorCallback = callback;
        if (callback != null) {
            // By default, the delegate must be from the same package as the delegator view.
            mAllowedHandwritingDelegatePackageName = mContext.getOpPackageName();
            setHandwritingArea(new Rect(0, 0, getWidth(), getHeight()));
        } else {
            mAllowedHandwritingDelegatePackageName = null;
        }
    }
    /**
     * If this view has been configured as a handwriting initiation delegate, returns the delegate
     * configuration.
     * Returns the callback set by {@link #setHandwritingDelegatorCallback} which should be called
     * when a stylus {@link MotionEvent} occurs within this view's bounds. The callback should only
     * be called from the UI thread.
     */
    @Nullable
    public HandwritingDelegateConfiguration getHandwritingDelegateConfiguration() {
        return mHandwritingDelegateConfiguration;
    public Runnable getHandwritingDelegatorCallback() {
        return mHandwritingDelegatorCallback;
    }
    /**
     * Specifies that this view may act as a handwriting initiation delegator for a delegate editor
     * view from the specified package. If this method is not called, delegators may only be used to
     * initiate handwriting mode for a delegate editor view from the same package as the delegator
     * view. This method allows specifying a different trusted package which may contain a delegate
     * editor view linked to this delegator view. This should be called after {@link
     * #setHandwritingDelegatorCallback}.
     *
     * <p>If this method is called on the delegator view, then {@link
     * #setAllowedHandwritingDelegatorPackage} should also be called on the delegate editor view.
     *
     * <p>For example, to configure a delegator view in package 1:
     *
     * <pre>
     * delegatorView.setHandwritingDelegatorCallback(callback);
     * delegatorView.setAllowedHandwritingDelegatePackage(package2);</pre>
     *
     * Then to configure the corresponding delegate editor view in package 2:
     *
     * <pre>
     * delegateEditorView.setIsHandwritingDelegate(true);
     * delegateEditorView.setAllowedHandwritingDelegatorPackage(package1);</pre>
     *
     * @param allowedPackageName the package name of a delegate editor view linked to this delegator
     *     view
     * @throws IllegalStateException If the view has not been configured as a handwriting delegator
     *     using {@link #setHandwritingDelegatorCallback}.
     */
    public void setAllowedHandwritingDelegatePackage(@NonNull String allowedPackageName) {
        if (mHandwritingDelegatorCallback == null) {
            throw new IllegalStateException("This view is not a handwriting delegator.");
        }
        mAllowedHandwritingDelegatePackageName = allowedPackageName;
    }
    /**
     * Returns the allowed package for delegate editor views for which this view may act as a
     * handwriting delegator. If {@link #setAllowedHandwritingDelegatePackage} has not been called,
     * this will return this view's package name, since by default delegators may only be used to
     * initiate handwriting mode for a delegate editor view from the same package as the delegator
     * view. This will return a different allowed package if set by {@link
     * #setAllowedHandwritingDelegatePackage}.
     *
     * @throws IllegalStateException If the view has not been configured as a handwriting delegator
     *     using {@link #setHandwritingDelegatorCallback}.
     */
    @NonNull
    public String getAllowedHandwritingDelegatePackageName() {
        if (mHandwritingDelegatorCallback == null) {
            throw new IllegalStateException("This view is not a handwriting delegator.");
        }
        return mAllowedHandwritingDelegatePackageName;
    }
    /**
     * Sets this view to be a handwriting delegate. If a delegate view creates an input connection
     * while a stylus {@link MotionEvent} sequence from a delegator view is ongoing, handwriting
     * mode will be initiated for the delegate view.
     *
     * @param isHandwritingDelegate whether this view is a handwriting initiation delegate
     * @see #setHandwritingDelegatorCallback(Runnable)
     */
    public void setIsHandwritingDelegate(boolean isHandwritingDelegate) {
        mIsHandwritingDelegate = isHandwritingDelegate;
        if (mIsHandwritingDelegate) {
            // By default, the delegator must be from the same package as the delegate view.
            mAllowedHandwritingDelegatorPackageName = mContext.getOpPackageName();
        } else {
            mAllowedHandwritingDelegatePackageName = null;
        }
    }
    /**
     * Returns whether this view has been set as a handwriting delegate by {@link
     * #setIsHandwritingDelegate}.
     */
    public boolean isHandwritingDelegate() {
        return mIsHandwritingDelegate;
    }
    /**
     * Specifies that a view from the specified package may act as a handwriting delegator for this
     * delegate editor view. If this method is not called, only views from the same package as the
     * delegate editor view may act as a handwriting delegator. This method allows specifying a
     * different trusted package which may contain a delegator view linked to this delegate editor
     * view. This should be called after {@link #setIsHandwritingDelegate}.
     *
     * <p>If this method is called on the delegate editor view, then {@link
     * #setAllowedHandwritingDelegatePackage} should also be called on the delegator view.
     *
     * @param allowedPackageName the package name of a delegator view linked to this delegate editor
     *     view
     * @throws IllegalStateException If the view has not been configured as a handwriting delegate
     *     using {@link #setIsHandwritingDelegate}.
     */
    public void setAllowedHandwritingDelegatorPackage(@NonNull String allowedPackageName) {
        if (!mIsHandwritingDelegate) {
            throw new IllegalStateException("This view is not a handwriting delegate.");
        }
        mAllowedHandwritingDelegatorPackageName = allowedPackageName;
    }
    /**
     * Returns the allowed package for views which may act as a handwriting delegator for this
     * delegate editor view. If {@link #setAllowedHandwritingDelegatorPackage} has not been called,
     * this will return this view's package name, since by default only views from the same package
     * as the delegator editor view may act as a handwriting delegator. This will return a different
     * allowed package if set by {@link #setAllowedHandwritingDelegatorPackage}.
     *
     * @throws IllegalStateException If the view has not been configured as a handwriting delegate
     *     using {@link #setIsHandwritingDelegate}.
     */
    @NonNull
    public String getAllowedHandwritingDelegatorPackageName() {
        if (!mIsHandwritingDelegate) {
            throw new IllegalStateException("This view is not a handwriting delegate.");
        }
        return mAllowedHandwritingDelegatorPackageName;
    }
    /**
@@ -24474,7 +24616,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            }
        }
        rebuildOutline();
        if (onCheckIsTextEditor() || mHandwritingDelegateConfiguration != null) {
        if (onCheckIsTextEditor() || mHandwritingDelegatorCallback != null) {
            setHandwritingArea(new Rect(0, 0, newWidth, newHeight));
        }
    }
+5 −9
Original line number Diff line number Diff line
@@ -32,7 +32,6 @@ import android.app.Instrumentation;
import android.content.Context;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.view.HandwritingDelegateConfiguration;
import android.view.HandwritingInitiator;
import android.view.InputDevice;
import android.view.MotionEvent;
@@ -210,14 +209,11 @@ public class HandwritingInitiatorTest {

    @Test
    public void onTouchEvent_startHandwriting_delegate() {
        int delegatorViewId = 234;
        View delegatorView = new View(mContext);
        delegatorView.setId(delegatorViewId);
        View delegateView = new View(mContext);
        delegateView.setIsHandwritingDelegate(true);

        mTestView.setHandwritingDelegateConfiguration(
                new HandwritingDelegateConfiguration(
                        delegatorViewId,
                        () -> mHandwritingInitiator.onInputConnectionCreated(delegatorView)));
        mTestView.setHandwritingDelegatorCallback(
                () -> mHandwritingInitiator.onInputConnectionCreated(delegateView));

        final int x1 = (sHwArea.left + sHwArea.right) / 2;
        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
@@ -229,7 +225,7 @@ public class HandwritingInitiatorTest {
        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
        mHandwritingInitiator.onTouchEvent(stylusEvent2);

        verify(mHandwritingInitiator, times(1)).startHandwriting(delegatorView);
        verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView);
    }

    @Test