Scribe initiation without InputConnection

Allow Stylus handwriting to initiate handwriting without waiting for
InputConnection to be established.
When an editor view receives focus, it sends startInput binder call immidiately.
Focused editor (servedView) is then ready for any
startStylusHandwriting() calls.

This should help shave few millis from initiation latency.

Note: this intentionally doesn't optimize delegation since that is
migrating to newer APIs.

Bug: 308827131
Bug: 324670412

Test: atest HandwritingInitiatorTest StylusHandwritingTest

Change-Id: I0174e15fbbedf88dcd68ebe98333b6b07871985a
diff --git a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java
index 123b2ee..0fd2449 100644
--- a/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/view/HandwritingInitiatorPerfTest.java
@@ -21,8 +21,11 @@
 import static android.view.MotionEvent.ACTION_UP;
 import static android.view.MotionEvent.TOOL_TYPE_FINGER;
 import static android.view.MotionEvent.TOOL_TYPE_STYLUS;
+import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
 
 
+import static org.junit.Assume.assumeFalse;
+
 import android.app.Instrumentation;
 import android.content.Context;
 import android.perftests.utils.BenchmarkState;
@@ -186,6 +189,7 @@
 
     @Test
     public void onInputConnectionCreated() {
+        assumeFalse(initiationWithoutInputConnection());
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final View view = new View(mContext);
         final EditorInfo editorInfo = new EditorInfo();
@@ -199,6 +203,7 @@
 
     @Test
     public void onInputConnectionClosed() {
+        assumeFalse(initiationWithoutInputConnection());
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final View view = new View(mContext);
         while (state.keepRunning()) {
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index 0ce1d47..eb28920 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -23,8 +23,10 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.EditText;
+import android.widget.Editor;
 import android.widget.TextView;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -80,6 +82,16 @@
      * connections and only set mConnectedView to null when mConnectionCount is zero.
      */
     private int mConnectionCount = 0;
+
+    /**
+     * The reference to the View that currently has focus.
+     * This replaces mConnecteView when {@code Flags#intitiationWithoutInputConnection()} is
+     * enabled.
+     */
+    @Nullable
+    @VisibleForTesting
+    public WeakReference<View> mFocusedView = null;
+
     private final InputMethodManager mImm;
 
     private final int[] mTempLocation = new int[2];
@@ -112,9 +124,15 @@
      *
      * If the stylus is hovering on an unconnected editor that supports handwriting, we always show
      * the hover icon.
+     * TODO(b/308827131): Rename to FocusedView after Flag is flipped.
      */
     private boolean mShowHoverIconForConnectedView = true;
 
+    /** When flag is enabled, touched editors don't wait for InputConnection for initiation.
+     * However, delegation still waits for InputConnection.
+     */
+    private final boolean mInitiateWithoutConnection = Flags.initiationWithoutInputConnection();
+
     @VisibleForTesting
     public HandwritingInitiator(@NonNull ViewConfiguration viewConfiguration,
             @NonNull InputMethodManager inputMethodManager) {
@@ -201,8 +219,8 @@
                     View candidateView = findBestCandidateView(mState.mStylusDownX,
                             mState.mStylusDownY, /* isHover */ false);
                     if (candidateView != null) {
-                        if (candidateView == getConnectedView()) {
-                            if (!candidateView.hasFocus()) {
+                        if (candidateView == getConnectedOrFocusedView()) {
+                            if (!mInitiateWithoutConnection && !candidateView.hasFocus()) {
                                 requestFocusWithoutReveal(candidateView);
                             }
                             startHandwriting(candidateView);
@@ -217,8 +235,17 @@
                             candidateView.getHandwritingDelegatorCallback().run();
                             mState.mHasPreparedHandwritingDelegation = true;
                         } else {
-                            mState.mPendingConnectedView = new WeakReference<>(candidateView);
-                            requestFocusWithoutReveal(candidateView);
+                            if (!mInitiateWithoutConnection) {
+                                mState.mPendingConnectedView = new WeakReference<>(candidateView);
+                            }
+                            if (!candidateView.hasFocus()) {
+                                requestFocusWithoutReveal(candidateView);
+                            }
+                            if (mInitiateWithoutConnection
+                                    && updateFocusedView(candidateView,
+                                            /* fromTouchEvent */ true)) {
+                                startHandwriting(candidateView);
+                            }
                         }
                     }
                 }
@@ -244,11 +271,7 @@
      */
     public void onDelegateViewFocused(@NonNull View view) {
         if (view == getConnectedView()) {
-            if (tryAcceptStylusHandwritingDelegation(view)) {
-                // A handwriting delegate view is accepted and handwriting starts; hide the
-                // hover icon.
-                mShowHoverIconForConnectedView = false;
-            }
+            tryAcceptStylusHandwritingDelegation(view);
         }
     }
 
@@ -260,6 +283,10 @@
      * @see  #onInputConnectionClosed(View)
      */
     public void onInputConnectionCreated(@NonNull View view) {
+        if (mInitiateWithoutConnection && !view.isHandwritingDelegate()) {
+            // When flag is enabled, only delegation continues to wait for InputConnection.
+            return;
+        }
         if (!view.isAutoHandwritingEnabled()) {
             clearConnectedView();
             return;
@@ -274,12 +301,15 @@
             // A new view just gain focus. By default, we should show hover icon for it.
             mShowHoverIconForConnectedView = true;
             if (view.isHandwritingDelegate() && tryAcceptStylusHandwritingDelegation(view)) {
-                // A handwriting delegate view is accepted and handwriting starts; hide the
-                // hover icon.
+                // tryAcceptStylusHandwritingDelegation should set boolean below, however, we
+                // cannot mock IMM to return true for acceptStylusDelegation().
+                // TODO(b/324670412): we should move any dependent tests to integration and remove
+                //  the assignment below.
                 mShowHoverIconForConnectedView = false;
                 return;
             }
-            if (mState != null && mState.mPendingConnectedView != null
+            if (!mInitiateWithoutConnection && mState != null
+                    && mState.mPendingConnectedView != null
                     && mState.mPendingConnectedView.get() == view) {
                 startHandwriting(view);
             }
@@ -293,6 +323,9 @@
      * @param view the view that closed the InputConnection.
      */
     public void onInputConnectionClosed(@NonNull View view) {
+        if (mInitiateWithoutConnection && !view.isHandwritingDelegate()) {
+            return;
+        }
         final View connectedView = getConnectedView();
         if (connectedView == null) return;
         if (connectedView == view) {
@@ -306,6 +339,48 @@
         }
     }
 
+    @Nullable
+    private View getFocusedView() {
+        if (mFocusedView == null) return null;
+        return mFocusedView.get();
+    }
+
+    /**
+     * Clear the tracked focused view tracked for handwriting initiation.
+     * @param view the focused view.
+     */
+    public void clearFocusedView(View view) {
+        if (view == null || mFocusedView == null) {
+            return;
+        }
+        if (mFocusedView.get() == view) {
+            mFocusedView = null;
+        }
+    }
+
+    /**
+     * Called when new {@link Editor} is focused.
+     * @return {@code true} if handwriting can initiate for given view.
+     */
+    @VisibleForTesting
+    public boolean updateFocusedView(@NonNull View view, boolean fromTouchEvent) {
+        if (!view.shouldInitiateHandwriting()) {
+            mFocusedView = null;
+            return false;
+        }
+
+        final View focusedView = getFocusedView();
+        if (focusedView != view) {
+            mFocusedView = new WeakReference<>(view);
+            // A new view just gain focus. By default, we should show hover icon for it.
+            mShowHoverIconForConnectedView = true;
+        }
+        if (!fromTouchEvent) {
+            tryAcceptStylusHandwritingDelegation(view);
+        }
+        return true;
+    }
+
     /** Starts a stylus handwriting session for the view. */
     @VisibleForTesting
     public void startHandwriting(@NonNull View view) {
@@ -324,6 +399,9 @@
      */
     @VisibleForTesting
     public boolean tryAcceptStylusHandwritingDelegation(@NonNull View view) {
+        if (!view.isHandwritingDelegate() || (mState != null && mState.mHasInitiatedHandwriting)) {
+            return false;
+        }
         String delegatorPackageName =
                 view.getAllowedHandwritingDelegatorPackageName();
         if (delegatorPackageName == null) {
@@ -337,6 +415,9 @@
             if (view instanceof TextView) {
                 ((TextView) view).hideHint();
             }
+            // A handwriting delegate view is accepted and handwriting starts; hide the
+            // hover icon.
+            mShowHoverIconForConnectedView = false;
             return true;
         }
         return false;
@@ -377,16 +458,25 @@
             return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING);
         }
 
-        if (hoverView != getConnectedView()) {
+        if (hoverView != getConnectedOrFocusedView()) {
             // The stylus is hovering on another view that supports handwriting. We should show
-            // hover icon. Also reset the mShowHoverIconForConnectedView so that hover
-            // icon is displayed again next time when the stylus hovers on connected view.
+            // hover icon. Also reset the mShowHoverIconForFocusedView so that hover
+            // icon is displayed again next time when the stylus hovers on focused view.
             mShowHoverIconForConnectedView = true;
             return PointerIcon.getSystemIcon(context, PointerIcon.TYPE_HANDWRITING);
         }
         return null;
     }
 
+    // TODO(b/308827131): Remove once Flag is flipped.
+    private View getConnectedOrFocusedView() {
+        if (mInitiateWithoutConnection) {
+            return mFocusedView == null ? null : mFocusedView.get();
+        } else {
+            return mConnectedView == null ? null : mConnectedView.get();
+        }
+    }
+
     private View getCachedHoverTarget() {
         if (mCachedHoverTarget == null) {
             return null;
@@ -458,20 +548,21 @@
      */
     @Nullable
     private View findBestCandidateView(float x, float y, boolean isHover) {
+        // TODO(b/308827131): Rename to FocusedView after Flag is flipped.
         // If the connectedView is not null and do not set any handwriting area, it will check
         // whether the connectedView's boundary contains the initial stylus position. If true,
         // directly return the connectedView.
-        final View connectedView = getConnectedView();
-        if (connectedView != null) {
+        final View connectedOrFocusedView = getConnectedOrFocusedView();
+        if (connectedOrFocusedView != null) {
             Rect handwritingArea = mTempRect;
-            if (getViewHandwritingArea(connectedView, handwritingArea)
-                    && isInHandwritingArea(handwritingArea, x, y, connectedView, isHover)
-                    && shouldTriggerStylusHandwritingForView(connectedView)) {
+            if (getViewHandwritingArea(connectedOrFocusedView, handwritingArea)
+                    && isInHandwritingArea(handwritingArea, x, y, connectedOrFocusedView, isHover)
+                    && shouldTriggerStylusHandwritingForView(connectedOrFocusedView)) {
                 if (!isHover && mState != null) {
                     mState.mStylusDownWithinEditorBounds =
                             contains(handwritingArea, x, y, 0f, 0f, 0f, 0f);
                 }
-                return connectedView;
+                return connectedOrFocusedView;
             }
         }
 
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3d70c5b..3b07f27 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -18,6 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
+import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -1950,6 +1951,10 @@
         if (mServedView != null) {
             clearedView = mServedView;
             mServedView = null;
+            if (initiationWithoutInputConnection() && clearedView.getViewRootImpl() != null) {
+                clearedView.getViewRootImpl().getHandwritingInitiator()
+                        .clearFocusedView(clearedView);
+            }
         }
         if (clearedView != null) {
             if (DEBUG) {
@@ -2932,6 +2937,10 @@
             switch (res.result) {
                 case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                     mRestartOnNextWindowFocus = true;
+                    if (initiationWithoutInputConnection()) {
+                        mServedView.getViewRootImpl().getHandwritingInitiator().clearFocusedView(
+                                mServedView);
+                    }
                     mServedView = null;
                     break;
             }
@@ -3094,6 +3103,11 @@
             return false;
         }
         mServedView = mNextServedView;
+        if (initiationWithoutInputConnection() && mServedView.onCheckIsTextEditor()
+                && mServedView.isHandwritingDelegate()) {
+            mServedView.getViewRootImpl().getHandwritingInitiator().onDelegateViewFocused(
+                    mServedView);
+        }
         if (mServedInputConnection != null) {
             mServedInputConnection.finishComposingTextFromImm();
         }
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index 7f1cc8e..8b91bcb 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -62,3 +62,11 @@
     bug: "311791923"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "initiation_without_input_connection"
+    namespace: "input_method"
+    description: "Feature flag for initiating handwriting without InputConnection"
+    bug: "308827131"
+    is_fixed_read_only: true
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index f39bddd..51eb41c 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -20,10 +20,12 @@
 import static android.view.MotionEvent.ACTION_HOVER_MOVE;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
+import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
 import static android.view.stylus.HandwritingTestUtil.createView;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeFalse;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -91,6 +93,7 @@
     private EditText mTestView1;
     private EditText mTestView2;
     private Context mContext;
+    private boolean mInitiateWithoutConnection;
 
     @Before
     public void setup() throws Exception {
@@ -119,6 +122,7 @@
         mHandwritingInitiator.updateHandwritingAreasForView(mTestView1);
         mHandwritingInitiator.updateHandwritingAreasForView(mTestView2);
         doReturn(true).when(mHandwritingInitiator).tryAcceptStylusHandwritingDelegation(any());
+        mInitiateWithoutConnection = initiationWithoutInputConnection();
     }
 
     @Test
@@ -194,7 +198,9 @@
         mTestView1.setText("hello");
         when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
 
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        if (!mInitiateWithoutConnection) {
+            mHandwritingInitiator.onInputConnectionCreated(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);
@@ -214,7 +220,7 @@
     }
 
     @Test
-    public void onTouchEvent_startHandwriting_inputConnectionBuiltAfterStylusMove() {
+    public void onTouchEvent_startHandwriting_servedViewUpdateAfterStylusMove() {
         final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
         final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -225,14 +231,19 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        // InputConnection is created after stylus movement.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        if (mInitiateWithoutConnection) {
+            // Focus is changed after stylus movement.
+            mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true);
+        } else {
+            // InputConnection is created after stylus movement.
+            mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        }
 
         verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView1);
     }
 
     @Test
-    public void onTouchEvent_startHandwriting_inputConnectionBuilt_stylusMoveInExtendedHWArea() {
+    public void onTouchEvent_startHandwriting_servedViewUpdate_stylusMoveInExtendedHWArea() {
         mTestView1.setText("hello");
         when(mTestView1.getOffsetForPosition(anyFloat(), anyFloat())).thenReturn(4);
         // The stylus down point is between mTestView1 and  mTestView2, but it is within the
@@ -247,21 +258,35 @@
         MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
         mHandwritingInitiator.onTouchEvent(stylusEvent2);
 
-        // First create InputConnection for mTestView2 and verify that handwriting is not started.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
-        verify(mHandwritingInitiator, never()).startHandwriting(mTestView2);
+        if (!mInitiateWithoutConnection) {
+            // First create InputConnection for mTestView2 and verify that handwriting is not
+            // started.
+            mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+        }
 
-        // Next create InputConnection for mTextView1. Handwriting is started for this view since
-        // the stylus down point is closest to this view.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        // 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);
+        }
+        // Handwriting is started for this view since  the stylus down point is closest to this
+        // view.
         verify(mHandwritingInitiator).startHandwriting(mTestView1);
         // Since the stylus down point was outside the TextView's bounds, the handwriting initiator
         // sets the cursor position.
         verify(mTestView1).setSelection(4);
     }
 
+
     @Test
     public void onTouchEvent_tryAcceptDelegation_delegatorCallbackCreatesInputConnection() {
+        assumeFalse(mInitiateWithoutConnection);
         View delegateView = new EditText(mContext);
         delegateView.setIsHandwritingDelegate(true);
 
@@ -281,6 +306,7 @@
         verify(mHandwritingInitiator, times(1)).tryAcceptStylusHandwritingDelegation(delegateView);
     }
 
+
     @Test
     public void onTouchEvent_tryAcceptDelegation_delegatorCallbackFocusesDelegate() {
         View delegateView = new EditText(mContext);
@@ -288,8 +314,14 @@
         mHandwritingInitiator.onInputConnectionCreated(delegateView);
         reset(mHandwritingInitiator);
 
-        mTestView1.setHandwritingDelegatorCallback(
-                () -> mHandwritingInitiator.onDelegateViewFocused(delegateView));
+        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;
@@ -339,6 +371,14 @@
         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);
@@ -346,7 +386,7 @@
                 false /* isStylusHandwritingAvailable */);
         mHandwritingInitiator.updateHandwritingAreasForView(testView);
 
-        mHandwritingInitiator.onInputConnectionCreated(testView);
+        callOnInputConnectionOrUpdateViewFocus(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);
@@ -365,7 +405,7 @@
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusTap_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        callOnInputConnectionOrUpdateViewFocus(mTestView1);
         final int x1 = 200;
         final int y1 = 200;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -381,7 +421,7 @@
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusMove_outOfHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        callOnInputConnectionOrUpdateViewFocus(mTestView1);
         final int x1 = 10;
         final int y1 = 10;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -397,7 +437,7 @@
 
     @Test
     public void onTouchEvent_notStartHandwriting_when_stylusMove_afterTimeOut() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        callOnInputConnectionOrUpdateViewFocus(mTestView1);
         final int x1 = 10;
         final int y1 = 10;
         final long time1 = 10L;
@@ -433,8 +473,9 @@
 
     @Test
     public void onTouchEvent_focusView_inputConnectionAlreadyBuilt_stylusMoveOnce_withinHWArea() {
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
-
+        if (!mInitiateWithoutConnection) {
+            mHandwritingInitiator.onInputConnectionCreated(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);
@@ -487,14 +528,14 @@
 
         verify(mTestView2, times(1)).requestFocus();
 
-        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+        callOnInputConnectionOrUpdateViewFocus(mTestView2);
         verify(mHandwritingInitiator, times(1)).startHandwriting(mTestView2);
     }
 
     @Test
     public void onTouchEvent_handwritingAreaOverlapped_focusedViewHasPriority() {
         // Simulate the case where mTestView1 is focused.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        callOnInputConnectionOrUpdateViewFocus(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.
@@ -559,9 +600,14 @@
         // Set mTextView2 to be the delegate of mTestView1.
         mTestView2.setIsHandwritingDelegate(true);
 
-        mTestView1.setHandwritingDelegatorCallback(
-                () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2));
-
+        if (mInitiateWithoutConnection) {
+            mTestView1.setHandwritingDelegatorCallback(
+                    () -> mHandwritingInitiator.updateFocusedView(
+                            mTestView2, /*fromTouchEvent*/ false));
+        } else {
+            mTestView1.setHandwritingDelegatorCallback(
+                    () -> mHandwritingInitiator.onInputConnectionCreated(mTestView2));
+        }
         injectStylusEvent(mHandwritingInitiator, sHwArea1.centerX(), sHwArea1.centerY(),
                 /* exceedsHWSlop */ true);
         // Prerequisite check, verify that handwriting started for delegateView.
@@ -610,8 +656,13 @@
         assertThat(icon1).isNull();
 
         // Simulate that focus is switched to mTestView2 first and then switched back.
-        mHandwritingInitiator.onInputConnectionCreated(mTestView2);
-        mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        if (mInitiateWithoutConnection) {
+            mHandwritingInitiator.updateFocusedView(mTestView2, /*fromTouchEvent*/ true);
+            mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true);
+        } else {
+            mHandwritingInitiator.onInputConnectionCreated(mTestView2);
+            mHandwritingInitiator.onInputConnectionCreated(mTestView1);
+        }
 
         PointerIcon icon2 = mHandwritingInitiator.onResolvePointerIcon(mContext, hoverEvent1);
         // After the change of focus, hover icon shows again.
@@ -620,9 +671,15 @@
 
     @Test
     public void autoHandwriting_whenDisabled_wontStartHW() {
-        View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */,
-                true /* isStylusHandwritingAvailable */);
-        mHandwritingInitiator.onInputConnectionCreated(mockView);
+        if (mInitiateWithoutConnection) {
+            mTestView1.setAutoHandwritingEnabled(false);
+            mTestView1.setHandwritingDelegatorCallback(null);
+            mHandwritingInitiator.updateFocusedView(mTestView1, /*fromTouchEvent*/ true);
+        } else {
+            View mockView = createView(sHwArea1, false /* autoHandwritingEnabled */,
+                    true /* isStylusHandwritingAvailable */);
+            mHandwritingInitiator.onInputConnectionCreated(mockView);
+        }
         final int x1 = (sHwArea1.left + sHwArea1.right) / 2;
         final int y1 = (sHwArea1.top + sHwArea1.bottom) / 2;
         MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
@@ -639,6 +696,7 @@
 
     @Test
     public void onInputConnectionCreated() {
+        assumeFalse(mInitiateWithoutConnection);
         mHandwritingInitiator.onInputConnectionCreated(mTestView1);
         assertThat(mHandwritingInitiator.mConnectedView).isNotNull();
         assertThat(mHandwritingInitiator.mConnectedView.get()).isEqualTo(mTestView1);
@@ -646,6 +704,7 @@
 
     @Test
     public void onInputConnectionCreated_whenAutoHandwritingIsDisabled() {
+        assumeFalse(mInitiateWithoutConnection);
         View view = new View(mContext);
         view.setAutoHandwritingEnabled(false);
         assertThat(view.isAutoHandwritingEnabled()).isFalse();
@@ -656,6 +715,7 @@
 
     @Test
     public void onInputConnectionClosed() {
+        assumeFalse(mInitiateWithoutConnection);
         mHandwritingInitiator.onInputConnectionCreated(mTestView1);
         mHandwritingInitiator.onInputConnectionClosed(mTestView1);
 
@@ -664,6 +724,7 @@
 
     @Test
     public void onInputConnectionClosed_whenAutoHandwritingIsDisabled() {
+        assumeFalse(mInitiateWithoutConnection);
         View view = new View(mContext);
         view.setAutoHandwritingEnabled(false);
         mHandwritingInitiator.onInputConnectionCreated(view);
@@ -674,6 +735,7 @@
 
     @Test
     public void onInputConnectionCreated_inputConnectionRestarted() {
+        assumeFalse(mInitiateWithoutConnection);
         // When IMM restarts input connection, View#onInputConnectionCreatedInternal might be
         // called before View#onInputConnectionClosedInternal. As a result, we need to handle the
         // case where "one view "2 InputConnections".