Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 970d9d2e authored by lumark's avatar lumark
Browse files

Introduce ImeFocusController

Introduced ImeFocusController to manage
IME focus target and retrieve IME target event from ViewRootImpl.

With this CL, removed InputMethodManager focus check related methods
and replaced with below methods in ImeFocusController:
  - onPreWindowFocus
  - onPostWindowFocus
  - onViewFocusChanged
  - onViewDetachedFromWindow
  - onWindowDismissed
  - onProcessImeInputStage

Since all methods are guaranteed to interact within View or
ViewRootImpl with UiThread, so it also benefits that we can without any
lock protection, and ViewRootImpl will no longer depend on
InputMethodManager methods call in the end.

Also, Add mHasImeFocus in View.AttachInfo, for propogating if this view's
window is IME focused, if so, View#notifyFocusChangeToInputMethodManager
-> ImeFocusController#onViewFocusChanged will be called to
notify the focus change to corresponding IME focus controller.

Bug: 141738570
Test: atest SearchViewTest, FocusHandlingTest
Test: atest ActivityViewTest, MultiDisplayClientTests,
      MultiDisplaySystemDecorationTests

Change-Id: Ib455704fe1e9d243f93190a84f230210dbceac2a
parent e27db5d3
Loading
Loading
Loading
Loading
+234 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.util.Log;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;

/**
 * Responsible for IME focus handling inside {@link ViewRootImpl}.
 * @hide
 */
public final class ImeFocusController {
    private static final boolean DEBUG = false;
    private static final String TAG = "ImeFocusController";

    private final ViewRootImpl mViewRootImpl;
    private boolean mHasImeFocus = false;
    private View mServedView;
    private View mNextServedView;

    @UiThread
    ImeFocusController(@NonNull ViewRootImpl viewRootImpl) {
        mViewRootImpl = viewRootImpl;
    }

    private InputMethodManagerDelegate getImmDelegate() {
        return mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
    }

    @UiThread
    void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
        final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */);
        if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
            return;
        }
        if (hasImeFocus == mHasImeFocus) {
            return;
        }
        mHasImeFocus = hasImeFocus;
        if (mHasImeFocus) {
            onPreWindowFocus(true /* hasWindowFocus */, windowAttribute);
            onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */,
                    windowAttribute);
        }
    }

    @UiThread
    void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
        if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
            return;
        }
        if (hasWindowFocus) {
            getImmDelegate().setCurrentRootView(mViewRootImpl);
        }
    }

    @UiThread
    boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) {
        final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
                windowAttribute.flags);
        if (force) {
            mHasImeFocus = hasImeFocus;
        }
        return hasImeFocus;
    }

    @UiThread
    void onPostWindowFocus(View focusedView, boolean hasWindowFocus,
            WindowManager.LayoutParams windowAttribute) {
        if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
            return;
        }
        if (DEBUG) {
            Log.v(TAG, "onWindowFocus: " + focusedView
                    + " softInputMode=" + InputMethodDebug.softInputModeToString(
                    windowAttribute.softInputMode));
        }

        boolean forceFocus = false;
        if (getImmDelegate().isRestartOnNextWindowFocus(true /* reset */)) {
            if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
            forceFocus = true;
        }
        // Update mNextServedView when focusedView changed.
        final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
        onViewFocusChanged(viewForWindowFocus, true);

        getImmDelegate().startInputAsyncOnWindowFocusGain(viewForWindowFocus,
                windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
    }

    public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
        if (!getImmDelegate().isCurrentRootView(mViewRootImpl)
                || (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) {
            getImmDelegate().finishInput();
            getImmDelegate().closeCurrentIme();
            return false;
        }
        mServedView = mNextServedView;
        getImmDelegate().finishComposingText();

        if (startInput) {
            getImmDelegate().startInput(StartInputReason.CHECK_FOCUS, null, 0, 0, 0);
        }
        return true;
    }

    @UiThread
    void onViewFocusChanged(View view, boolean hasFocus) {
        if (view == null || view.isTemporarilyDetached()) {
            return;
        }
        if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
            return;
        }
        if (mServedView == view || !view.hasImeFocus() || !view.hasWindowFocus()) {
            return;
        }
        mNextServedView = hasFocus ? view : null;
        mViewRootImpl.dispatchCheckFocus();
    }

    @UiThread
    void onViewDetachedFromWindow(View view) {
        if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
            return;
        }
        if (mServedView == view) {
            mNextServedView = null;
            mViewRootImpl.dispatchCheckFocus();
        }
    }

    @UiThread
    void onWindowDismissed() {
        if (!getImmDelegate().isCurrentRootView(mViewRootImpl)) {
            return;
        }
        if (mServedView != null) {
            getImmDelegate().finishInput();
        }
        getImmDelegate().setCurrentRootView(null);
        mHasImeFocus = false;
    }

    /**
     * @param windowAttribute {@link WindowManager.LayoutParams} to be checked.
     * @return Whether the window is in local focus mode or not.
     */
    @AnyThread
    private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) {
        return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
    }

    int onProcessImeInputStage(Object token, InputEvent event,
            WindowManager.LayoutParams windowAttribute,
            InputMethodManager.FinishedInputEventCallback callback) {
        if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
            return InputMethodManager.DISPATCH_NOT_HANDLED;
        }
        final InputMethodManager imm =
                mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
        if (imm == null) {
            return InputMethodManager.DISPATCH_NOT_HANDLED;
        }
        return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler);
    }

    /**
     * A delegate implementing some basic {@link InputMethodManager} APIs.
     * @hide
     */
    public interface InputMethodManagerDelegate {
        boolean startInput(@StartInputReason int startInputReason, View focusedView,
                @StartInputFlags int startInputFlags,
                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
        void startInputAsyncOnWindowFocusGain(View rootView,
                @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
                boolean forceNewFocus);
        void finishInput();
        void closeCurrentIme();
        void finishComposingText();
        void setCurrentRootView(ViewRootImpl rootView);
        boolean isCurrentRootView(ViewRootImpl rootView);
        boolean isRestartOnNextWindowFocus(boolean reset);
    }

    public View getServedView() {
        return mServedView;
    }

    public View getNextServedView() {
        return mNextServedView;
    }

    public void setServedView(View view) {
        mServedView = view;
    }

    public void setNextServedView(View view) {
        mNextServedView = view;
    }
}
+27 −24
Original line number Diff line number Diff line
@@ -130,7 +130,6 @@ import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.ContentCaptureSession;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.view.inspector.InspectableProperty;
import android.view.inspector.InspectableProperty.EnumEntry;
import android.view.inspector.InspectableProperty.FlagEntry;
@@ -7942,12 +7941,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            if (isPressed()) {
                setPressed(false);
            }
            if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
                notifyFocusChangeToInputMethodManager(false /* hasFocus */);
            if (hasWindowFocus()) {
                notifyFocusChangeToImeFocusController(false /* hasFocus */);
            }
            onFocusLost();
        } else if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
            notifyFocusChangeToInputMethodManager(true /* hasFocus */);
        } else if (hasWindowFocus()) {
            notifyFocusChangeToImeFocusController(true /* hasFocus */);
        }
        invalidate(true);
@@ -7964,23 +7963,15 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
    }
    /**
     * Notify {@link InputMethodManager} about the focus change of the {@link View}.
     *
     * <p>Does nothing when {@link InputMethodManager} is not available.</p>
     * Notify {@link ImeFocusController} about the focus change of the {@link View}.
     *
     * @param hasFocus {@code true} when the {@link View} is being focused.
     */
    private void notifyFocusChangeToInputMethodManager(boolean hasFocus) {
        final InputMethodManager imm =
                getContext().getSystemService(InputMethodManager.class);
        if (imm == null) {
    private void notifyFocusChangeToImeFocusController(boolean hasFocus) {
        if (mAttachInfo == null) {
            return;
        }
        if (hasFocus) {
            imm.focusIn(this);
        } else {
            imm.focusOut(this);
        }
        mAttachInfo.mViewRootImpl.getImeFocusController().onViewFocusChanged(this, hasFocus);
    }
    /** @hide */
@@ -13918,7 +13909,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
        onFinishTemporaryDetach();
        if (hasWindowFocus() && hasFocus()) {
            notifyFocusChangeToInputMethodManager(true /* hasFocus */);
            notifyFocusChangeToImeFocusController(true /* hasFocus */);
        }
        notifyEnterOrExitForAutoFillIfNeeded(true);
        notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
@@ -14326,13 +14317,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
            if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
                notifyFocusChangeToInputMethodManager(false /* hasFocus */);
                notifyFocusChangeToImeFocusController(false /* hasFocus */);
            }
            removeLongPressCallback();
            removeTapCallback();
            onFocusLost();
        } else if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
            notifyFocusChangeToInputMethodManager(true /* hasFocus */);
            notifyFocusChangeToImeFocusController(true /* hasFocus */);
        }
        refreshDrawableState();
@@ -14348,6 +14339,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        return mAttachInfo != null && mAttachInfo.mHasWindowFocus;
    }
    /**
     * @return {@code true} if this view is in a window that currently has IME focusable state.
     * @hide
     */
    public boolean hasImeFocus() {
        return mAttachInfo != null && mAttachInfo.mHasImeFocus;
    }
    /**
     * Dispatch a view visibility change down the view hierarchy.
     * ViewGroups should override to route to their children.
@@ -19644,7 +19643,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        rebuildOutline();
        if (isFocused()) {
            notifyFocusChangeToInputMethodManager(true /* hasFocus */);
            notifyFocusChangeToImeFocusController(true /* hasFocus */);
        }
    }
@@ -20227,9 +20226,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        onDetachedFromWindow();
        onDetachedFromWindowInternal();
        InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
        if (imm != null) {
            imm.onViewDetachedFromWindow(this);
        if (info != null) {
            info.mViewRootImpl.getImeFocusController().onViewDetachedFromWindow(this);
        }
        ListenerInfo li = mListenerInfo;
@@ -28564,6 +28562,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
        @UnsupportedAppUsage
        boolean mHasWindowFocus;
        /**
         * Indicates whether the view's window has IME focused.
         */
        boolean mHasImeFocus;
        /**
         * The current visibility of the window.
         */
+32 −50
Original line number Diff line number Diff line
@@ -453,7 +453,6 @@ public final class ViewRootImpl implements ViewParent,
    boolean mReportNextDraw;
    boolean mFullRedrawNeeded;
    boolean mNewSurfaceNeeded;
    boolean mLastWasImTarget;
    boolean mForceNextWindowRelayout;
    CountDownLatch mWindowDrawCountDown;

@@ -619,6 +618,16 @@ public final class ViewRootImpl implements ViewParent,
            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
                    new InputEventConsistencyVerifier(this, 0) : null;

    private final ImeFocusController mImeFocusController;

    /**
     * @return {@link ImeFocusController} for this instance.
     */
    @NonNull
    public ImeFocusController getImeFocusController() {
        return mImeFocusController;
    }

    private final InsetsController mInsetsController = new InsetsController(this);

    private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
@@ -704,6 +713,7 @@ public final class ViewRootImpl implements ViewParent,
        }

        loadSystemProperties();
        mImeFocusController = new ImeFocusController(this);
    }

    public static void addFirstDrawHandler(Runnable callback) {
@@ -1050,11 +1060,6 @@ public final class ViewRootImpl implements ViewParent,
        }
    }

    /** Whether the window is in local focus mode or not */
    private boolean isInLocalFocusMode() {
        return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
    }

    @UnsupportedAppUsage
    public int getWindowFlags() {
        return mWindowAttributes.flags;
@@ -2888,19 +2893,7 @@ public final class ViewRootImpl implements ViewParent,
        mViewVisibility = viewVisibility;
        mHadWindowFocus = hasWindowFocus;

        if (hasWindowFocus && !isInLocalFocusMode()) {
            final boolean imTarget = WindowManager.LayoutParams
                    .mayUseInputMethod(mWindowAttributes.flags);
            if (imTarget != mLastWasImTarget) {
                mLastWasImTarget = imTarget;
                InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
                if (imm != null && imTarget) {
                    imm.onPreWindowFocus(mView, hasWindowFocus);
                    imm.onPostWindowFocus(mView, mView.findFocus(),
                            mWindowAttributes.softInputMode, mWindowAttributes.flags);
                }
            }
        }
        mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);

        // Remember if we must report the next draw.
        if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
@@ -3068,14 +3061,10 @@ public final class ViewRootImpl implements ViewParent,
            }

            mAttachInfo.mHasWindowFocus = hasWindowFocus;
            mAttachInfo.mHasImeFocus = mImeFocusController.updateImeFocusable(
                    mWindowAttributes, true /* force */);
            mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes);

            mLastWasImTarget = WindowManager.LayoutParams
                    .mayUseInputMethod(mWindowAttributes.flags);

            InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
            if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                imm.onPreWindowFocus(mView, hasWindowFocus);
            }
            if (mView != null) {
                mAttachInfo.mKeyDispatchState.reset();
                mView.dispatchWindowFocusChanged(hasWindowFocus);
@@ -3087,11 +3076,10 @@ public final class ViewRootImpl implements ViewParent,

            // Note: must be done after the focus change callbacks,
            // so all of the view state is set up correctly.
            mImeFocusController.onPostWindowFocus(mView.findFocus(), hasWindowFocus,
                    mWindowAttributes);

            if (hasWindowFocus) {
                if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
                    imm.onPostWindowFocus(mView, mView.findFocus(),
                            mWindowAttributes.softInputMode, mWindowAttributes.flags);
                }
                // Clear the forward bit.  We can just do this directly, since
                // the window manager doesn't care about it.
                mWindowAttributes.softInputMode &=
@@ -4887,10 +4875,7 @@ public final class ViewRootImpl implements ViewParent,
                    enqueueInputEvent(event, null, 0, true);
                } break;
                case MSG_CHECK_FOCUS: {
                    InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
                    if (imm != null) {
                        imm.checkFocus();
                    }
                    getImeFocusController().checkFocus(false, true);
                } break;
                case MSG_CLOSE_SYSTEM_DIALOGS: {
                    if (mView != null) {
@@ -5454,23 +5439,20 @@ public final class ViewRootImpl implements ViewParent,

        @Override
        protected int onProcess(QueuedInputEvent q) {
            if (mLastWasImTarget && !isInLocalFocusMode()) {
                InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
                if (imm != null) {
                    final InputEvent event = q.mEvent;
                    if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event);
                    int result = imm.dispatchInputEvent(event, q, this, mHandler);
                    if (result == InputMethodManager.DISPATCH_HANDLED) {
                        return FINISH_HANDLED;
                    } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
            final int result = mImeFocusController.onProcessImeInputStage(
                    q, q.mEvent, mWindowAttributes, this);
            switch (result) {
                case InputMethodManager.DISPATCH_IN_PROGRESS:
                    // callback will be invoked later
                    return DEFER;
                case InputMethodManager.DISPATCH_NOT_HANDLED:
                    // The IME could not handle it, so skip along to the next InputStage
                    return FORWARD;
                    } else {
                        return DEFER; // callback will be invoked later
                    }
                }
                case InputMethodManager.DISPATCH_HANDLED:
                    return FINISH_HANDLED;
                default:
                    throw new IllegalStateException("Unexpected result=" + result);
            }
            return FORWARD;
        }

        @Override
+2 −5
Original line number Diff line number Diff line
@@ -487,11 +487,8 @@ public final class WindowManagerGlobal {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        if (root != null) {
            root.getImeFocusController().onWindowDismissed();
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
+3 −2
Original line number Diff line number Diff line
@@ -758,8 +758,9 @@ public class BaseInputConnection implements InputConnection {
            Context context;
            if (mTargetView != null) {
                context = mTargetView.getContext();
            } else if (mIMM.mServedView != null) {
                context = mIMM.mServedView.getContext();
            } else if (mIMM.mCurRootView != null) {
                final View servedView = mIMM.mCurRootView.getImeFocusController().getServedView();
                context = servedView != null ? servedView.getContext() : null;
            } else {
                context = null;
            }
Loading