diff --git a/core/api/current.txt b/core/api/current.txt index f770e272098f5..7f75b2f5175b5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -50379,12 +50379,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 @@ -51982,6 +51976,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(); @@ -52037,7 +52033,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); @@ -52193,6 +52189,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(); @@ -52363,6 +52360,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); @@ -52405,7 +52404,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); @@ -52418,6 +52417,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); diff --git a/core/java/android/view/HandwritingDelegateConfiguration.java b/core/java/android/view/HandwritingDelegateConfiguration.java deleted file mode 100644 index 719c614f42f0e..0000000000000 --- a/core/java/android/view/HandwritingDelegateConfiguration.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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. - * - *

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. - * - *

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; - } -} diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java index 8d221ab0b70a9..98f61a3663810 100644 --- a/core/java/android/view/HandwritingInitiator.java +++ b/core/java/android/view/HandwritingInitiator.java @@ -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,17 +277,15 @@ public class HandwritingInitiator { } final Rect handwritingArea = getViewHandwritingArea(connectedView); - if ((mState.mDelegatorViewId != View.NO_ID - && mState.mDelegatorViewId == connectedView.getId()) - || isInHandwritingArea( - handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) { + if (isInHandwritingArea( + handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) { startHandwriting(connectedView); } else { mState.mShouldInitHandwriting = false; } } - /** 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; diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 7d18bf082bcbe..fee20517a499d 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -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. + * + *

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. + * + *

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. * *

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}. + * + *

If this method is called on the delegator view, then {@link + * #setAllowedHandwritingDelegatorPackage} should also be called on the delegate editor view. + * + *

For example, to configure a delegator view in package 1: + * + *

+     * delegatorView.setHandwritingDelegatorCallback(callback);
+     * delegatorView.setAllowedHandwritingDelegatePackage(package2);
+ * + * Then to configure the corresponding delegate editor view in package 2: + * + *
+     * delegateEditorView.setIsHandwritingDelegate(true);
+     * delegateEditorView.setAllowedHandwritingDelegatorPackage(package1);
+ * + * @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}. + * + *

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)); } } diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java index 95aa5d0de1194..76f5277cfe0b3 100644 --- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java @@ -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