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

Commit baebf0fe authored by Ming-Shin Lu's avatar Ming-Shin Lu
Browse files

Introduce ImeVisibilityApplier

With go/new-ime-visibility-control-u, this CL introduced
ImeVisibilityApplier interface to abstract the implementation of
applying IME visibility with adjusting IME z-ordering for aiming to
stablize IME z-ordering control.

Note that this is the first CL with
- Refactoring part of IMMS#{show, hide}CurrentInputLocked logic to a
  default implementation class of ImeVisibilityAppler
- Clean-up IMMS#{mShowRequestWindowMap, mHideRequestWindowMap} with
  replaced by ImeVisibilityComputer.WindowState#setRequestImeToken

Will keep update follow-up CLs for clean-up applying IME visiblity and
adjusting IME z-ordering stuffs.

Bug: 246309664
Test: atest CtsInputMethodTestCases
Change-Id: I410a29ce4a4e27b2ffa66c9d090d969eb43d0e36

Change-Id: I12d99c15ae0d8965a21406d2495ce5cb18afaea0
parent 4c1ffefb
Loading
Loading
Loading
Loading
+114 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.server.inputmethod;

import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;

import static com.android.server.EventLogTags.IMF_HIDE_IME;
import static com.android.server.EventLogTags.IMF_SHOW_IME;

import android.annotation.Nullable;
import android.os.Binder;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.util.EventLog;
import android.util.Slog;
import android.view.inputmethod.ImeTracker;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;

import java.util.Objects;

/**
 * The default implementation of {@link ImeVisibilityApplier} used in
 * {@link InputMethodManagerService}.
 */
final class DefaultImeVisibilityApplier implements ImeVisibilityApplier {

    private static final String TAG = "DefaultImeVisibilityApplier";

    private static final boolean DEBUG = InputMethodManagerService.DEBUG;

    private InputMethodManagerService mService;

    DefaultImeVisibilityApplier(InputMethodManagerService service) {
        mService = service;
    }

    @GuardedBy("ImfLock.class")
    @Override
    public void performShowIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
            int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
        final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
        if (curMethod != null) {
            // create a placeholder token for IMS so that IMS cannot inject windows into client app.
            final IBinder showInputToken = new Binder();
            mService.setRequestImeTokenToWindow(windowToken, showInputToken);
            if (DEBUG) {
                Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
                        + ", " + showFlags + ", " + resultReceiver + ") for reason: "
                        + InputMethodDebug.softInputDisplayReasonToString(reason));
            }
            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
            if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
                if (DEBUG_IME_VISIBILITY) {
                    EventLog.writeEvent(IMF_SHOW_IME, statsToken.getTag(),
                            Objects.toString(mService.mCurFocusedWindow),
                            InputMethodDebug.softInputDisplayReasonToString(reason),
                            InputMethodDebug.softInputModeToString(
                                    mService.mCurFocusedWindowSoftInputMode));
                }
                mService.onShowHideSoftInputRequested(true /* show */, windowToken, reason,
                        statsToken);
            }
        }
    }

    @GuardedBy("ImfLock.class")
    @Override
    public void performHideIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
        final IInputMethodInvoker curMethod = mService.getCurMethodLocked();
        if (curMethod != null) {
            final Binder hideInputToken = new Binder();
            mService.setRequestImeTokenToWindow(windowToken, hideInputToken);
            // The IME will report its visible state again after the following message finally
            // delivered to the IME process as an IPC.  Hence the inconsistency between
            // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
            // the final state.
            if (DEBUG) {
                Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
                        + ", " + resultReceiver + ") for reason: "
                        + InputMethodDebug.softInputDisplayReasonToString(reason));
            }
            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
            if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) {
                if (DEBUG_IME_VISIBILITY) {
                    EventLog.writeEvent(IMF_HIDE_IME, statsToken.getTag(),
                            Objects.toString(mService.mCurFocusedWindow),
                            InputMethodDebug.softInputDisplayReasonToString(reason),
                            InputMethodDebug.softInputModeToString(
                                    mService.mCurFocusedWindowSoftInputMode));
                }
                mService.onShowHideSoftInputRequested(false /* show */, windowToken, reason,
                        statsToken);
            }
        }
    }
}
+80 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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 com.android.server.inputmethod;

import android.annotation.Nullable;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.view.inputmethod.ImeTracker;

import com.android.internal.inputmethod.SoftInputShowHideReason;

/**
 * Interface for IME visibility operations like show/hide and update Z-ordering relative to the IME
 * targeted window.
 */
interface ImeVisibilityApplier {
    /**
     * Performs showing IME on top of the given window.
     *
     * @param windowToken    The token of a window that currently has focus.
     * @param statsToken     A token that tracks the progress of an IME request.
     * @param showFlags      Provides additional operating flags to show IME.
     * @param resultReceiver If non-null, this will be called back to the caller when
     *                       it has processed request to tell what it has done.
     * @param reason         The reason for requesting to show IME.
     */
    default void performShowIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
            int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}

    /**
     * Performs hiding IME to the given window
     *
     * @param windowToken    The token of a window that currently has focus.
     * @param statsToken     A token that tracks the progress of an IME request.
     * @param resultReceiver If non-null, this will be called back to the caller when
     *                       it has processed request to tell what it has done.
     * @param reason         The reason for requesting to hide IME.
     */
    default void performHideIme(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {}

    /**
     * Applies the IME visibility from {@link android.inputmethodservice.InputMethodService} with
     * according to the given visibility state.
     *
     * @param windowToken The token of a window for applying the IME visibility
     * @param state The new IME visibility state for the applier to handle
     */
    default void applyImeVisibility(IBinder windowToken,
            @ImeVisibilityStateComputer.VisibilityState int state) {
        // TODO: migrate IMMS#applyImeVisibility logic to here.
    }

    /**
     * Updates the IME Z-ordering relative to the given window.
     *
     * This used to adjust the IME relative layer of the window during
     * {@link InputMethodManagerService} is in switching IME clients.
     *
     * @param windowToken The token of a window to update the Z-ordering relative to the IME.
     */
    default void updateImeLayeringByTarget(IBinder windowToken) {
        // TODO: add a method in WindowManagerInternal to call DC#updateImeInputAndControlTarget
        //  here to end up updating IME layering after IMMS#attachNewInputLocked called.
    }
}
+10 −1
Original line number Diff line number Diff line
@@ -201,6 +201,14 @@ public final class ImeVisibilityStateComputer {
        return state;
    }

    void setRequestImeTokenToWindow(IBinder windowToken, IBinder token) {
        ImeTargetWindowState state = getWindowStateOrNull(windowToken);
        if (state != null) {
            state.setRequestImeToken(token);
            setWindowState(windowToken, state);
        }
    }

    void setWindowState(IBinder windowToken, ImeTargetWindowState newState) {
        if (DEBUG) Slog.d(TAG, "setWindowState, windowToken=" + windowToken
                + ", state=" + newState);
@@ -214,7 +222,8 @@ public final class ImeVisibilityStateComputer {
                return windowToken;
            }
        }
        return null;
        // Fallback to the focused window for some edge cases (e.g. relaunching the activity)
        return mService.mCurFocusedWindow;
    }

    void dumpDebug(ProtoOutputStream proto, long fieldId) {
+18 −65
Original line number Diff line number Diff line
@@ -46,10 +46,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;

import static com.android.server.EventLogTags.IMF_HIDE_IME;
import static com.android.server.EventLogTags.IMF_SHOW_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT;
import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed;
@@ -300,6 +297,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
    @GuardedBy("ImfLock.class")
    @NonNull private final ImeVisibilityStateComputer mVisibilityStateComputer;

    @GuardedBy("ImfLock.class")
    @NonNull private final DefaultImeVisibilityApplier mVisibilityApplier;

    /**
     * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
     *
@@ -964,22 +964,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        }
    }

    /**
     * Map of generated token to windowToken that is requesting
     * {@link InputMethodManager#showSoftInput(View, int)}.
     * This map tracks origin of showSoftInput requests.
     */
    @GuardedBy("ImfLock.class")
    private final WeakHashMap<IBinder, IBinder> mShowRequestWindowMap = new WeakHashMap<>();

    /**
     * Map of generated token to windowToken that is requesting
     * {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}.
     * This map tracks origin of hideSoftInput requests.
     */
    @GuardedBy("ImfLock.class")
    private final WeakHashMap<IBinder, IBinder> mHideRequestWindowMap = new WeakHashMap<>();

    /**
     * A ring buffer to store the history of {@link StartInputInfo}.
     */
@@ -1740,6 +1724,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        mAutofillController = new AutofillSuggestionsController(this);

        mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
        mVisibilityApplier = new DefaultImeVisibilityApplier(this);

        mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
                com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -3357,6 +3342,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        }
    }

    @GuardedBy("ImfLock.class")
    void setRequestImeTokenToWindow(IBinder windowToken, IBinder token) {
        mVisibilityStateComputer.setRequestImeTokenToWindow(windowToken, token);
    }

    @BinderThread
    @Override
    public void reportPerceptibleAsync(IBinder windowToken, boolean perceptible) {
@@ -3411,38 +3401,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        // Ensure binding the connection when IME is going to show.
        mBindingController.setCurrentMethodVisible();
        final IInputMethodInvoker curMethod = getCurMethodLocked();
        if (curMethod != null) {
            // create a placeholder token for IMS so that IMS cannot inject windows into client app.
            Binder showInputToken = new Binder();
            mShowRequestWindowMap.put(showInputToken, windowToken);
        ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
        if (curMethod != null) {
            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
            mCurStatsToken = null;
            final int showFlags = mVisibilityStateComputer.getImeShowFlags();
            if (DEBUG) {
                Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken
                        + ", " + showFlags + ", " + resultReceiver + ") for reason: "
                        + InputMethodDebug.softInputDisplayReasonToString(reason));
            }

            if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
                curMethod.updateEditorToolType(lastClickToolType);
            }
            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
            if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
                if (DEBUG_IME_VISIBILITY) {
                    EventLog.writeEvent(IMF_SHOW_IME, statsToken.getTag(),
                            Objects.toString(mCurFocusedWindow),
                            InputMethodDebug.softInputDisplayReasonToString(reason),
                            InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
                }
                onShowHideSoftInputRequested(true /* show */, windowToken, reason, statsToken);
            }
            mVisibilityApplier.performShowIme(windowToken, statsToken,
                    mVisibilityStateComputer.getImeShowFlags(), resultReceiver, reason);
            // TODO(b/246309664): make mInputShown tracked by the Ime visibility computer.
            mInputShown = true;
            return true;
        } else {
            ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME);
            mCurStatsToken = statsToken;
        }
@@ -3516,29 +3488,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub

        mVisibilityStateComputer.requestImeVisibility(windowToken, false);
        if (shouldHideSoftInput) {
            final Binder hideInputToken = new Binder();
            mHideRequestWindowMap.put(hideInputToken, windowToken);
            // The IME will report its visible state again after the following message finally
            // delivered to the IME process as an IPC.  Hence the inconsistency between
            // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
            // the final state.
            ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
            if (DEBUG) {
                Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken
                        + ", " + resultReceiver + ") for reason: "
                        + InputMethodDebug.softInputDisplayReasonToString(reason));
            }
            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
            if (curMethod.hideSoftInput(hideInputToken, statsToken, 0 /* flags */,
                    resultReceiver)) {
                if (DEBUG_IME_VISIBILITY) {
                    EventLog.writeEvent(IMF_HIDE_IME, statsToken.getTag(),
                            Objects.toString(mCurFocusedWindow),
                            InputMethodDebug.softInputDisplayReasonToString(reason),
                            InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
                }
                onShowHideSoftInputRequested(false /* show */, windowToken, reason, statsToken);
            }
            mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason);
        } else {
            ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
        }
@@ -4758,14 +4713,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                return;
            }
            final IBinder requestToken = mVisibilityStateComputer.getWindowTokenFrom(windowToken);
            if (!setVisible) {
                if (mCurClient != null) {
                    ImeTracker.get().onProgress(statsToken,
                            ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);

                    mWindowManagerInternal.hideIme(
                            mHideRequestWindowMap.get(windowToken),
                            mCurClient.mSelfReportedDisplayId, statsToken);
                    mWindowManagerInternal.hideIme(requestToken, mCurClient.mSelfReportedDisplayId,
                            statsToken);
                } else {
                    ImeTracker.get().onFailed(statsToken,
                            ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
@@ -4774,8 +4728,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                ImeTracker.get().onProgress(statsToken,
                        ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
                // Send to window manager to show IME after IME layout finishes.
                mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken),
                        statsToken);
                mWindowManagerInternal.showImePostLayout(requestToken, statsToken);
            }
        }
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
@@ -4820,7 +4773,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub

    /** Called right after {@link com.android.internal.inputmethod.IInputMethod#showSoftInput}. */
    @GuardedBy("ImfLock.class")
    private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
    void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
            @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken) {
        final WindowManagerInternal.ImeTargetInfo info =
                mWindowManagerInternal.onToggleImeRequested(