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

Commit ce7ff9ce authored by Felix Stern's avatar Felix Stern Committed by Android (Google) Code Review
Browse files

Merge changes Id02bfb3f,I1399ec6c into main

* changes:
  Remove DefaultImeVisibilityApplier
  Remove VisibilityState flags and simplify DefaultImeVisibilityApplier
parents f35fb717 c7106b66
Loading
Loading
Loading
Loading
+0 −232
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 static com.android.server.inputmethod.ImeProtoLogGroup.IME_VISIBILITY_APPLIER_DEBUG;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;

import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.os.IBinder;
import android.util.EventLog;
import android.view.inputmethod.ImeTracker;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.protolog.ProtoLog;
import com.android.server.LocalServices;
import com.android.server.wm.ImeTargetVisibilityPolicy;
import com.android.server.wm.WindowManagerInternal;

import java.util.Objects;

/**
 * A stateless helper class for IME visibility operations like show/hide and update Z-ordering
 * relative to the IME targeted window.
 */
final class DefaultImeVisibilityApplier {

    static final String TAG = "DefaultImeVisibilityApplier";

    private InputMethodManagerService mService;

    private final WindowManagerInternal mWindowManagerInternal;

    @NonNull
    private final ImeTargetVisibilityPolicy mImeTargetVisibilityPolicy;

    DefaultImeVisibilityApplier(InputMethodManagerService service) {
        mService = service;
        mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
        mImeTargetVisibilityPolicy = LocalServices.getService(ImeTargetVisibilityPolicy.class);
    }

    /**
     * Performs showing IME on top of the given window.
     *
     * @param showInputToken a token that represents the requester to show IME
     * @param statsToken     the token tracking the current IME request
     * @param reason         the reason for requesting to show IME
     * @param userId         the target user when performing show IME
     */
    @GuardedBy("ImfLock.class")
    void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
            @SoftInputShowHideReason int reason, @UserIdInt int userId) {
        final var userData = mService.getUserData(userId);
        final var bindingController = userData.mBindingController;
        final IInputMethodInvoker curMethod = bindingController.getCurMethod();
        if (curMethod != null) {
            ProtoLog.v(IME_VISIBILITY_APPLIER_DEBUG,
                    "Calling %s.showSoftInput(%s) for reason: %s", curMethod,
                    showInputToken, InputMethodDebug.softInputDisplayReasonToString(reason));
            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
            if (curMethod.showSoftInput(statsToken)) {
                if (DEBUG_IME_VISIBILITY) {
                    EventLog.writeEvent(IMF_SHOW_IME,
                            statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
                            Objects.toString(userData.mImeBindingState.mFocusedWindow),
                            InputMethodDebug.softInputDisplayReasonToString(reason),
                            InputMethodDebug.softInputModeToString(
                                    userData.mImeBindingState.mFocusedWindowSoftInputMode));
                }
                // TODO(b/419459695): Check if we still need to pass the input token
                mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
                        statsToken, userId);
            }
        }
    }

    /**
     * Performs hiding IME to the given window
     *
     * @param hideInputToken a token that represents the requester to hide IME
     * @param statsToken     the token tracking the current IME request
     * @param reason         the reason for requesting to hide IME
     * @param userId         the target user when performing hide IME
     */
    @GuardedBy("ImfLock.class")
    void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
            @SoftInputShowHideReason int reason, @UserIdInt int userId) {
        final var userData = mService.getUserData(userId);
        final var bindingController = userData.mBindingController;
        final IInputMethodInvoker curMethod = bindingController.getCurMethod();
        if (curMethod != null) {
            // 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.
            ProtoLog.v(IME_VISIBILITY_APPLIER_DEBUG,
                    "Calling %s.hideSoftInput(%s) for reason: %s", curMethod, hideInputToken,
                    InputMethodDebug.softInputDisplayReasonToString(reason));
            // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
            if (curMethod.hideSoftInput(statsToken)) {
                if (DEBUG_IME_VISIBILITY) {
                    EventLog.writeEvent(IMF_HIDE_IME,
                            statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
                            Objects.toString(userData.mImeBindingState.mFocusedWindow),
                            InputMethodDebug.softInputDisplayReasonToString(reason),
                            InputMethodDebug.softInputModeToString(
                                    userData.mImeBindingState.mFocusedWindowSoftInputMode));
                }
                // TODO(b/419459695): Check if we still need to pass the input token
                mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
                        statsToken, userId);
            }
        }
    }

    /**
     * Applies the IME visibility from {@link android.inputmethodservice.InputMethodService} with
     * according to the given visibility state.
     *
     * @param statsToken the token tracking the current IME request
     * @param state      the new IME visibility state for the applier to handle
     * @param userId     the target user when applying the IME visibility state
     */
    @GuardedBy("ImfLock.class")
    void applyImeVisibility(@NonNull ImeTracker.Token statsToken,
            @ImeVisibilityStateComputer.VisibilityState int state,
            @UserIdInt int userId) {
        final var userData = mService.getUserData(userId);
        switch (state) {
            case STATE_SHOW_IME:
            case STATE_HIDE_IME:
                // no-op
                break;
            case STATE_HIDE_IME_EXPLICIT:
            case STATE_HIDE_IME_NOT_ALWAYS:
                mService.setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
                break;
            case STATE_SHOW_IME_IMPLICIT:
                // This can be triggered by IMMS#startInputOrWindowGainedFocus. We need to
                // set the requestedVisibleTypes in InsetsController first, before applying it.
                mService.setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
                break;
            default:
                throw new IllegalArgumentException("Invalid IME visibility state: " + state);
        }
    }

    /**
     * Applies the IME screenshot visibility on the given IME target window.
     *
     * @param imeTarget the token of the IME target window.
     * @param show      whether to show or remove the screenshot.
     * @param userId    the ID of the user to apply the screenshot visibility for.
     */
    @GuardedBy("ImfLock.class")
    void applyImeScreenshotVisibility(IBinder imeTarget, boolean show, @UserIdInt int userId) {
        final var userData = mService.getUserData(userId);
        final var bindingController = userData.mBindingController;
        final int displayId = bindingController.getDisplayIdToShowIme();
        if (show) {
            showImeScreenshot(imeTarget, displayId, userId);
        } else {
            removeImeScreenshot(imeTarget, displayId, userId);
        }
    }

    /**
     * Shows the IME screenshot and attaches it to the given IME target window.
     *
     * @param imeTarget the token of the IME target window.
     * @param displayId the ID of the display to show the screenshot on.
     * @param userId    the ID of the user to show the screenshot for.
     * @return {@code true} if successful, {@code false} otherwise.
     */
    @VisibleForTesting
    @GuardedBy("ImfLock.class")
    boolean showImeScreenshot(IBinder imeTarget, int displayId, @UserIdInt int userId) {
        if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) {
            mService.onShowHideSoftInputRequested(false /* show */, imeTarget,
                    SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */,
                    userId);
            return true;
        }
        return false;
    }

    /**
     * Removes the IME screenshot from the given display.
     *
     * @param imeTarget the token of the IME target window.
     * @param displayId the ID of the display to remove the screenshot from.
     * @param userId    the ID of the user to remove the screenshot for.
     * @return {@code true} if successful, {@code false} otherwise.
     */
    @VisibleForTesting
    @GuardedBy("ImfLock.class")
    boolean removeImeScreenshot(IBinder imeTarget, int displayId, @UserIdInt int userId) {
        if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
            mService.onShowHideSoftInputRequested(false /* show */, imeTarget,
                    SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */,
                    userId);
            return true;
        }
        return false;
    }
}
+0 −2
Original line number Diff line number Diff line
@@ -24,8 +24,6 @@ public enum ImeProtoLogGroup implements IProtoLogGroup {
    // TODO(b/393561240): add info/warn/error log level and replace in IMMS
    IMMS_DEBUG(Consts.ENABLE_DEBUG, false, false,
            InputMethodManagerService.TAG),
    IME_VISIBILITY_APPLIER_DEBUG(Consts.ENABLE_DEBUG, false, false,
            DefaultImeVisibilityApplier.TAG),
    IME_VIS_STATE_COMPUTER_DEBUG(Consts.ENABLE_DEBUG, false, false,
            ImeVisibilityStateComputer.TAG);

+15 −51
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import static com.android.server.inputmethod.InputMethodManagerService.computeIm

import android.accessibilityservice.AccessibilityService;
import android.annotation.AnyThread;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -117,39 +116,6 @@ public final class ImeVisibilityStateComputer {
    @Nullable
    private IBinder mLastImeTargetWindow;

    /** Represent the invalid IME visibility state */
    public static final int STATE_INVALID = -1;

    /** State to handle hiding the IME window requested by the app. */
    public static final int STATE_HIDE_IME = 0;

    /** State to handle showing the IME window requested by the app. */
    public static final int STATE_SHOW_IME = 1;

    /** State to handle showing the IME window with making the overlay window above it.  */
    public static final int STATE_SHOW_IME_ABOVE_OVERLAY = 2;

    /** State to handle showing the IME window with making the overlay window behind it.  */
    public static final int STATE_SHOW_IME_BEHIND_OVERLAY = 3;

    public static final int STATE_HIDE_IME_EXPLICIT = 4;

    public static final int STATE_HIDE_IME_NOT_ALWAYS = 5;

    public static final int STATE_SHOW_IME_IMPLICIT = 6;

    @IntDef({
            STATE_INVALID,
            STATE_HIDE_IME,
            STATE_SHOW_IME,
            STATE_SHOW_IME_ABOVE_OVERLAY,
            STATE_SHOW_IME_BEHIND_OVERLAY,
            STATE_HIDE_IME_EXPLICIT,
            STATE_HIDE_IME_NOT_ALWAYS,
            STATE_SHOW_IME_IMPLICIT,
    })
    @interface VisibilityState {}

    /**
     * The policy to configure the IME visibility.
     */
@@ -215,8 +181,9 @@ public final class ImeVisibilityStateComputer {
            final int reason = SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
            final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
                    ImeTracker.ORIGIN_SERVER, reason, false /* fromUser */);
            mService.onApplyImeVisibilityFromComputerLocked(statsToken,
                    new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, reason), mUserId);
            final var userData = mService.getUserData(mUserId);
            mService.setImeVisibilityOnFocusedWindowClient(false /* visible */, userData,
                    statsToken);
        }
        mCurVisibleImeInputTarget = null;
    }
@@ -254,9 +221,6 @@ public final class ImeVisibilityStateComputer {
     *
     * @param windowToken The window which requests to show/hide IME.
     * @param showIme {@code true} means to show IME, {@code false} otherwise.
     *                            Note that in the computer will take this option to compute the
     *                            visibility state, it could be {@link #STATE_SHOW_IME} or
     *                            {@link #STATE_HIDE_IME}.
     */
    @GuardedBy("ImfLock.class")
    void requestImeVisibility(IBinder windowToken, boolean showIme) {
@@ -310,16 +274,16 @@ public final class ImeVisibilityStateComputer {
    }

    static class ImeVisibilityResult {
        private final @VisibilityState int mState;
        private final boolean mVisible;
        private final @SoftInputShowHideReason int mReason;

        ImeVisibilityResult(@VisibilityState int state, @SoftInputShowHideReason int reason) {
            mState = state;
        ImeVisibilityResult(boolean visible, @SoftInputShowHideReason int reason) {
            mVisible = visible;
            mReason = reason;
        }

        @VisibilityState int getState() {
            return mState;
        public boolean isVisible() {
            return mVisible;
        }

        @SoftInputShowHideReason int getReason() {
@@ -373,7 +337,7 @@ public final class ImeVisibilityStateComputer {
            // focused with an editor.
            state.setRequestedImeVisible(true);
            setWindowStateInner(getWindowTokenFrom(state), state);
            return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
            return new ImeVisibilityResult(true /* visible */,
                    SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY);
        }

@@ -386,7 +350,7 @@ public final class ImeVisibilityStateComputer {
                        // soft input window if it is shown.
                        ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG,
                                "Unspecified window will hide input");
                        return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS,
                        return new ImeVisibilityResult(false /* visible */,
                                SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW);
                    }
                } else if (state.hasEditorFocused() && doAutoShow && isForwardNavigation) {
@@ -398,7 +362,7 @@ public final class ImeVisibilityStateComputer {
                    // by the IME) or if running on a large screen where there
                    // is more room for the target window + IME.
                    ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Unspecified window will show input");
                    return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
                    return new ImeVisibilityResult(true /* visible */,
                            SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
                }
                break;
@@ -422,7 +386,7 @@ public final class ImeVisibilityStateComputer {
                    if (allowVisible) {
                        ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG,
                                "Window asks to show input going forward");
                        return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
                        return new ImeVisibilityResult(true /* visible */,
                                SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV);
                    } else {
                        Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
@@ -435,7 +399,7 @@ public final class ImeVisibilityStateComputer {
                ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Window asks to always show input");
                if (allowVisible) {
                    if (state.hasImeFocusChanged()) {
                        return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT,
                        return new ImeVisibilityResult(true /* visible */,
                                SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE);
                    }
                } else {
@@ -456,7 +420,7 @@ public final class ImeVisibilityStateComputer {
            if (state.isStartInputByWindowGainFocus()) {
                ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG,
                        "Same window without editor will hide input");
                return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
                return new ImeVisibilityResult(false /* visible */,
                        SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
            }
        }
@@ -472,7 +436,7 @@ public final class ImeVisibilityStateComputer {
            // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
            ProtoLog.v(IME_VIS_STATE_COMPUTER_DEBUG, "Window without editor will hide input");
            state.setRequestedImeVisible(false);
            return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT,
            return new ImeVisibilityResult(false /* visible */,
                    SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
        }
        return null;
+136 −21

File changed.

Preview size limit exceeded, changes collapsed.

+7 −13
Original line number Diff line number Diff line
@@ -26,15 +26,13 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
import static com.android.server.inputmethod.InputMethodManagerService.FALLBACK_DISPLAY_ID;
import static com.android.server.inputmethod.InputMethodManagerService.ImeDisplayValidator;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;

import android.annotation.UserIdInt;
@@ -263,18 +261,14 @@ public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTes
            mComputer.setHasVisibleImeLayeringOverlay(true /* visibleAndNotRemoved */);
            mComputer.onImeInputTargetVisibilityChanged(testImeInputTarget,
                    false /* visibleAndNotRemoved */);
            final ArgumentCaptor<ImeVisibilityResult> resultCaptor = ArgumentCaptor.forClass(
                    ImeVisibilityResult.class);
            final ArgumentCaptor<Integer> userIdCaptor = ArgumentCaptor.forClass(Integer.class);
            verify(mInputMethodManagerService).onApplyImeVisibilityFromComputerLocked(
                    notNull() /* statsToken */, resultCaptor.capture(), userIdCaptor.capture());
            final ImeVisibilityResult result = resultCaptor.getValue();
            final int userId = userIdCaptor.getValue();
            final ArgumentCaptor<UserData> userDataCaptor = ArgumentCaptor.forClass(UserData.class);
            verify(mInputMethodManagerService).setImeVisibilityOnFocusedWindowClient(
                    eq(false) /* visible */, userDataCaptor.capture(), notNull() /* statsToken */);
            final UserData userData = userDataCaptor.getValue();

            // Verify the computer will callback hiding IME state to IMMS.
            assertThat(result.getState()).isEqualTo(STATE_HIDE_IME_EXPLICIT);
            assertThat(result.getReason()).isEqualTo(HIDE_WHEN_INPUT_TARGET_INVISIBLE);
            assertThat(userId).isEqualTo(mUserId);
            assertThat(userData).isNotNull();
            assertThat(userData.mUserId).isEqualTo(mUserId);
        }
    }

Loading