Loading core/java/android/view/HandwritingInitiator.java +50 −14 Original line number Diff line number Diff line Loading @@ -225,6 +225,7 @@ public class HandwritingInitiator { View candidateView = findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY, /* isHover */ false); if (candidateView != null && candidateView.isEnabled()) { boolean candidateHasFocus = candidateView.hasFocus(); if (shouldShowHandwritingUnavailableMessageForView(candidateView)) { int messagesResId = (candidateView instanceof TextView tv && tv.isAnyPasswordInputType()) Loading @@ -243,23 +244,24 @@ public class HandwritingInitiator { | MotionEvent.ACTION_CANCEL); candidateView.getRootView().dispatchTouchEvent(motionEvent); } else if (candidateView == getConnectedOrFocusedView()) { if (!mInitiateWithoutConnection && !candidateView.hasFocus()) { if (!candidateHasFocus) { requestFocusWithoutReveal(candidateView); } startHandwriting(candidateView); } else if (candidateView.getHandwritingDelegatorCallback() != null) { prepareDelegation(candidateView); } else { if (!mInitiateWithoutConnection) { mState.mPendingConnectedView = new WeakReference<>(candidateView); if (mInitiateWithoutConnection) { if (!candidateHasFocus) { // schedule for view focus. mState.mPendingFocusedView = new WeakReference<>(candidateView); requestFocusWithoutReveal(candidateView); } if (!candidateView.hasFocus()) { } else { mState.mPendingConnectedView = new WeakReference<>(candidateView); if (!candidateHasFocus) { requestFocusWithoutReveal(candidateView); } if (mInitiateWithoutConnection && updateFocusedView(candidateView, /* fromTouchEvent */ true)) { startHandwriting(candidateView); } } } Loading @@ -285,6 +287,9 @@ public class HandwritingInitiator { * gained focus. */ public void onDelegateViewFocused(@NonNull View view) { if (mInitiateWithoutConnection) { onEditorFocused(view); } if (view == getConnectedView()) { tryAcceptStylusHandwritingDelegation(view); } Loading Loading @@ -331,6 +336,33 @@ public class HandwritingInitiator { } } /** * Notify HandwritingInitiator that a new editor is focused. * @param view the view that received focus. */ @VisibleForTesting public void onEditorFocused(@NonNull View view) { if (!mInitiateWithoutConnection) { return; } if (!view.isAutoHandwritingEnabled()) { clearFocusedView(view); return; } final View focusedView = getFocusedView(); if (focusedView == view) { return; } updateFocusedView(view); if (mState != null && mState.mPendingFocusedView != null && mState.mPendingFocusedView.get() == view) { startHandwriting(view); } } /** * Notify HandwritingInitiator that the InputConnection has closed for the given view. * The caller of this method should guarantee that each onInputConnectionClosed call Loading Loading @@ -378,7 +410,7 @@ public class HandwritingInitiator { * @return {@code true} if handwriting can initiate for given view. */ @VisibleForTesting public boolean updateFocusedView(@NonNull View view, boolean fromTouchEvent) { public boolean updateFocusedView(@NonNull View view) { if (!view.shouldInitiateHandwriting()) { mFocusedView = null; return false; Loading @@ -390,9 +422,7 @@ public class HandwritingInitiator { // A new view just gain focus. By default, we should show hover icon for it. mShowHoverIconForConnectedView = true; } if (!fromTouchEvent && view.isHandwritingDelegate()) { tryAcceptStylusHandwritingDelegation(view); } return true; } Loading Loading @@ -861,6 +891,12 @@ public class HandwritingInitiator { */ private WeakReference<View> mPendingConnectedView = null; /** * A view which has requested focus and is yet to receive it. * When view receives focus, a handwriting session should be started for the view. */ private WeakReference<View> mPendingFocusedView = null; /** The pointer id of the stylus pointer that is being tracked. */ private final int mStylusPointerId; /** The time stamp when the stylus pointer goes down. */ Loading core/java/android/view/View.java +10 −4 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.flags.Flags.viewVelocityApi; import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; Loading Loading @@ -8669,11 +8670,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onFocusLost(); } else if (hasWindowFocus()) { notifyFocusChangeToImeFocusController(true /* hasFocus */); if (mIsHandwritingDelegate) { ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null) { if (mIsHandwritingDelegate) { viewRoot.getHandwritingInitiator().onDelegateViewFocused(this); } else if (initiationWithoutInputConnection() && onCheckIsTextEditor()) { viewRoot.getHandwritingInitiator().onEditorFocused(this); } } } Loading Loading @@ -16702,6 +16704,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onFocusLost(); } else if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { notifyFocusChangeToImeFocusController(true /* hasFocus */); ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && initiationWithoutInputConnection() && onCheckIsTextEditor()) { viewRoot.getHandwritingInitiator().onEditorFocused(this); } } refreshDrawableState(); core/java/android/view/inputmethod/InputMethodManager.java +0 −4 Original line number Diff line number Diff line Loading @@ -3499,10 +3499,6 @@ public final class InputMethodManager { return false; } mServedView = mNextServedView; if (initiationWithoutInputConnection() && mServedView.isHandwritingDelegate()) { mServedView.getViewRootImpl().getHandwritingInitiator().onDelegateViewFocused( mServedView); } if (mServedInputConnection != null) { mServedInputConnection.finishComposingTextFromImm(); } Loading core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +45 −76 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; Loading Loading @@ -135,7 +136,7 @@ public class HandwritingInitiatorTest { when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0); mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -172,7 +173,7 @@ public class HandwritingInitiatorTest { when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(2); mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -202,7 +203,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwritingOnce_when_stylusMoveMultiTimes_withinHWArea() { mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -246,9 +247,7 @@ public class HandwritingInitiatorTest { when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0); if (!mInitiateWithoutConnection) { mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2; final int y1 = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -284,13 +283,7 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); if (mInitiateWithoutConnection) { // Focus is changed after stylus movement. mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); } else { // InputConnection is created after stylus movement. mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView1); verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); } Loading @@ -312,24 +305,11 @@ public class HandwritingInitiatorTest { final int y2 = y1; MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); if (!mInitiateWithoutConnection) { // First create InputConnection for mTestView2 and verify that handwriting is not // started. mHandwritingInitiator.onInputConnectionCreated(mTestView2); } onEditorFocusedOrConnectionCreated(mTestView2); // Note: mTestView2 receives focus when initiationWithoutInputConnection() is enabled. // verify that handwriting is not started. verify(mHandwritingInitiator, never()).startHandwriting(mTestView2); if (mInitiateWithoutConnection) { // Focus is changed after stylus movement. mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); } else { // Next create InputConnection for mTextView1. Handwriting is started for this view // since the stylus down point is closest to this view. mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView1); // Handwriting is started for this view since the stylus down point is closest to this // view. verify(mHandwritingInitiator).startHandwriting(mTestView1); Loading @@ -351,7 +331,7 @@ public class HandwritingInitiatorTest { delegateView.setIsHandwritingDelegate(true); mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.onInputConnectionCreated(delegateView)); () -> onEditorFocusedOrConnectionCreated(delegateView)); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; Loading @@ -371,17 +351,15 @@ public class HandwritingInitiatorTest { public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() { View delegateView = new EditText(mContext); delegateView.setIsHandwritingDelegate(true); if (mInitiateWithoutConnection) { mHandwritingInitiator.onEditorFocused(delegateView); } mHandwritingInitiator.onInputConnectionCreated(delegateView); reset(mHandwritingInitiator); if (mInitiateWithoutConnection) { mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.updateFocusedView( delegateView, /*fromTouchEvent*/ false)); } else { mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.onDelegateViewFocused(delegateView)); } final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; Loading @@ -393,7 +371,7 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView); verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(any()); } @Test Loading Loading @@ -431,14 +409,6 @@ public class HandwritingInitiatorTest { assertThat(onTouchEventResult4).isTrue(); } private void callOnInputConnectionOrUpdateViewFocus(View view) { if (mInitiateWithoutConnection) { mHandwritingInitiator.updateFocusedView(view, /*fromTouchEvent*/ true); } else { mHandwritingInitiator.onInputConnectionCreated(view); } } @Test public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() { final Rect rect = new Rect(600, 600, 900, 900); Loading @@ -446,7 +416,7 @@ public class HandwritingInitiatorTest { false /* isStylusHandwritingAvailable */); mHandwritingInitiator.updateHandwritingAreasForView(testView); callOnInputConnectionOrUpdateViewFocus(testView); onEditorFocusedOrConnectionCreated(testView); final int x1 = (rect.left + rect.right) / 2; final int y1 = (rect.top + rect.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading @@ -465,7 +435,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() { callOnInputConnectionOrUpdateViewFocus(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = 200; final int y1 = 200; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading @@ -481,7 +451,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() { callOnInputConnectionOrUpdateViewFocus(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = 10; final int y1 = 10; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading @@ -497,7 +467,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTimeOut() { callOnInputConnectionOrUpdateViewFocus(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = 10; final int y1 = 10; final long time1 = 10L; Loading Loading @@ -553,9 +523,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_focusView_inputConnectionAlreadyBuilt_stylusMoveOnce_withinHWArea() { if (!mInitiateWithoutConnection) { mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -608,14 +576,14 @@ public class HandwritingInitiatorTest { verify(mTestView2, times(1)).requestFocus(); callOnInputConnectionOrUpdateViewFocus(mTestView2); onEditorFocusedOrConnectionCreated(mTestView2); verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView2); } @Test public void onTouchEvent_handwritingAreaOverlapped_focusedViewHasPriority() { // Simulate the case where mTestView1 is focused. callOnInputConnectionOrUpdateViewFocus(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); // The ACTION_DOWN location is within the handwriting bounds of both mTestView1 and // mTestView2. Although it's closer to mTestView2's handwriting bounds, handwriting is // initiated for mTestView1 because it's focused. Loading Loading @@ -653,7 +621,7 @@ public class HandwritingInitiatorTest { @Test public void onResolvePointerIcon_afterHandwriting_hidePointerIconForConnectedView() { // simulate the case where sTestView1 is focused. mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), /* exceedsHWSlop */ true); // Verify that handwriting started for sTestView1. Loading @@ -679,15 +647,14 @@ public class HandwritingInitiatorTest { public void onResolvePointerIcon_afterHandwriting_hidePointerIconForDelegatorView() { // Set mTextView2 to be the delegate of mTestView1. mTestView2.setIsHandwritingDelegate(true); if (mInitiateWithoutConnection) { mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.updateFocusedView( mTestView2, /*fromTouchEvent*/ false)); } else { mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2)); () -> { if (mInitiateWithoutConnection) { mHandwritingInitiator.updateFocusedView(mTestView2); } mHandwritingInitiator.onInputConnectionCreated(mTestView2); }); injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), /* exceedsHWSlop */ true); // Prerequisite check, verify that handwriting started for delegateView. Loading @@ -702,7 +669,7 @@ public class HandwritingInitiatorTest { @Test public void onResolvePointerIcon_showHoverIconAfterTap() { // Simulate the case where sTestView1 is focused. mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), /* exceedsHWSlop */ true); // Verify that handwriting started for sTestView1. Loading @@ -724,7 +691,7 @@ public class HandwritingInitiatorTest { @Test public void onResolvePointerIcon_showHoverIconAfterFocusChange() { // Simulate the case where sTestView1 is focused. mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), /* exceedsHWSlop */ true); // Verify that handwriting started for sTestView1. Loading @@ -735,14 +702,8 @@ public class HandwritingInitiatorTest { // After handwriting is initiated for the connected view, hide the hover icon. assertThat(icon1).isNull(); // Simulate that focus is switched to mTestView2 first and then switched back. if (mInitiateWithoutConnection) { mHandwritingInitiator.updateFocusedView(mTestView2, /*fromTouchEvent*/ true); mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); } else { mHandwritingInitiator.onInputConnectionCreated(mTestView2); mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView2); onEditorFocusedOrConnectionCreated(mTestView1); PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1); // After the change of focus, hover icon shows again. Loading @@ -754,11 +715,11 @@ public class HandwritingInitiatorTest { if (mInitiateWithoutConnection) { mTestView1.setAutoHandwritingEnabled(false); mTestView1.setHandwritingDelegatorCallback(null); mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); onEditorFocusedOrConnectionCreated(mTestView1); } else { View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */, true /* isStylusHandwritingAvailable */); mHandwritingInitiator.onInputConnectionCreated(mockView); onEditorFocusedOrConnectionCreated(mockView); } final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; Loading Loading @@ -974,4 +935,12 @@ public class HandwritingInitiatorTest { 1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_STYLUS, 0 /* flags */); } private void onEditorFocusedOrConnectionCreated(View testView) { if (Flags.initiationWithoutInputConnection()) { mHandwritingInitiator.onEditorFocused(testView); } else { mHandwritingInitiator.onInputConnectionCreated(testView); } } } Loading
core/java/android/view/HandwritingInitiator.java +50 −14 Original line number Diff line number Diff line Loading @@ -225,6 +225,7 @@ public class HandwritingInitiator { View candidateView = findBestCandidateView(mState.mStylusDownX, mState.mStylusDownY, /* isHover */ false); if (candidateView != null && candidateView.isEnabled()) { boolean candidateHasFocus = candidateView.hasFocus(); if (shouldShowHandwritingUnavailableMessageForView(candidateView)) { int messagesResId = (candidateView instanceof TextView tv && tv.isAnyPasswordInputType()) Loading @@ -243,23 +244,24 @@ public class HandwritingInitiator { | MotionEvent.ACTION_CANCEL); candidateView.getRootView().dispatchTouchEvent(motionEvent); } else if (candidateView == getConnectedOrFocusedView()) { if (!mInitiateWithoutConnection && !candidateView.hasFocus()) { if (!candidateHasFocus) { requestFocusWithoutReveal(candidateView); } startHandwriting(candidateView); } else if (candidateView.getHandwritingDelegatorCallback() != null) { prepareDelegation(candidateView); } else { if (!mInitiateWithoutConnection) { mState.mPendingConnectedView = new WeakReference<>(candidateView); if (mInitiateWithoutConnection) { if (!candidateHasFocus) { // schedule for view focus. mState.mPendingFocusedView = new WeakReference<>(candidateView); requestFocusWithoutReveal(candidateView); } if (!candidateView.hasFocus()) { } else { mState.mPendingConnectedView = new WeakReference<>(candidateView); if (!candidateHasFocus) { requestFocusWithoutReveal(candidateView); } if (mInitiateWithoutConnection && updateFocusedView(candidateView, /* fromTouchEvent */ true)) { startHandwriting(candidateView); } } } Loading @@ -285,6 +287,9 @@ public class HandwritingInitiator { * gained focus. */ public void onDelegateViewFocused(@NonNull View view) { if (mInitiateWithoutConnection) { onEditorFocused(view); } if (view == getConnectedView()) { tryAcceptStylusHandwritingDelegation(view); } Loading Loading @@ -331,6 +336,33 @@ public class HandwritingInitiator { } } /** * Notify HandwritingInitiator that a new editor is focused. * @param view the view that received focus. */ @VisibleForTesting public void onEditorFocused(@NonNull View view) { if (!mInitiateWithoutConnection) { return; } if (!view.isAutoHandwritingEnabled()) { clearFocusedView(view); return; } final View focusedView = getFocusedView(); if (focusedView == view) { return; } updateFocusedView(view); if (mState != null && mState.mPendingFocusedView != null && mState.mPendingFocusedView.get() == view) { startHandwriting(view); } } /** * Notify HandwritingInitiator that the InputConnection has closed for the given view. * The caller of this method should guarantee that each onInputConnectionClosed call Loading Loading @@ -378,7 +410,7 @@ public class HandwritingInitiator { * @return {@code true} if handwriting can initiate for given view. */ @VisibleForTesting public boolean updateFocusedView(@NonNull View view, boolean fromTouchEvent) { public boolean updateFocusedView(@NonNull View view) { if (!view.shouldInitiateHandwriting()) { mFocusedView = null; return false; Loading @@ -390,9 +422,7 @@ public class HandwritingInitiator { // A new view just gain focus. By default, we should show hover icon for it. mShowHoverIconForConnectedView = true; } if (!fromTouchEvent && view.isHandwritingDelegate()) { tryAcceptStylusHandwritingDelegation(view); } return true; } Loading Loading @@ -861,6 +891,12 @@ public class HandwritingInitiator { */ private WeakReference<View> mPendingConnectedView = null; /** * A view which has requested focus and is yet to receive it. * When view receives focus, a handwriting session should be started for the view. */ private WeakReference<View> mPendingFocusedView = null; /** The pointer id of the stylus pointer that is being tracked. */ private final int mStylusPointerId; /** The time stamp when the stylus pointer goes down. */ Loading
core/java/android/view/View.java +10 −4 Original line number Diff line number Diff line Loading @@ -44,6 +44,7 @@ import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.flags.Flags.viewVelocityApi; import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; import static android.view.inputmethod.Flags.initiationWithoutInputConnection; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; Loading Loading @@ -8669,11 +8670,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onFocusLost(); } else if (hasWindowFocus()) { notifyFocusChangeToImeFocusController(true /* hasFocus */); if (mIsHandwritingDelegate) { ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null) { if (mIsHandwritingDelegate) { viewRoot.getHandwritingInitiator().onDelegateViewFocused(this); } else if (initiationWithoutInputConnection() && onCheckIsTextEditor()) { viewRoot.getHandwritingInitiator().onEditorFocused(this); } } } Loading Loading @@ -16702,6 +16704,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, onFocusLost(); } else if ((mPrivateFlags & PFLAG_FOCUSED) != 0) { notifyFocusChangeToImeFocusController(true /* hasFocus */); ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && initiationWithoutInputConnection() && onCheckIsTextEditor()) { viewRoot.getHandwritingInitiator().onEditorFocused(this); } } refreshDrawableState();
core/java/android/view/inputmethod/InputMethodManager.java +0 −4 Original line number Diff line number Diff line Loading @@ -3499,10 +3499,6 @@ public final class InputMethodManager { return false; } mServedView = mNextServedView; if (initiationWithoutInputConnection() && mServedView.isHandwritingDelegate()) { mServedView.getViewRootImpl().getHandwritingInitiator().onDelegateViewFocused( mServedView); } if (mServedInputConnection != null) { mServedInputConnection.finishComposingTextFromImm(); } Loading
core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java +45 −76 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.view.PointerIcon; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.inputmethod.Flags; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; Loading Loading @@ -135,7 +136,7 @@ public class HandwritingInitiatorTest { when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0); mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -172,7 +173,7 @@ public class HandwritingInitiatorTest { when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(2); mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -202,7 +203,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_startHandwritingOnce_when_stylusMoveMultiTimes_withinHWArea() { mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -246,9 +247,7 @@ public class HandwritingInitiatorTest { when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4); when(mTestView1.getLineAtCoordinate(anyFloat())).thenReturn(0); if (!mInitiateWithoutConnection) { mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = sHwArea1.left - HW_BOUNDS_OFFSETS_LEFT_PX / 2; final int y1 = sHwArea1.top - HW_BOUNDS_OFFSETS_TOP_PX / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -284,13 +283,7 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); if (mInitiateWithoutConnection) { // Focus is changed after stylus movement. mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); } else { // InputConnection is created after stylus movement. mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView1); verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1); } Loading @@ -312,24 +305,11 @@ public class HandwritingInitiatorTest { final int y2 = y1; MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); if (!mInitiateWithoutConnection) { // First create InputConnection for mTestView2 and verify that handwriting is not // started. mHandwritingInitiator.onInputConnectionCreated(mTestView2); } onEditorFocusedOrConnectionCreated(mTestView2); // Note: mTestView2 receives focus when initiationWithoutInputConnection() is enabled. // verify that handwriting is not started. verify(mHandwritingInitiator, never()).startHandwriting(mTestView2); if (mInitiateWithoutConnection) { // Focus is changed after stylus movement. mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); } else { // Next create InputConnection for mTextView1. Handwriting is started for this view // since the stylus down point is closest to this view. mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView1); // Handwriting is started for this view since the stylus down point is closest to this // view. verify(mHandwritingInitiator).startHandwriting(mTestView1); Loading @@ -351,7 +331,7 @@ public class HandwritingInitiatorTest { delegateView.setIsHandwritingDelegate(true); mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.onInputConnectionCreated(delegateView)); () -> onEditorFocusedOrConnectionCreated(delegateView)); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; Loading @@ -371,17 +351,15 @@ public class HandwritingInitiatorTest { public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() { View delegateView = new EditText(mContext); delegateView.setIsHandwritingDelegate(true); if (mInitiateWithoutConnection) { mHandwritingInitiator.onEditorFocused(delegateView); } mHandwritingInitiator.onInputConnectionCreated(delegateView); reset(mHandwritingInitiator); if (mInitiateWithoutConnection) { mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.updateFocusedView( delegateView, /*fromTouchEvent*/ false)); } else { mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.onDelegateViewFocused(delegateView)); } final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; Loading @@ -393,7 +371,7 @@ public class HandwritingInitiatorTest { MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0); mHandwritingInitiator.onTouchEvent(stylusEvent2); verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView); verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(any()); } @Test Loading Loading @@ -431,14 +409,6 @@ public class HandwritingInitiatorTest { assertThat(onTouchEventResult4).isTrue(); } private void callOnInputConnectionOrUpdateViewFocus(View view) { if (mInitiateWithoutConnection) { mHandwritingInitiator.updateFocusedView(view, /*fromTouchEvent*/ true); } else { mHandwritingInitiator.onInputConnectionCreated(view); } } @Test public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() { final Rect rect = new Rect(600, 600, 900, 900); Loading @@ -446,7 +416,7 @@ public class HandwritingInitiatorTest { false /* isStylusHandwritingAvailable */); mHandwritingInitiator.updateHandwritingAreasForView(testView); callOnInputConnectionOrUpdateViewFocus(testView); onEditorFocusedOrConnectionCreated(testView); final int x1 = (rect.left + rect.right) / 2; final int y1 = (rect.top + rect.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading @@ -465,7 +435,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() { callOnInputConnectionOrUpdateViewFocus(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = 200; final int y1 = 200; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading @@ -481,7 +451,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() { callOnInputConnectionOrUpdateViewFocus(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = 10; final int y1 = 10; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading @@ -497,7 +467,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTimeOut() { callOnInputConnectionOrUpdateViewFocus(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = 10; final int y1 = 10; final long time1 = 10L; Loading Loading @@ -553,9 +523,7 @@ public class HandwritingInitiatorTest { @Test public void onTouchEvent_focusView_inputConnectionAlreadyBuilt_stylusMoveOnce_withinHWArea() { if (!mInitiateWithoutConnection) { mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView1); final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0); Loading Loading @@ -608,14 +576,14 @@ public class HandwritingInitiatorTest { verify(mTestView2, times(1)).requestFocus(); callOnInputConnectionOrUpdateViewFocus(mTestView2); onEditorFocusedOrConnectionCreated(mTestView2); verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView2); } @Test public void onTouchEvent_handwritingAreaOverlapped_focusedViewHasPriority() { // Simulate the case where mTestView1 is focused. callOnInputConnectionOrUpdateViewFocus(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); // The ACTION_DOWN location is within the handwriting bounds of both mTestView1 and // mTestView2. Although it's closer to mTestView2's handwriting bounds, handwriting is // initiated for mTestView1 because it's focused. Loading Loading @@ -653,7 +621,7 @@ public class HandwritingInitiatorTest { @Test public void onResolvePointerIcon_afterHandwriting_hidePointerIconForConnectedView() { // simulate the case where sTestView1 is focused. mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), /* exceedsHWSlop */ true); // Verify that handwriting started for sTestView1. Loading @@ -679,15 +647,14 @@ public class HandwritingInitiatorTest { public void onResolvePointerIcon_afterHandwriting_hidePointerIconForDelegatorView() { // Set mTextView2 to be the delegate of mTestView1. mTestView2.setIsHandwritingDelegate(true); if (mInitiateWithoutConnection) { mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.updateFocusedView( mTestView2, /*fromTouchEvent*/ false)); } else { mTestView1.setHandwritingDelegatorCallback( () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2)); () -> { if (mInitiateWithoutConnection) { mHandwritingInitiator.updateFocusedView(mTestView2); } mHandwritingInitiator.onInputConnectionCreated(mTestView2); }); injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), /* exceedsHWSlop */ true); // Prerequisite check, verify that handwriting started for delegateView. Loading @@ -702,7 +669,7 @@ public class HandwritingInitiatorTest { @Test public void onResolvePointerIcon_showHoverIconAfterTap() { // Simulate the case where sTestView1 is focused. mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), /* exceedsHWSlop */ true); // Verify that handwriting started for sTestView1. Loading @@ -724,7 +691,7 @@ public class HandwritingInitiatorTest { @Test public void onResolvePointerIcon_showHoverIconAfterFocusChange() { // Simulate the case where sTestView1 is focused. mHandwritingInitiator.onInputConnectionCreated(mTestView1); onEditorFocusedOrConnectionCreated(mTestView1); injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(), /* exceedsHWSlop */ true); // Verify that handwriting started for sTestView1. Loading @@ -735,14 +702,8 @@ public class HandwritingInitiatorTest { // After handwriting is initiated for the connected view, hide the hover icon. assertThat(icon1).isNull(); // Simulate that focus is switched to mTestView2 first and then switched back. if (mInitiateWithoutConnection) { mHandwritingInitiator.updateFocusedView(mTestView2, /*fromTouchEvent*/ true); mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); } else { mHandwritingInitiator.onInputConnectionCreated(mTestView2); mHandwritingInitiator.onInputConnectionCreated(mTestView1); } onEditorFocusedOrConnectionCreated(mTestView2); onEditorFocusedOrConnectionCreated(mTestView1); PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1); // After the change of focus, hover icon shows again. Loading @@ -754,11 +715,11 @@ public class HandwritingInitiatorTest { if (mInitiateWithoutConnection) { mTestView1.setAutoHandwritingEnabled(false); mTestView1.setHandwritingDelegatorCallback(null); mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true); onEditorFocusedOrConnectionCreated(mTestView1); } else { View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */, true /* isStylusHandwritingAvailable */); mHandwritingInitiator.onInputConnectionCreated(mockView); onEditorFocusedOrConnectionCreated(mockView); } final int x1 = (sHwArea1.left + sHwArea1.right) / 2; final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2; Loading Loading @@ -974,4 +935,12 @@ public class HandwritingInitiatorTest { 1 /* yPrecision */, 0 /* deviceId */, 0 /* edgeFlags */, InputDevice.SOURCE_STYLUS, 0 /* flags */); } private void onEditorFocusedOrConnectionCreated(View testView) { if (Flags.initiationWithoutInputConnection()) { mHandwritingInitiator.onEditorFocused(testView); } else { mHandwritingInitiator.onInputConnectionCreated(testView); } } }