Loading core/java/android/view/ImeFocusController.java +14 −181 Original line number Original line Diff line number Diff line Loading @@ -17,8 +17,6 @@ package android.view; package android.view; import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS; import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS; import static android.view.ImeFocusControllerProto.NEXT_SERVED_VIEW; import static android.view.ImeFocusControllerProto.SERVED_VIEW; import android.annotation.AnyThread; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.NonNull; Loading @@ -31,8 +29,6 @@ import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.inputmethod.StartInputReason; import java.util.Objects; /** /** * Responsible for IME focus handling inside {@link ViewRootImpl}. * Responsible for IME focus handling inside {@link ViewRootImpl}. * @hide * @hide Loading @@ -43,21 +39,6 @@ public final class ImeFocusController { private final ViewRootImpl mViewRootImpl; private final ViewRootImpl mViewRootImpl; private boolean mHasImeFocus = false; private boolean mHasImeFocus = false; /** * This is the view that should currently be served by an input method, * regardless of the state of setting that up. * @see InputMethodManagerDelegate#getLockObject() */ private View mServedView; /** * This is the next view that will be served by the input method, when * we get around to updating things. * @see InputMethodManagerDelegate#getLockObject() */ private View mNextServedView; private InputMethodManagerDelegate mDelegate; private InputMethodManagerDelegate mDelegate; @UiThread @UiThread Loading Loading @@ -134,122 +115,30 @@ public final class ImeFocusController { windowAttribute.softInputMode)); windowAttribute.softInputMode)); } } boolean forceFocus = false; getImmDelegate().onPostWindowFocus(viewForWindowFocus, windowAttribute); final InputMethodManagerDelegate immDelegate = getImmDelegate(); synchronized (immDelegate.getLockObject()) { // Update mNextServedView when focusedView changed. onViewFocusChanged(viewForWindowFocus, true); // Starting new input when the next focused view is same as served view but the // currently active connection (if any) is not associated with it. final boolean nextFocusIsServedView = mServedView == viewForWindowFocus; if (nextFocusIsServedView && !immDelegate.hasActiveConnection(viewForWindowFocus)) { forceFocus = true; } } immDelegate.startInputOnWindowFocusGain(viewForWindowFocus, windowAttribute.softInputMode, windowAttribute.flags, forceFocus); } } /** /** * @see InputMethodManager#checkFocus() * @see InputMethodManager#checkFocus() */ */ public boolean checkFocus(boolean forceNewFocus, boolean startInput) { public boolean checkFocus(boolean forceNewFocus, boolean startInput) { final InputMethodManagerDelegate immDelegate = getImmDelegate(); return getImmDelegate().checkFocus(forceNewFocus, startInput, mViewRootImpl); synchronized (immDelegate.getLockObject()) { if (!immDelegate.isCurrentRootView(mViewRootImpl)) { return false; } if (mServedView == mNextServedView && !forceNewFocus) { return false; } if (DEBUG) { Log.v(TAG, "checkFocus: view=" + mServedView + " next=" + mNextServedView + " force=" + forceNewFocus + " package=" + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>")); } // Close the connection when no next served view coming. if (mNextServedView == null) { immDelegate.finishInput(); immDelegate.closeCurrentIme(); return false; } mServedView = mNextServedView; immDelegate.finishComposingText(); } if (startInput) { immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */, 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */); } return true; } } @UiThread @UiThread void onViewFocusChanged(View view, boolean hasFocus) { void onViewFocusChanged(View view, boolean hasFocus) { if (view == null || view.isTemporarilyDetached()) { getImmDelegate().onViewFocusChanged(view, hasFocus); return; } final InputMethodManagerDelegate immDelegate = getImmDelegate(); synchronized (immDelegate.getLockObject()) { if (!immDelegate.isCurrentRootView(view.getViewRootImpl())) { return; } if (!view.hasImeFocus() || !view.hasWindowFocus()) { return; } if (DEBUG) { Log.d(TAG, "onViewFocusChanged, view=" + InputMethodDebug.dumpViewInfo(view) + ", mServedView=" + InputMethodDebug.dumpViewInfo(mServedView)); } // We don't need to track the next served view when the view lost focus here because: // 1) The current view focus may be cleared temporary when in touch mode, closing input // at this moment isn't the right way. // 2) We only care about the served view change when it focused, since changing input // connection when the focus target changed is reasonable. // 3) Setting the next served view as null when no more served view should be handled in // other special events (e.g. view detached from window or the window dismissed). if (hasFocus) { mNextServedView = view; } } mViewRootImpl.dispatchCheckFocus(); } } @UiThread @UiThread void onViewDetachedFromWindow(View view) { void onViewDetachedFromWindow(View view) { final InputMethodManagerDelegate immDelegate = getImmDelegate(); getImmDelegate().onViewDetachedFromWindow(view, mViewRootImpl); synchronized (immDelegate.getLockObject()) { if (!immDelegate.isCurrentRootView(view.getViewRootImpl())) { return; } if (mNextServedView == view) { mNextServedView = null; } if (mServedView == view) { mViewRootImpl.dispatchCheckFocus(); } } } } @UiThread @UiThread void onWindowDismissed() { void onWindowDismissed() { final InputMethodManagerDelegate immDelegate = getImmDelegate(); getImmDelegate().onWindowDismissed(mViewRootImpl); synchronized (immDelegate.getLockObject()) { if (!immDelegate.isCurrentRootView(mViewRootImpl)) { return; } if (mServedView != null) { immDelegate.finishInput(); } immDelegate.setCurrentRootView(null); } mHasImeFocus = false; mHasImeFocus = false; } } Loading Loading @@ -302,75 +191,21 @@ public final class ImeFocusController { public interface InputMethodManagerDelegate { public interface InputMethodManagerDelegate { /** /** * Starts the input connection. * Starts the input connection. * Note that this method must not hold the {@link InputMethodManager} lock with * {@link InputMethodManagerDelegate#getLockObject()} while {@link InputMethodManager} * calling into app-code in different threads. */ */ boolean startInput(@StartInputReason int startInputReason, View focusedView, boolean startInput(@StartInputReason int startInputReason, View focusedView, @StartInputFlags int startInputFlags, @StartInputFlags int startInputFlags, @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags); @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags); /** * Starts the input connection when gaining the window focus. void onPostWindowFocus(View viewForWindowFocus, * Note that this method must not hold the {@link InputMethodManager} lock with @NonNull WindowManager.LayoutParams windowAttribute); * {@link InputMethodManagerDelegate#getLockObject()} while {@link InputMethodManager} void onViewFocusChanged(@NonNull View view, boolean hasFocus); * calling into app-code in different threads. boolean checkFocus(boolean forceNewFocus, boolean startInput, ViewRootImpl viewRootImpl); */ void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl); void startInputOnWindowFocusGain(View rootView, void onWindowDismissed(ViewRootImpl viewRootImpl); @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus); void finishInput(); void finishInputAndReportToIme(); void finishInputAndReportToIme(); void closeCurrentIme(); void finishComposingText(); void setCurrentRootView(ViewRootImpl rootView); void setCurrentRootView(ViewRootImpl rootView); boolean isCurrentRootView(ViewRootImpl rootView); boolean isCurrentRootView(ViewRootImpl rootView); boolean hasActiveConnection(View view); /** * Returns the {@code InputMethodManager#mH} lock object. * Used for {@link ImeFocusController} to guard the served view being accessed by * {@link InputMethodManager} in different threads. */ Object getLockObject(); } /** * Returns The current IME served view for {@link InputMethodManager}. * Used to start input connection or check the caller's validity when calling * {@link InputMethodManager} APIs. * Note that this method requires to be called inside {@code InputMethodManager#mH} lock for * data consistency. */ public View getServedViewLocked() { return mServedView; } /** * Returns The next incoming IME served view for {@link InputMethodManager}. * Note that this method requires to be called inside {@code InputMethodManager#mH} lock for * data consistency. */ public View getNextServedViewLocked() { return mNextServedView; } /** * Clears the served & the next served view when the controller triggers * {@link InputMethodManagerDelegate#finishInput()} or * {@link InputMethodManagerDelegate#finishInputAndReportToIme()}. * Note that this method requires to be called inside {@code InputMethodManager#mH} lock for * data consistency. * * @return The {@code mServedView} that has cleared, or {@code null} means nothing to clear. */ public View clearServedViewsLocked() { View clearedView = null; mNextServedView = null; if (mServedView != null) { clearedView = mServedView; mServedView = null; } return clearedView; } } /** /** Loading @@ -384,8 +219,6 @@ public final class ImeFocusController { void dumpDebug(ProtoOutputStream proto, long fieldId) { void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); final long token = proto.start(fieldId); proto.write(HAS_IME_FOCUS, mHasImeFocus); proto.write(HAS_IME_FOCUS, mHasImeFocus); proto.write(SERVED_VIEW, Objects.toString(mServedView)); proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView)); proto.end(token); proto.end(token); } } } } core/java/android/view/inputmethod/InputMethodManager.java +137 −49 Original line number Original line Diff line number Diff line Loading @@ -94,6 +94,7 @@ import android.view.KeyEvent; import android.view.View; import android.view.View; import android.view.ViewRootImpl; import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillId; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager; Loading Loading @@ -464,6 +465,22 @@ public final class InputMethodManager { // ----------------------------------------------------------- // ----------------------------------------------------------- /** * This is the view that should currently be served by an input method, * regardless of the state of setting that up. */ @Nullable @GuardedBy("mH") private View mServedView; /** * This is the next view that will be served by the input method, when * we get around to updating things. */ @Nullable @GuardedBy("mH") private View mNextServedView; /** /** * This is the root view of the overall window that currently has input * This is the root view of the overall window that currently has input * method focus. * method focus. Loading Loading @@ -699,8 +716,7 @@ public final class InputMethodManager { if (mCurRootView == null) { if (mCurRootView == null) { return null; return null; } } final View servedView = mCurRootView.getImeFocusController().getServedViewLocked(); return mServedView != null ? mServedView.getContext() : null; return servedView != null ? servedView.getContext() : null; } } } } Loading Loading @@ -735,11 +751,7 @@ public final class InputMethodManager { startInputFlags, softInputMode, windowFlags); startInputFlags, softInputMode, windowFlags); } } /** private void finishInput() { * Used by {@link ImeFocusController} to finish input connection. */ @Override public void finishInput() { ImeTracing.getInstance().triggerClientDump( ImeTracing.getInstance().triggerClientDump( "InputMethodManager.DelegateImpl#finishInput", InputMethodManager.this, "InputMethodManager.DelegateImpl#finishInput", InputMethodManager.this, null /* icProto */); null /* icProto */); Loading Loading @@ -768,19 +780,28 @@ public final class InputMethodManager { } } } } /** * Used by {@link ImeFocusController} to hide current input method editor. */ @Override @Override public void closeCurrentIme() { public void onPostWindowFocus(View viewForWindowFocus, closeCurrentInput(); @NonNull WindowManager.LayoutParams windowAttribute) { boolean forceFocus = false; synchronized (mH) { // Update mNextServedView when focusedView changed. onViewFocusChanged(viewForWindowFocus, true); // Starting new input when the next focused view is same as served view but the // currently active connection (if any) is not associated with it. final boolean nextFocusIsServedView = mServedView == viewForWindowFocus; if (nextFocusIsServedView && !hasActiveInputConnectionInternal(viewForWindowFocus)) { forceFocus = true; } } startInputOnWindowFocusGain(viewForWindowFocus, windowAttribute.softInputMode, windowAttribute.flags, forceFocus); } } /** private void startInputOnWindowFocusGain(View focusedView, * For {@link ImeFocusController} to start input when gaining the window focus. */ @Override public void startInputOnWindowFocusGain(View focusedView, @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) { @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) { int startInputFlags = getStartInputFlags(focusedView, 0); int startInputFlags = getStartInputFlags(focusedView, 0); startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS; startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS; Loading Loading @@ -816,8 +837,7 @@ public final class InputMethodManager { synchronized (mH) { synchronized (mH) { // For some reason we didn't do a startInput + windowFocusGain, so // For some reason we didn't do a startInput + windowFocusGain, so // we'll just do a window focus gain and call it a day. // we'll just do a window focus gain and call it a day. View servedView = controller.getServedViewLocked(); boolean nextFocusHasConnection = mServedView != null && mServedView == focusedView boolean nextFocusHasConnection = servedView != null && servedView == focusedView && hasActiveInputConnectionInternal(focusedView); && hasActiveInputConnectionInternal(focusedView); if (DEBUG) { if (DEBUG) { Log.v(TAG, "Reporting focus gain, without startInput" Log.v(TAG, "Reporting focus gain, without startInput" Loading @@ -839,16 +859,102 @@ public final class InputMethodManager { } } } } /** * Used by {@link ImeFocusController} to finish current composing text. */ @Override @Override public void finishComposingText() { public void onViewFocusChanged(@Nullable View view, boolean hasFocus) { if (view == null || view.isTemporarilyDetached()) { return; } final ViewRootImpl viewRootImpl = view.getViewRootImpl(); synchronized (mH) { if (mCurRootView != viewRootImpl) { return; } if (!view.hasImeFocus() || !view.hasWindowFocus()) { return; } if (DEBUG) { Log.d(TAG, "onViewFocusChanged, view=" + InputMethodDebug.dumpViewInfo(view)); } // We don't need to track the next served view when the view lost focus here // because: // 1) The current view focus may be cleared temporary when in touch mode, closing // input at this moment isn't the right way. // 2) We only care about the served view change when it focused, since changing // input connection when the focus target changed is reasonable. // 3) Setting the next served view as null when no more served view should be // handled in other special events (e.g. view detached from window or the window // dismissed). if (hasFocus) { mNextServedView = view; } } viewRootImpl.dispatchCheckFocus(); } @Override public boolean checkFocus(boolean forceNewFocus, boolean startInput, ViewRootImpl viewRootImpl) { synchronized (mH) { synchronized (mH) { if (mCurRootView != viewRootImpl) { return false; } if (mServedView == mNextServedView && !forceNewFocus) { return false; } if (DEBUG) { Log.v(TAG, "checkFocus: view=" + mServedView + " next=" + mNextServedView + " force=" + forceNewFocus + " package=" + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>")); } // Close the connection when no next served view coming. if (mNextServedView == null) { finishInput(); closeCurrentInput(); return false; } mServedView = mNextServedView; if (mServedInputConnection != null) { if (mServedInputConnection != null) { mServedInputConnection.finishComposingTextFromImm(); mServedInputConnection.finishComposingTextFromImm(); } } } } if (startInput) { startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */, 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */); } return true; } @Override public void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl) { synchronized (mH) { if (mCurRootView != view.getViewRootImpl()) { return; } if (mNextServedView == view) { mNextServedView = null; } if (mServedView == view) { viewRootImpl.dispatchCheckFocus(); } } } @Override public void onWindowDismissed(ViewRootImpl viewRootImpl) { synchronized (mH) { if (mCurRootView != viewRootImpl) { return; } if (mServedView != null) { finishInput(); } setCurrentRootView(null); } } } /** /** Loading @@ -873,26 +979,6 @@ public final class InputMethodManager { return mCurRootView == rootView; return mCurRootView == rootView; } } } } /** * Checks whether the active input connection (if any) is for the given view. * * @see #hasActiveInputConnectionInternal(View)} */ @Override public boolean hasActiveConnection(View view) { return hasActiveInputConnectionInternal(view); } /** * Returns the {@link InputMethodManager#mH} lock object. * Used for {@link ImeFocusController} to guard the served view being accessed by * {@link InputMethodManager} in different threads. */ @Override public Object getLockObject() { return mH; } } } /** @hide */ /** @hide */ Loading Loading @@ -946,14 +1032,12 @@ public final class InputMethodManager { @GuardedBy("mH") @GuardedBy("mH") private View getServedViewLocked() { private View getServedViewLocked() { return mCurRootView != null ? mCurRootView.getImeFocusController().getServedViewLocked() return mCurRootView != null ? mServedView : null; : null; } } @GuardedBy("mH") @GuardedBy("mH") private View getNextServedViewLocked() { private View getNextServedViewLocked() { return mCurRootView != null ? mCurRootView.getImeFocusController().getNextServedViewLocked() return mCurRootView != null ? mNextServedView : null; : null; } } private ImeFocusController getFocusController() { private ImeFocusController getFocusController() { Loading Loading @@ -1802,8 +1886,12 @@ public final class InputMethodManager { @GuardedBy("mH") @GuardedBy("mH") void finishInputLocked() { void finishInputLocked() { mVirtualDisplayToScreenMatrix = null; mVirtualDisplayToScreenMatrix = null; final ImeFocusController controller = getFocusController(); View clearedView = null; final View clearedView = controller != null ? controller.clearServedViewsLocked() : null; mNextServedView = null; if (mServedView != null) { clearedView = mServedView; mServedView = null; } if (clearedView != null) { if (clearedView != null) { if (DEBUG) { if (DEBUG) { Log.v(TAG, "FINISH INPUT: mServedView=" Log.v(TAG, "FINISH INPUT: mServedView=" Loading Loading
core/java/android/view/ImeFocusController.java +14 −181 Original line number Original line Diff line number Diff line Loading @@ -17,8 +17,6 @@ package android.view; package android.view; import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS; import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS; import static android.view.ImeFocusControllerProto.NEXT_SERVED_VIEW; import static android.view.ImeFocusControllerProto.SERVED_VIEW; import android.annotation.AnyThread; import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.NonNull; Loading @@ -31,8 +29,6 @@ import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.inputmethod.StartInputReason; import java.util.Objects; /** /** * Responsible for IME focus handling inside {@link ViewRootImpl}. * Responsible for IME focus handling inside {@link ViewRootImpl}. * @hide * @hide Loading @@ -43,21 +39,6 @@ public final class ImeFocusController { private final ViewRootImpl mViewRootImpl; private final ViewRootImpl mViewRootImpl; private boolean mHasImeFocus = false; private boolean mHasImeFocus = false; /** * This is the view that should currently be served by an input method, * regardless of the state of setting that up. * @see InputMethodManagerDelegate#getLockObject() */ private View mServedView; /** * This is the next view that will be served by the input method, when * we get around to updating things. * @see InputMethodManagerDelegate#getLockObject() */ private View mNextServedView; private InputMethodManagerDelegate mDelegate; private InputMethodManagerDelegate mDelegate; @UiThread @UiThread Loading Loading @@ -134,122 +115,30 @@ public final class ImeFocusController { windowAttribute.softInputMode)); windowAttribute.softInputMode)); } } boolean forceFocus = false; getImmDelegate().onPostWindowFocus(viewForWindowFocus, windowAttribute); final InputMethodManagerDelegate immDelegate = getImmDelegate(); synchronized (immDelegate.getLockObject()) { // Update mNextServedView when focusedView changed. onViewFocusChanged(viewForWindowFocus, true); // Starting new input when the next focused view is same as served view but the // currently active connection (if any) is not associated with it. final boolean nextFocusIsServedView = mServedView == viewForWindowFocus; if (nextFocusIsServedView && !immDelegate.hasActiveConnection(viewForWindowFocus)) { forceFocus = true; } } immDelegate.startInputOnWindowFocusGain(viewForWindowFocus, windowAttribute.softInputMode, windowAttribute.flags, forceFocus); } } /** /** * @see InputMethodManager#checkFocus() * @see InputMethodManager#checkFocus() */ */ public boolean checkFocus(boolean forceNewFocus, boolean startInput) { public boolean checkFocus(boolean forceNewFocus, boolean startInput) { final InputMethodManagerDelegate immDelegate = getImmDelegate(); return getImmDelegate().checkFocus(forceNewFocus, startInput, mViewRootImpl); synchronized (immDelegate.getLockObject()) { if (!immDelegate.isCurrentRootView(mViewRootImpl)) { return false; } if (mServedView == mNextServedView && !forceNewFocus) { return false; } if (DEBUG) { Log.v(TAG, "checkFocus: view=" + mServedView + " next=" + mNextServedView + " force=" + forceNewFocus + " package=" + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>")); } // Close the connection when no next served view coming. if (mNextServedView == null) { immDelegate.finishInput(); immDelegate.closeCurrentIme(); return false; } mServedView = mNextServedView; immDelegate.finishComposingText(); } if (startInput) { immDelegate.startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */, 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */); } return true; } } @UiThread @UiThread void onViewFocusChanged(View view, boolean hasFocus) { void onViewFocusChanged(View view, boolean hasFocus) { if (view == null || view.isTemporarilyDetached()) { getImmDelegate().onViewFocusChanged(view, hasFocus); return; } final InputMethodManagerDelegate immDelegate = getImmDelegate(); synchronized (immDelegate.getLockObject()) { if (!immDelegate.isCurrentRootView(view.getViewRootImpl())) { return; } if (!view.hasImeFocus() || !view.hasWindowFocus()) { return; } if (DEBUG) { Log.d(TAG, "onViewFocusChanged, view=" + InputMethodDebug.dumpViewInfo(view) + ", mServedView=" + InputMethodDebug.dumpViewInfo(mServedView)); } // We don't need to track the next served view when the view lost focus here because: // 1) The current view focus may be cleared temporary when in touch mode, closing input // at this moment isn't the right way. // 2) We only care about the served view change when it focused, since changing input // connection when the focus target changed is reasonable. // 3) Setting the next served view as null when no more served view should be handled in // other special events (e.g. view detached from window or the window dismissed). if (hasFocus) { mNextServedView = view; } } mViewRootImpl.dispatchCheckFocus(); } } @UiThread @UiThread void onViewDetachedFromWindow(View view) { void onViewDetachedFromWindow(View view) { final InputMethodManagerDelegate immDelegate = getImmDelegate(); getImmDelegate().onViewDetachedFromWindow(view, mViewRootImpl); synchronized (immDelegate.getLockObject()) { if (!immDelegate.isCurrentRootView(view.getViewRootImpl())) { return; } if (mNextServedView == view) { mNextServedView = null; } if (mServedView == view) { mViewRootImpl.dispatchCheckFocus(); } } } } @UiThread @UiThread void onWindowDismissed() { void onWindowDismissed() { final InputMethodManagerDelegate immDelegate = getImmDelegate(); getImmDelegate().onWindowDismissed(mViewRootImpl); synchronized (immDelegate.getLockObject()) { if (!immDelegate.isCurrentRootView(mViewRootImpl)) { return; } if (mServedView != null) { immDelegate.finishInput(); } immDelegate.setCurrentRootView(null); } mHasImeFocus = false; mHasImeFocus = false; } } Loading Loading @@ -302,75 +191,21 @@ public final class ImeFocusController { public interface InputMethodManagerDelegate { public interface InputMethodManagerDelegate { /** /** * Starts the input connection. * Starts the input connection. * Note that this method must not hold the {@link InputMethodManager} lock with * {@link InputMethodManagerDelegate#getLockObject()} while {@link InputMethodManager} * calling into app-code in different threads. */ */ boolean startInput(@StartInputReason int startInputReason, View focusedView, boolean startInput(@StartInputReason int startInputReason, View focusedView, @StartInputFlags int startInputFlags, @StartInputFlags int startInputFlags, @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags); @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags); /** * Starts the input connection when gaining the window focus. void onPostWindowFocus(View viewForWindowFocus, * Note that this method must not hold the {@link InputMethodManager} lock with @NonNull WindowManager.LayoutParams windowAttribute); * {@link InputMethodManagerDelegate#getLockObject()} while {@link InputMethodManager} void onViewFocusChanged(@NonNull View view, boolean hasFocus); * calling into app-code in different threads. boolean checkFocus(boolean forceNewFocus, boolean startInput, ViewRootImpl viewRootImpl); */ void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl); void startInputOnWindowFocusGain(View rootView, void onWindowDismissed(ViewRootImpl viewRootImpl); @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus); void finishInput(); void finishInputAndReportToIme(); void finishInputAndReportToIme(); void closeCurrentIme(); void finishComposingText(); void setCurrentRootView(ViewRootImpl rootView); void setCurrentRootView(ViewRootImpl rootView); boolean isCurrentRootView(ViewRootImpl rootView); boolean isCurrentRootView(ViewRootImpl rootView); boolean hasActiveConnection(View view); /** * Returns the {@code InputMethodManager#mH} lock object. * Used for {@link ImeFocusController} to guard the served view being accessed by * {@link InputMethodManager} in different threads. */ Object getLockObject(); } /** * Returns The current IME served view for {@link InputMethodManager}. * Used to start input connection or check the caller's validity when calling * {@link InputMethodManager} APIs. * Note that this method requires to be called inside {@code InputMethodManager#mH} lock for * data consistency. */ public View getServedViewLocked() { return mServedView; } /** * Returns The next incoming IME served view for {@link InputMethodManager}. * Note that this method requires to be called inside {@code InputMethodManager#mH} lock for * data consistency. */ public View getNextServedViewLocked() { return mNextServedView; } /** * Clears the served & the next served view when the controller triggers * {@link InputMethodManagerDelegate#finishInput()} or * {@link InputMethodManagerDelegate#finishInputAndReportToIme()}. * Note that this method requires to be called inside {@code InputMethodManager#mH} lock for * data consistency. * * @return The {@code mServedView} that has cleared, or {@code null} means nothing to clear. */ public View clearServedViewsLocked() { View clearedView = null; mNextServedView = null; if (mServedView != null) { clearedView = mServedView; mServedView = null; } return clearedView; } } /** /** Loading @@ -384,8 +219,6 @@ public final class ImeFocusController { void dumpDebug(ProtoOutputStream proto, long fieldId) { void dumpDebug(ProtoOutputStream proto, long fieldId) { final long token = proto.start(fieldId); final long token = proto.start(fieldId); proto.write(HAS_IME_FOCUS, mHasImeFocus); proto.write(HAS_IME_FOCUS, mHasImeFocus); proto.write(SERVED_VIEW, Objects.toString(mServedView)); proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView)); proto.end(token); proto.end(token); } } } }
core/java/android/view/inputmethod/InputMethodManager.java +137 −49 Original line number Original line Diff line number Diff line Loading @@ -94,6 +94,7 @@ import android.view.KeyEvent; import android.view.View; import android.view.View; import android.view.ViewRootImpl; import android.view.ViewRootImpl; import android.view.WindowInsets; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillId; import android.view.autofill.AutofillId; import android.view.autofill.AutofillManager; import android.view.autofill.AutofillManager; Loading Loading @@ -464,6 +465,22 @@ public final class InputMethodManager { // ----------------------------------------------------------- // ----------------------------------------------------------- /** * This is the view that should currently be served by an input method, * regardless of the state of setting that up. */ @Nullable @GuardedBy("mH") private View mServedView; /** * This is the next view that will be served by the input method, when * we get around to updating things. */ @Nullable @GuardedBy("mH") private View mNextServedView; /** /** * This is the root view of the overall window that currently has input * This is the root view of the overall window that currently has input * method focus. * method focus. Loading Loading @@ -699,8 +716,7 @@ public final class InputMethodManager { if (mCurRootView == null) { if (mCurRootView == null) { return null; return null; } } final View servedView = mCurRootView.getImeFocusController().getServedViewLocked(); return mServedView != null ? mServedView.getContext() : null; return servedView != null ? servedView.getContext() : null; } } } } Loading Loading @@ -735,11 +751,7 @@ public final class InputMethodManager { startInputFlags, softInputMode, windowFlags); startInputFlags, softInputMode, windowFlags); } } /** private void finishInput() { * Used by {@link ImeFocusController} to finish input connection. */ @Override public void finishInput() { ImeTracing.getInstance().triggerClientDump( ImeTracing.getInstance().triggerClientDump( "InputMethodManager.DelegateImpl#finishInput", InputMethodManager.this, "InputMethodManager.DelegateImpl#finishInput", InputMethodManager.this, null /* icProto */); null /* icProto */); Loading Loading @@ -768,19 +780,28 @@ public final class InputMethodManager { } } } } /** * Used by {@link ImeFocusController} to hide current input method editor. */ @Override @Override public void closeCurrentIme() { public void onPostWindowFocus(View viewForWindowFocus, closeCurrentInput(); @NonNull WindowManager.LayoutParams windowAttribute) { boolean forceFocus = false; synchronized (mH) { // Update mNextServedView when focusedView changed. onViewFocusChanged(viewForWindowFocus, true); // Starting new input when the next focused view is same as served view but the // currently active connection (if any) is not associated with it. final boolean nextFocusIsServedView = mServedView == viewForWindowFocus; if (nextFocusIsServedView && !hasActiveInputConnectionInternal(viewForWindowFocus)) { forceFocus = true; } } startInputOnWindowFocusGain(viewForWindowFocus, windowAttribute.softInputMode, windowAttribute.flags, forceFocus); } } /** private void startInputOnWindowFocusGain(View focusedView, * For {@link ImeFocusController} to start input when gaining the window focus. */ @Override public void startInputOnWindowFocusGain(View focusedView, @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) { @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) { int startInputFlags = getStartInputFlags(focusedView, 0); int startInputFlags = getStartInputFlags(focusedView, 0); startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS; startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS; Loading Loading @@ -816,8 +837,7 @@ public final class InputMethodManager { synchronized (mH) { synchronized (mH) { // For some reason we didn't do a startInput + windowFocusGain, so // For some reason we didn't do a startInput + windowFocusGain, so // we'll just do a window focus gain and call it a day. // we'll just do a window focus gain and call it a day. View servedView = controller.getServedViewLocked(); boolean nextFocusHasConnection = mServedView != null && mServedView == focusedView boolean nextFocusHasConnection = servedView != null && servedView == focusedView && hasActiveInputConnectionInternal(focusedView); && hasActiveInputConnectionInternal(focusedView); if (DEBUG) { if (DEBUG) { Log.v(TAG, "Reporting focus gain, without startInput" Log.v(TAG, "Reporting focus gain, without startInput" Loading @@ -839,16 +859,102 @@ public final class InputMethodManager { } } } } /** * Used by {@link ImeFocusController} to finish current composing text. */ @Override @Override public void finishComposingText() { public void onViewFocusChanged(@Nullable View view, boolean hasFocus) { if (view == null || view.isTemporarilyDetached()) { return; } final ViewRootImpl viewRootImpl = view.getViewRootImpl(); synchronized (mH) { if (mCurRootView != viewRootImpl) { return; } if (!view.hasImeFocus() || !view.hasWindowFocus()) { return; } if (DEBUG) { Log.d(TAG, "onViewFocusChanged, view=" + InputMethodDebug.dumpViewInfo(view)); } // We don't need to track the next served view when the view lost focus here // because: // 1) The current view focus may be cleared temporary when in touch mode, closing // input at this moment isn't the right way. // 2) We only care about the served view change when it focused, since changing // input connection when the focus target changed is reasonable. // 3) Setting the next served view as null when no more served view should be // handled in other special events (e.g. view detached from window or the window // dismissed). if (hasFocus) { mNextServedView = view; } } viewRootImpl.dispatchCheckFocus(); } @Override public boolean checkFocus(boolean forceNewFocus, boolean startInput, ViewRootImpl viewRootImpl) { synchronized (mH) { synchronized (mH) { if (mCurRootView != viewRootImpl) { return false; } if (mServedView == mNextServedView && !forceNewFocus) { return false; } if (DEBUG) { Log.v(TAG, "checkFocus: view=" + mServedView + " next=" + mNextServedView + " force=" + forceNewFocus + " package=" + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>")); } // Close the connection when no next served view coming. if (mNextServedView == null) { finishInput(); closeCurrentInput(); return false; } mServedView = mNextServedView; if (mServedInputConnection != null) { if (mServedInputConnection != null) { mServedInputConnection.finishComposingTextFromImm(); mServedInputConnection.finishComposingTextFromImm(); } } } } if (startInput) { startInput(StartInputReason.CHECK_FOCUS, null /* focusedView */, 0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */); } return true; } @Override public void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl) { synchronized (mH) { if (mCurRootView != view.getViewRootImpl()) { return; } if (mNextServedView == view) { mNextServedView = null; } if (mServedView == view) { viewRootImpl.dispatchCheckFocus(); } } } @Override public void onWindowDismissed(ViewRootImpl viewRootImpl) { synchronized (mH) { if (mCurRootView != viewRootImpl) { return; } if (mServedView != null) { finishInput(); } setCurrentRootView(null); } } } /** /** Loading @@ -873,26 +979,6 @@ public final class InputMethodManager { return mCurRootView == rootView; return mCurRootView == rootView; } } } } /** * Checks whether the active input connection (if any) is for the given view. * * @see #hasActiveInputConnectionInternal(View)} */ @Override public boolean hasActiveConnection(View view) { return hasActiveInputConnectionInternal(view); } /** * Returns the {@link InputMethodManager#mH} lock object. * Used for {@link ImeFocusController} to guard the served view being accessed by * {@link InputMethodManager} in different threads. */ @Override public Object getLockObject() { return mH; } } } /** @hide */ /** @hide */ Loading Loading @@ -946,14 +1032,12 @@ public final class InputMethodManager { @GuardedBy("mH") @GuardedBy("mH") private View getServedViewLocked() { private View getServedViewLocked() { return mCurRootView != null ? mCurRootView.getImeFocusController().getServedViewLocked() return mCurRootView != null ? mServedView : null; : null; } } @GuardedBy("mH") @GuardedBy("mH") private View getNextServedViewLocked() { private View getNextServedViewLocked() { return mCurRootView != null ? mCurRootView.getImeFocusController().getNextServedViewLocked() return mCurRootView != null ? mNextServedView : null; : null; } } private ImeFocusController getFocusController() { private ImeFocusController getFocusController() { Loading Loading @@ -1802,8 +1886,12 @@ public final class InputMethodManager { @GuardedBy("mH") @GuardedBy("mH") void finishInputLocked() { void finishInputLocked() { mVirtualDisplayToScreenMatrix = null; mVirtualDisplayToScreenMatrix = null; final ImeFocusController controller = getFocusController(); View clearedView = null; final View clearedView = controller != null ? controller.clearServedViewsLocked() : null; mNextServedView = null; if (mServedView != null) { clearedView = mServedView; mServedView = null; } if (clearedView != null) { if (clearedView != null) { if (DEBUG) { if (DEBUG) { Log.v(TAG, "FINISH INPUT: mServedView=" Log.v(TAG, "FINISH INPUT: mServedView=" Loading