diff options
| -rw-r--r-- | core/api/current.txt | 8 | ||||
| -rw-r--r-- | core/java/android/view/HandwritingDelegateConfiguration.java | 74 | ||||
| -rw-r--r-- | core/java/android/view/HandwritingInitiator.java | 28 | ||||
| -rw-r--r-- | core/java/android/view/View.java | 33 | ||||
| -rw-r--r-- | core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java | 25 |
5 files changed, 165 insertions, 3 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index eef2324c7a6c..2b85729eb217 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -48550,6 +48550,12 @@ 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 @@ -50160,6 +50166,7 @@ package android.view { method public float getHandwritingBoundsOffsetLeft(); method public float getHandwritingBoundsOffsetRight(); method public float getHandwritingBoundsOffsetTop(); + method @Nullable public android.view.HandwritingDelegateConfiguration getHandwritingDelegateConfiguration(); method public final boolean getHasOverlappingRendering(); method public final int getHeight(); method public void getHitRect(android.graphics.Rect); @@ -50526,6 +50533,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 setHapticFeedbackEnabled(boolean); method public void setHasTransientState(boolean); method public void setHorizontalFadingEdgeEnabled(boolean); diff --git a/core/java/android/view/HandwritingDelegateConfiguration.java b/core/java/android/view/HandwritingDelegateConfiguration.java new file mode 100644 index 000000000000..524bb4ca68ab --- /dev/null +++ b/core/java/android/view/HandwritingDelegateConfiguration.java @@ -0,0 +1,74 @@ +/* + * 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 + */ + 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. + */ + @NonNull + public Runnable getInitiationCallback() { + return mInitiationCallback; + } +} diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index a0a07b30662f..2e4073e9cfd0 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -16,6 +16,7 @@ package android.view; +import android.annotation.IdRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; @@ -161,6 +162,15 @@ 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.getRevealOnFocusHint()) { candidateView.setRevealOnFocusHint(false); @@ -259,8 +269,10 @@ public class HandwritingInitiator { } final Rect handwritingArea = getViewHandwritingArea(connectedView); - if (isInHandwritingArea(handwritingArea, mState.mStylusDownX, - mState.mStylusDownY, connectedView)) { + if ((mState.mDelegatorViewId != View.NO_ID + && mState.mDelegatorViewId == connectedView.getId()) + || isInHandwritingArea( + handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) { startHandwriting(connectedView); } else { mState.mShouldInitHandwriting = false; @@ -287,6 +299,11 @@ public class HandwritingInitiator { if (!view.isAutoHandwritingEnabled()) { return false; } + // The view may be a handwriting initiation delegate, in which case it is not the editor + // view for which handwriting would be started. However, in almost all cases, the return + // values of View#isStylusHandwritingAvailable will be the same for the delegate view and + // the delegator editor view. So the delegate view can be used to decide whether handwriting + // should be triggered. return view.isStylusHandwritingAvailable(); } @@ -473,6 +490,13 @@ 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; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 7b6ebf7cd9b8..49d9e67ce51d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -5063,6 +5063,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; + + /** * Solid color to use as a background when creating the drawing cache. Enables * the cache to use 16 bit bitmaps instead of 32 bit. */ @@ -12255,6 +12262,30 @@ 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. + * + * <p>If {@code null} is passed, this view will no longer act as a handwriting initiation + * delegate. + */ + public void setHandwritingDelegateConfiguration( + @Nullable HandwritingDelegateConfiguration configuration) { + mHandwritingDelegateConfiguration = configuration; + if (configuration != null) { + setHandwritingArea(new Rect(0, 0, getWidth(), getHeight())); + } + } + + /** + * If this view has been configured as a handwriting initiation delegate, returns the delegate + * configuration. + */ + @Nullable + public HandwritingDelegateConfiguration getHandwritingDelegateConfiguration() { + return mHandwritingDelegateConfiguration; + } + + /** * Gets the coordinates of this view in the coordinate space of the * {@link Surface} that contains the view. * @@ -24205,7 +24236,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } } rebuildOutline(); - if (onCheckIsTextEditor()) { + if (onCheckIsTextEditor() || mHandwritingDelegateConfiguration != null) { setHandwritingArea(new Rect(0, 0, newWidth, newHeight)); } } diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index d4a663221cd7..95aa5d0de119 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -32,6 +32,7 @@ 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; @@ -208,6 +209,30 @@ public class HandwritingInitiatorTest { } @Test + public void onTouchEvent_startHandwriting_delegate() { + int delegatorViewId = 234; + View delegatorView = new View(mContext); + delegatorView.setId(delegatorViewId); + + mTestView.setHandwritingDelegateConfiguration( + new HandwritingDelegateConfiguration( + delegatorViewId, + () -> mHandwritingInitiator.onInputConnectionCreated(delegatorView))); + + final int x1 = (sHwArea.left + sHwArea.right) / 2; + final int y1 = (sHwArea.top + sHwArea.bottom) / 2; + MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent1); + + final int x2 = x1 + mHandwritingSlop * 2; + final int y2 = y1; + MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); + mHandwritingInitiator.onTouchEvent(stylusEvent2); + + verify(mHandwritingInitiator, times(1)).startHandwriting(delegatorView); + } + + @Test public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() { final Rect rect = new Rect(600, 600, 900, 900); final View testView = createView(rect, true /* autoHandwritingEnabled */, |