summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/api/current.txt8
-rw-r--r--core/java/android/view/HandwritingDelegateConfiguration.java74
-rw-r--r--core/java/android/view/HandwritingInitiator.java28
-rw-r--r--core/java/android/view/View.java33
-rw-r--r--core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java25
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 */,