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

Commit c7106b66 authored by Felix Stern's avatar Felix Stern
Browse files

Remove DefaultImeVisibilityApplier

With the changes in [1] and [2], there is no actual need anymore to have
a separate DefaultImeVisibilityApplier class. Thus, moving the remaining
methods (show/hide screenshot, perform show/hide) to IMMS.

[1]: I8e3a74ee579f085cb582040fdba725e7a63d6b85
[2]: I1399ec6c9e3f5ed70f02ba2326edd0c73eb930b4

Bug: 433457669
Test: atest com.android.server.inputmethod
Flag: EXEMPT refactor
Change-Id: Id02bfb3f44d57200422b6eb1e5e678f1abf751c3
parent d3ede6ef
Loading
Loading
Loading
Loading
+0 −191
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 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 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;

    @NonNull
    private final ImeTargetVisibilityPolicy mImeTargetVisibilityPolicy;

    DefaultImeVisibilityApplier(InputMethodManagerService service) {
        mService = service;
        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 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);

+135 −14
Original line number Diff line number Diff line
@@ -47,7 +47,10 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER;
import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED;
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.IMMS_DEBUG;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
@@ -193,6 +196,7 @@ import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeS
import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.utils.PriorityDump;
import com.android.server.wm.ImeTargetVisibilityPolicy;
import com.android.server.wm.WindowManagerInternal;

import java.io.FileDescriptor;
@@ -435,11 +439,8 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
    @MultiUserUnawareField
    private final InputMethodMenuController mMenuController;
    private final InputMethodMenuControllerNew mMenuControllerNew;

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

    /**
     * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
@@ -1246,6 +1247,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
            mImePlatformCompatUtils = new ImePlatformCompatUtils();
            mInputMethodDeviceConfigs = new InputMethodDeviceConfigs();
            mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
            mImeTargetVisibilityPolicy = LocalServices.getService(ImeTargetVisibilityPolicy.class);

            mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);

@@ -1265,7 +1267,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
            mMenuController = new InputMethodMenuController(this);
            mMenuControllerNew = Flags.imeSwitcherRevamp()
                    ? new InputMethodMenuControllerNew() : null;
            mVisibilityApplier = new DefaultImeVisibilityApplier(this);

            mClientController = new ClientController(mPackageManagerInternal);
            mClientController.addClientControllerCallback(c -> onClientRemoved(c));
@@ -3382,7 +3383,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
            userData.mCurStatsToken = null;

            maybeReportToolType(userId);
            mVisibilityApplier.performShowIme(windowToken, statsToken, reason, userId);
            performShowIme(windowToken, statsToken, reason, userData);
            visibilityStateComputer.setInputShown(true);
            return true;
        } else {
@@ -3392,6 +3393,40 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
        return false;
    }

    /**
     * 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 userData       the data of the target user when performing show IME
     */
    @GuardedBy("ImfLock.class")
    void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
            @SoftInputShowHideReason int reason, UserData userData) {
        final var bindingController = userData.mBindingController;
        final IInputMethodInvoker curMethod = bindingController.getCurMethod();
        if (curMethod != null) {
            ProtoLog.v(IMMS_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.getTag(),
                            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
                onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
                        statsToken, userData.mUserId);
            }
        }
    }

    @GuardedBy("ImfLock.class")
    private void maybeReportToolType(@UserIdInt int userId) {
        // TODO(b/356638981): This needs to be compatible with visible background users.
@@ -3463,7 +3498,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
            // IMMS#mInputShown and the user's ImeWindowVis should be resolved spontaneously in
            // the final state.
            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
            mVisibilityApplier.performHideIme(windowToken, statsToken, reason, userId);
            performHideIme(windowToken, statsToken, reason, userData);
        } else {
            ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE);
        }
@@ -3476,6 +3511,44 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
        return shouldHideSoftInput;
    }

    /**
     * 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 userData       the data of the target user when performing show IME
     */
    @GuardedBy("ImfLock.class")
    void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
            @SoftInputShowHideReason int reason, UserData userData) {
        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(IMMS_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.getTag(),
                            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
                onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
                        statsToken, userData.mUserId);
            }
        }
    }

    /**
     * Checks whether the specified IME client has IME focus or not.
     *
@@ -4579,12 +4652,6 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
        setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
    }

    @GuardedBy("ImfLock.class")
    @VisibleForTesting
    DefaultImeVisibilityApplier getVisibilityApplierLocked() {
        return mVisibilityApplier;
    }

    @GuardedBy("ImfLock.class")
    void setEnabledSessionLocked(SessionState session, @NonNull UserData userData) {
        if (userData.mEnabledSession != session) {
@@ -4899,7 +4966,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
                final Boolean showScreenshot = visibilityStateComputer.shouldShowImeScreenshot(
                        userData.mImeBindingState.mFocusedWindow, interactive);
                if (showScreenshot != null) {
                    mVisibilityApplier.applyImeScreenshotVisibility(
                    applyImeScreenshotVisibility(
                            userData.mImeBindingState.mFocusedWindow, showScreenshot, userId);
                }
                // Eligible IME processes use new "setInteractive" protocol.
@@ -5389,6 +5456,60 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl.
        }
    }


    /**
     * 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 = 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.
     */
    @VisibleForTesting
    @GuardedBy("ImfLock.class")
    void showImeScreenshot(IBinder imeTarget, int displayId, @UserIdInt int userId) {
        if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) {
            onShowHideSoftInputRequested(false /* show */, imeTarget,
                    SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */,
                    userId);
        }
    }

    /**
     * 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.
     */
    @VisibleForTesting
    @GuardedBy("ImfLock.class")
    void removeImeScreenshot(IBinder imeTarget, int displayId, @UserIdInt int userId) {
        if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
            onShowHideSoftInputRequested(false /* show */, imeTarget,
                    SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */,
                    userId);
        }
    }

    private void publishLocalService() {
        LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal);
    }
+67 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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 com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;

import static java.util.Objects.requireNonNull;

import android.os.Binder;
import android.os.RemoteException;
import android.view.inputmethod.ImeTracker;

import androidx.test.ext.junit.runners.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidJUnit4.class)
public class InputMethodManagerServicePerformShowHideTest extends
        InputMethodManagerServiceTestBase {

    UserData mUserData;

    @Before
    public void setUp() throws RemoteException {
        super.setUp();
        synchronized (ImfLock.class) {
            mUserData = mInputMethodManagerService.getUserData(mUserId);
            mUserData.mCurClient = requireNonNull(
                    mInputMethodManagerService.getClientStateLocked(mMockInputMethodClient));
        }
    }

    @Test
    public void testPerformShowIme() throws Exception {
        synchronized (ImfLock.class) {
            mInputMethodManagerService.performShowIme(new Binder() /* showInputToken */,
                    ImeTracker.Token.empty(), SHOW_SOFT_INPUT, mUserData);
        }
        verifyShowSoftInput(true  /* showSoftInput */);
    }

    @Test
    public void testPerformHideIme() throws Exception {
        synchronized (ImfLock.class) {
            mInputMethodManagerService.performHideIme(new Binder() /* hideInputToken */,
                    ImeTracker.Token.empty(), HIDE_SOFT_INPUT, mUserData);
        }
        verifyHideSoftInput(true  /* hideSoftInput */);
    }
}
+17 −37

File changed and moved.

Preview size limit exceeded, changes collapsed.

Loading