All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.android.view.ImeFocusController Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * 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 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.NonNull;
import android.annotation.UiThread;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.inputmethod.InputMethodManager;

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

import java.util.Objects;

/**
 * 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;
    private InputMethodManagerDelegate mDelegate;

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

    @NonNull
    private InputMethodManagerDelegate getImmDelegate() {
        InputMethodManagerDelegate delegate = mDelegate;
        if (delegate != null) {
            return delegate;
        }
        delegate = mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
        mDelegate = delegate;
        return delegate;
    }

    /** Called when the view root is moved to a different display. */
    @UiThread
    void onMovedToDisplay() {
        // InputMethodManager managed its instances for different displays. So if the associated
        // display is changed, the delegate also needs to be refreshed (by getImmDelegate).
        // See the comment in {@link android.app.SystemServiceRegistry} for InputMethodManager
        // and {@link android.view.inputmethod.InputMethodManager#forContext}.
        mDelegate = null;
    }

    @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;
        }
        View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
        if (DEBUG) {
            Log.v(TAG, "onWindowFocus: " + viewForWindowFocus
                    + " softInputMode=" + InputMethodDebug.softInputModeToString(
                    windowAttribute.softInputMode));
        }

        boolean forceFocus = false;
        final InputMethodManagerDelegate immDelegate = getImmDelegate();
        if (immDelegate.isRestartOnNextWindowFocus(true /* reset */)) {
            if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
            forceFocus = true;
        }

        // 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.startInputAsyncOnWindowFocusGain(viewForWindowFocus,
                windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
    }

    public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
        final InputMethodManagerDelegate immDelegate = getImmDelegate();
        if (!immDelegate.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() : ""));

        // 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
    void onViewFocusChanged(View view, boolean hasFocus) {
        if (view == null || view.isTemporarilyDetached()) {
            return;
        }
        if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
            return;
        }
        if (!view.hasImeFocus() || !view.hasWindowFocus()) {
            return;
        }
        if (DEBUG) Log.d(TAG, "onViewFocusChanged, view=" + view + ", mServedView=" + 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
    void onViewDetachedFromWindow(View view) {
        if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
            return;
        }
        if (mServedView == view) {
            mNextServedView = null;
            mViewRootImpl.dispatchCheckFocus();
        }
    }

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

    /**
     * To handle the lifecycle of the input connection when the device interactivity state changed.
     * (i.e. Calling IMS#onFinishInput when the device screen-off and Calling IMS#onStartInput
     * when the device screen-on again).
     */
    @UiThread
    public void onInteractiveChanged(boolean interactive) {
        final InputMethodManagerDelegate immDelegate = getImmDelegate();
        if (!immDelegate.isCurrentRootView(mViewRootImpl)) {
            return;
        }
        if (interactive) {
            final View focusedView = mViewRootImpl.mView.findFocus();
            onViewFocusChanged(focusedView, focusedView != null);
        } else {
            mDelegate.finishInputAndReportToIme();
        }
    }

    /**
     * @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 finishInputAndReportToIme();
        void closeCurrentIme();
        void finishComposingText();
        void setCurrentRootView(ViewRootImpl rootView);
        boolean isCurrentRootView(ViewRootImpl rootView);
        boolean isRestartOnNextWindowFocus(boolean reset);
        boolean hasActiveConnection(View view);
    }

    public View getServedView() {
        return mServedView;
    }

    public View getNextServedView() {
        return mNextServedView;
    }

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

    public void setNextServedView(View view) {
        mNextServedView = view;
    }

    /**
     * Indicates whether the view's window has IME focused.
     */
    @UiThread
    boolean hasImeFocus() {
        return mHasImeFocus;
    }

    void dumpDebug(ProtoOutputStream proto, long fieldId) {
        final long token = proto.start(fieldId);
        proto.write(HAS_IME_FOCUS, mHasImeFocus);
        proto.write(SERVED_VIEW, Objects.toString(mServedView));
        proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView));
        proto.end(token);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy