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

Commit 2b816bda authored by Antonio Kantek's avatar Antonio Kantek Committed by Android (Google) Code Review
Browse files

Merge "Introduce IMMS ClientController component" into main

parents 3dbafcf0 ba02b466
Loading
Loading
Loading
Loading
+162 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2023 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.NonNull;
import android.content.pm.PackageManagerInternal;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.SparseArray;
import android.view.inputmethod.InputBinding;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IRemoteInputConnection;

/**
 * Store and manage {@link InputMethodManagerService} clients. This class was designed to be a
 * singleton in {@link InputMethodManagerService} since it stores information about all clients,
 * still the current client will be defined per display.
 *
 * <p>
 * As part of the re-architecture plan (described in go/imms-rearchitecture-plan), the following
 * fields and methods will be moved out from IMMS and placed here:
 * <ul>
 * <li>mCurClient (ClientState)</li>
 * <li>mClients (ArrayMap of ClientState indexed by IBinder)</li>
 * <li>mLastSwitchUserId</li>
 * </ul>
 * <p>
 * Nested Classes (to move from IMMS):
 * <ul>
 * <li>ClientDeathRecipient</li>
 * <li>ClientState<</li>
 * </ul>
 * <p>
 * Methods to rewrite and/or extract from IMMS and move here:
 * <ul>
 * <li>addClient</li>
 * <li>removeClient</li>
 * <li>verifyClientAndPackageMatch</li>
 * <li>setImeTraceEnabledForAllClients (make it reactive)</li>
 * <li>unbindCurrentClient</li>
 * </ul>
 */
// TODO(b/314150112): Update the Javadoc above, by removing the re-architecture steps, once this
//  class is finalized
final class ClientController {

    // TODO(b/314150112): Make this field private when breaking the cycle with IMMS.
    @GuardedBy("ImfLock.class")
    final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();

    private final PackageManagerInternal mPackageManagerInternal;

    ClientController(PackageManagerInternal packageManagerInternal) {
        mPackageManagerInternal = packageManagerInternal;
    }

    @GuardedBy("ImfLock.class")
    void addClient(IInputMethodClientInvoker clientInvoker,
            IRemoteInputConnection inputConnection,
            int selfReportedDisplayId, IBinder.DeathRecipient deathRecipient, int callerUid,
            int callerPid) {
        // TODO: Optimize this linear search.
        final int numClients = mClients.size();
        for (int i = 0; i < numClients; ++i) {
            final ClientState state = mClients.valueAt(i);
            if (state.mUid == callerUid && state.mPid == callerPid
                    && state.mSelfReportedDisplayId == selfReportedDisplayId) {
                throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
                        + "/displayId=" + selfReportedDisplayId + " is already registered");
            }
        }
        try {
            clientInvoker.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
        } catch (RemoteException e) {
            throw new IllegalStateException(e);
        }
        // We cannot fully avoid race conditions where the client UID already lost the access to
        // the given self-reported display ID, even if the client is not maliciously reporting
        // a fake display ID. Unconditionally returning SecurityException just because the
        // client doesn't pass display ID verification can cause many test failures hence not an
        // option right now.  At the same time
        //    context.getSystemService(InputMethodManager.class)
        // is expected to return a valid non-null instance at any time if we do not choose to
        // have the client crash.  Thus we do not verify the display ID at all here.  Instead we
        // later check the display ID every time the client needs to interact with the specified
        // display.
        mClients.put(clientInvoker.asBinder(), new ClientState(clientInvoker, inputConnection,
                callerUid, callerPid, selfReportedDisplayId, deathRecipient));
    }

    @GuardedBy("ImfLock.class")
    boolean verifyClientAndPackageMatch(
            @NonNull IInputMethodClient client, @NonNull String packageName) {
        ClientState cs = mClients.get(client.asBinder());
        if (cs == null) {
            throw new IllegalArgumentException("unknown client " + client.asBinder());
        }
        return InputMethodUtils.checkIfPackageBelongsToUid(
                mPackageManagerInternal, cs.mUid, packageName);
    }

    static final class ClientState {
        final IInputMethodClientInvoker mClient;
        final IRemoteInputConnection mFallbackInputConnection;
        final int mUid;
        final int mPid;
        final int mSelfReportedDisplayId;
        final InputBinding mBinding;
        final IBinder.DeathRecipient mClientDeathRecipient;

        @GuardedBy("ImfLock.class")
        boolean mSessionRequested;

        @GuardedBy("ImfLock.class")
        boolean mSessionRequestedForAccessibility;

        @GuardedBy("ImfLock.class")
        InputMethodManagerService.SessionState mCurSession;

        @GuardedBy("ImfLock.class")
        SparseArray<InputMethodManagerService.AccessibilitySessionState> mAccessibilitySessions =
                new SparseArray<>();

        @Override
        public String toString() {
            return "ClientState{" + Integer.toHexString(
                    System.identityHashCode(this)) + " mUid=" + mUid
                    + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
        }

        ClientState(IInputMethodClientInvoker client,
                IRemoteInputConnection fallbackInputConnection,
                int uid, int pid, int selfReportedDisplayId,
                IBinder.DeathRecipient clientDeathRecipient) {
            mClient = client;
            mFallbackInputConnection = fallbackInputConnection;
            mUid = uid;
            mPid = pid;
            mSelfReportedDisplayId = selfReportedDisplayId;
            mBinding = new InputBinding(null /*conn*/, mFallbackInputConnection.asBinder(), mUid,
                    mPid);
            mClientDeathRecipient = clientDeathRecipient;
        }
    }
}
+55 −123
Original line number Original line Diff line number Diff line
@@ -48,6 +48,7 @@ import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;


import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
@@ -127,7 +128,6 @@ import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.Flags;
import android.view.inputmethod.Flags;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto;
@@ -273,6 +273,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
    @NonNull
    @NonNull
    private final String[] mNonPreemptibleInputMethods;
    private final String[] mNonPreemptibleInputMethods;


    // TODO(b/314150112): Move this to ClientController.
    @UserIdInt
    @UserIdInt
    private int mLastSwitchUserId;
    private int mLastSwitchUserId;


@@ -393,7 +394,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
    /**
    /**
     * Record session state for an accessibility service.
     * Record session state for an accessibility service.
     */
     */
    private static class AccessibilitySessionState {
    static class AccessibilitySessionState {
        final ClientState mClient;
        final ClientState mClient;
        // Id of the accessibility service.
        // Id of the accessibility service.
        final int mId;
        final int mId;
@@ -417,58 +418,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        }
        }
    }
    }


    private static final class ClientDeathRecipient implements IBinder.DeathRecipient {
    /**
        private final InputMethodManagerService mImms;
     * Manages the IME clients.
        private final IInputMethodClient mClient;
     */

    private final ClientController mClientController;
        ClientDeathRecipient(InputMethodManagerService imms, IInputMethodClient client) {
            mImms = imms;
            mClient = client;
        }

        @Override
        public void binderDied() {
            mImms.removeClient(mClient);
        }
    }

    static final class ClientState {
        final IInputMethodClientInvoker mClient;
        final IRemoteInputConnection mFallbackInputConnection;
        final int mUid;
        final int mPid;
        final int mSelfReportedDisplayId;
        final InputBinding mBinding;
        final ClientDeathRecipient mClientDeathRecipient;

        boolean mSessionRequested;
        boolean mSessionRequestedForAccessibility;
        SessionState mCurSession;
        SparseArray<AccessibilitySessionState> mAccessibilitySessions = new SparseArray<>();

        @Override
        public String toString() {
            return "ClientState{" + Integer.toHexString(
                    System.identityHashCode(this)) + " mUid=" + mUid
                    + " mPid=" + mPid + " mSelfReportedDisplayId=" + mSelfReportedDisplayId + "}";
        }

        ClientState(IInputMethodClientInvoker client,
                IRemoteInputConnection fallbackInputConnection,
                int uid, int pid, int selfReportedDisplayId,
                ClientDeathRecipient clientDeathRecipient) {
            mClient = client;
            mFallbackInputConnection = fallbackInputConnection;
            mUid = uid;
            mPid = pid;
            mSelfReportedDisplayId = selfReportedDisplayId;
            mBinding = new InputBinding(null, mFallbackInputConnection.asBinder(), mUid, mPid);
            mClientDeathRecipient = clientDeathRecipient;
        }
    }

    @GuardedBy("ImfLock.class")
    final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>();


    /**
    /**
     * Set once the system is ready to run third party code.
     * Set once the system is ready to run third party code.
@@ -526,6 +479,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
    /**
    /**
     * The client that is currently bound to an input method.
     * The client that is currently bound to an input method.
     */
     */
    // TODO(b/314150112): Move this to ClientController.
    @Nullable
    @Nullable
    private ClientState mCurClient;
    private ClientState mCurClient;


@@ -866,8 +820,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            @Nullable
            @Nullable
            final String mImeSurfaceParentName;
            final String mImeSurfaceParentName;


            Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName,
            Entry(ClientState client, EditorInfo editorInfo,
                    @SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason,
                    String focusedWindowName, @SoftInputModeFlags int softInputMode,
                    @SoftInputShowHideReason int reason,
                    boolean inFullscreenMode, String requestWindowName,
                    boolean inFullscreenMode, String requestWindowName,
                    @Nullable String imeControlTargetName, @Nullable String imeTargetName,
                    @Nullable String imeControlTargetName, @Nullable String imeTargetName,
                    @Nullable String imeSurfaceParentName) {
                    @Nullable String imeSurfaceParentName) {
@@ -1722,6 +1677,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub


        mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
        mVisibilityStateComputer = new ImeVisibilityStateComputer(this);
        mVisibilityApplier = new DefaultImeVisibilityApplier(this);
        mVisibilityApplier = new DefaultImeVisibilityApplier(this);
        mClientController = new ClientController(mPackageManagerInternal);


        mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
        mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
                com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
                com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -1875,7 +1831,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        mLastSwitchUserId = newUserId;
        mLastSwitchUserId = newUserId;


        if (mIsInteractive && clientToBeReset != null) {
        if (mIsInteractive && clientToBeReset != null) {
            final ClientState cs = mClients.get(clientToBeReset.asBinder());
            final ClientState cs =
                    mClientController.mClients.get(clientToBeReset.asBinder());
            if (cs == null) {
            if (cs == null) {
                // The client is already gone.
                // The client is already gone.
                return;
                return;
@@ -2212,43 +2169,22 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        // actually running.
        // actually running.
        final int callerUid = Binder.getCallingUid();
        final int callerUid = Binder.getCallingUid();
        final int callerPid = Binder.getCallingPid();
        final int callerPid = Binder.getCallingPid();
        synchronized (ImfLock.class) {

            // TODO: Optimize this linear search.
        // TODO(b/314150112): Move the death recipient logic to ClientController when moving
            final int numClients = mClients.size();
        //     removeClient method.
            for (int i = 0; i < numClients; ++i) {
        final IBinder.DeathRecipient deathRecipient = () -> removeClient(client);
                final ClientState state = mClients.valueAt(i);
                if (state.mUid == callerUid && state.mPid == callerPid
                        && state.mSelfReportedDisplayId == selfReportedDisplayId) {
                    throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid
                            + "/displayId=" + selfReportedDisplayId + " is already registered.");
                }
            }
            final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client);
            try {
                client.asBinder().linkToDeath(deathRecipient, 0 /* flags */);
            } catch (RemoteException e) {
                throw new IllegalStateException(e);
            }
            // We cannot fully avoid race conditions where the client UID already lost the access to
            // the given self-reported display ID, even if the client is not maliciously reporting
            // a fake display ID. Unconditionally returning SecurityException just because the
            // client doesn't pass display ID verification can cause many test failures hence not an
            // option right now.  At the same time
            //    context.getSystemService(InputMethodManager.class)
            // is expected to return a valid non-null instance at any time if we do not choose to
            // have the client crash.  Thus we do not verify the display ID at all here.  Instead we
            // later check the display ID every time the client needs to interact with the specified
            // display.
        final IInputMethodClientInvoker clientInvoker =
        final IInputMethodClientInvoker clientInvoker =
                IInputMethodClientInvoker.create(client, mHandler);
                IInputMethodClientInvoker.create(client, mHandler);
            mClients.put(client.asBinder(), new ClientState(clientInvoker, inputConnection,
        synchronized (ImfLock.class) {
                    callerUid, callerPid, selfReportedDisplayId, deathRecipient));
            mClientController.addClient(clientInvoker, inputConnection, selfReportedDisplayId,
                    deathRecipient, callerUid, callerPid);
        }
        }
    }
    }


    // TODO(b/314150112): Move this to ClientController.
    void removeClient(IInputMethodClient client) {
    void removeClient(IInputMethodClient client) {
        synchronized (ImfLock.class) {
        synchronized (ImfLock.class) {
            ClientState cs = mClients.remove(client.asBinder());
            ClientState cs = mClientController.mClients.remove(client.asBinder());
            if (cs != null) {
            if (cs != null) {
                client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
                client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */);
                clearClientSessionLocked(cs);
                clearClientSessionLocked(cs);
@@ -2278,6 +2214,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        }
        }
    }
    }


    // TODO(b/314150112): Move this to ClientController.
    @GuardedBy("ImfLock.class")
    @GuardedBy("ImfLock.class")
    void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
    void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) {
        if (mCurClient != null) {
        if (mCurClient != null) {
@@ -2330,7 +2267,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        }
        }
    }
    }


    /** {@code true} when a {@link ClientState} has attached from starting the input connection. */
    /**
     * {@code true} when a {@link ClientState} has attached from starting the
     * input connection.
     */
    @GuardedBy("ImfLock.class")
    @GuardedBy("ImfLock.class")
    boolean hasAttachedClient() {
    boolean hasAttachedClient() {
        return mCurClient != null;
        return mCurClient != null;
@@ -2974,10 +2914,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
    @GuardedBy("ImfLock.class")
    @GuardedBy("ImfLock.class")
    void clearClientSessionsLocked() {
    void clearClientSessionsLocked() {
        if (getCurMethodLocked() != null) {
        if (getCurMethodLocked() != null) {
            final int numClients = mClients.size();
            final int numClients = mClientController.mClients.size();
            for (int i = 0; i < numClients; ++i) {
            for (int i = 0; i < numClients; ++i) {
                clearClientSessionLocked(mClients.valueAt(i));
                clearClientSessionLocked(mClientController.mClients.valueAt(i));
                clearClientSessionForAccessibilityLocked(mClients.valueAt(i));
                clearClientSessionForAccessibilityLocked(mClientController.mClients.valueAt(i));
            }
            }


            finishSessionLocked(mEnabledSession);
            finishSessionLocked(mEnabledSession);
@@ -3512,10 +3452,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                    + " pref is disabled for user: " + userId);
                    + " pref is disabled for user: " + userId);
            return;
            return;
        }
        }
        if (!verifyClientAndPackageMatch(client, delegatorPackageName)) {
        synchronized (ImfLock.class) {
            if (!mClientController.verifyClientAndPackageMatch(client,
                    delegatorPackageName)) {
                Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
                Slog.w(TAG, "prepareStylusHandwritingDelegation() fail");
                throw new IllegalArgumentException("Delegator doesn't match Uid");
                throw new IllegalArgumentException("Delegator doesn't match Uid");
            }
            }
        }
        schedulePrepareStylusHandwritingDelegation(
        schedulePrepareStylusHandwritingDelegation(
                userId, delegatePackageName, delegatorPackageName);
                userId, delegatePackageName, delegatorPackageName);
    }
    }
@@ -3540,30 +3483,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        return true;
        return true;
    }
    }


    private boolean verifyClientAndPackageMatch(
            @NonNull IInputMethodClient client, @NonNull String packageName) {
        ClientState cs;
        synchronized (ImfLock.class) {
            cs = mClients.get(client.asBinder());
        }
        if (cs == null) {
            throw new IllegalArgumentException("unknown client " + client.asBinder());
        }
        return InputMethodUtils.checkIfPackageBelongsToUid(
                mPackageManagerInternal, cs.mUid, packageName);
    }

    private boolean verifyDelegator(
    private boolean verifyDelegator(
            @NonNull IInputMethodClient client,
            @NonNull IInputMethodClient client,
            @NonNull String delegatePackageName,
            @NonNull String delegatePackageName,
            @NonNull String delegatorPackageName,
            @NonNull String delegatorPackageName,
            @InputMethodManager.HandwritingDelegateFlags int flags) {
            @InputMethodManager.HandwritingDelegateFlags int flags) {
        if (!verifyClientAndPackageMatch(client, delegatePackageName)) {
        synchronized (ImfLock.class) {
            if (!mClientController.verifyClientAndPackageMatch(client, delegatePackageName)) {
                Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
                Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring"
                        + " startStylusHandwriting");
                        + " startStylusHandwriting");
                return false;
                return false;
            }
            }
        synchronized (ImfLock.class) {
            boolean homeDelegatorAllowed =
            boolean homeDelegatorAllowed =
                    (flags & InputMethodManager.HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED)
                    (flags & InputMethodManager.HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED)
                            != 0;
                            != 0;
@@ -3826,7 +3756,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            return InputBindResult.INVALID_USER;
            return InputBindResult.INVALID_USER;
        }
        }


        final ClientState cs = mClients.get(client.asBinder());
        final ClientState cs = mClientController.mClients.get(client.asBinder());
        if (cs == null) {
        if (cs == null) {
            throw new IllegalArgumentException("unknown client " + client.asBinder());
            throw new IllegalArgumentException("unknown client " + client.asBinder());
        }
        }
@@ -4000,7 +3930,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            // We need to check if this is the current client with
            // We need to check if this is the current client with
            // focus in the window manager, to allow this call to
            // focus in the window manager, to allow this call to
            // be made before input is started in it.
            // be made before input is started in it.
            final ClientState cs = mClients.get(client.asBinder());
            final ClientState cs =
                    mClientController.mClients.get(client.asBinder());
            if (cs == null) {
            if (cs == null) {
                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN);
                throw new IllegalArgumentException("unknown client " + client.asBinder());
                throw new IllegalArgumentException("unknown client " + client.asBinder());
@@ -4624,7 +4555,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        ImeTracing.getInstance().startTrace(null /* printwriter */);
        ImeTracing.getInstance().startTrace(null /* printwriter */);
        ArrayMap<IBinder, ClientState> clients;
        ArrayMap<IBinder, ClientState> clients;
        synchronized (ImfLock.class) {
        synchronized (ImfLock.class) {
            clients = new ArrayMap<>(mClients);
            clients = new ArrayMap<>(mClientController.mClients);
        }
        }
        for (ClientState state : clients.values()) {
        for (ClientState state : clients.values()) {
            if (state != null) {
            if (state != null) {
@@ -4642,7 +4573,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        ImeTracing.getInstance().stopTrace(null /* printwriter */);
        ImeTracing.getInstance().stopTrace(null /* printwriter */);
        ArrayMap<IBinder, ClientState> clients;
        ArrayMap<IBinder, ClientState> clients;
        synchronized (ImfLock.class) {
        synchronized (ImfLock.class) {
            clients = new ArrayMap<>(mClients);
            clients = new ArrayMap<>(mClientController.mClients);
        }
        }
        for (ClientState state : clients.values()) {
        for (ClientState state : clients.values()) {
            if (state != null) {
            if (state != null) {
@@ -5887,10 +5818,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                // We only have sessions when we bound to an input method. Remove this session
                // We only have sessions when we bound to an input method. Remove this session
                // from all clients.
                // from all clients.
                if (getCurMethodLocked() != null) {
                if (getCurMethodLocked() != null) {
                    final int numClients = mClients.size();
                    final int numClients = mClientController.mClients.size();
                    for (int i = 0; i < numClients; ++i) {
                    for (int i = 0; i < numClients; ++i) {
                        clearClientSessionForAccessibilityLocked(mClients.valueAt(i),
                        clearClientSessionForAccessibilityLocked(
                                accessibilityConnectionId);
                                mClientController.mClients.valueAt(i), accessibilityConnectionId);
                    }
                    }
                    AccessibilitySessionState session = mEnabledAccessibilitySessions.get(
                    AccessibilitySessionState session = mEnabledAccessibilitySessions.get(
                            accessibilityConnectionId);
                            accessibilityConnectionId);
@@ -6075,9 +6006,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                info.dump(p, "    ");
                info.dump(p, "    ");
            }
            }
            p.println("  ClientStates:");
            p.println("  ClientStates:");
            final int numClients = mClients.size();
            // TODO(b/314150112): move client related dump info to ClientController#dump
            final int numClients = mClientController.mClients.size();
            for (int i = 0; i < numClients; ++i) {
            for (int i = 0; i < numClients; ++i) {
                final ClientState ci = mClients.valueAt(i);
                final ClientState ci = mClientController.mClients.valueAt(i);
                p.println("  " + ci + ":");
                p.println("  " + ci + ":");
                p.println("    client=" + ci.mClient);
                p.println("    client=" + ci.mClient);
                p.println("    fallbackInputConnection=" + ci.mFallbackInputConnection);
                p.println("    fallbackInputConnection=" + ci.mFallbackInputConnection);
@@ -6696,7 +6628,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
        boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled();
        ArrayMap<IBinder, ClientState> clients;
        ArrayMap<IBinder, ClientState> clients;
        synchronized (ImfLock.class) {
        synchronized (ImfLock.class) {
            clients = new ArrayMap<>(mClients);
            clients = new ArrayMap<>(mClientController.mClients);
        }
        }
        for (ClientState state : clients.values()) {
        for (ClientState state : clients.values()) {
            if (state != null) {
            if (state != null) {
+24 −0
Original line number Original line Diff line number Diff line
@@ -40,6 +40,7 @@ android_test {
        "frameworks-base-testutils",
        "frameworks-base-testutils",
        "mockito-target-extended-minus-junit4",
        "mockito-target-extended-minus-junit4",
        "platform-test-annotations",
        "platform-test-annotations",
        "ravenwood-junit",
        "services.core",
        "services.core",
        "service-permission.stubs.system_server",
        "service-permission.stubs.system_server",
        "servicestests-core-utils",
        "servicestests-core-utils",
@@ -66,6 +67,28 @@ android_test {
    },
    },
}
}


android_ravenwood_test {
    name: "FrameworksInputMethodSystemServerTests_host",
    static_libs: [
        "androidx.annotation_annotation",
        "androidx.test.rules",
        "framework",
        "mockito_ravenwood",
        "ravenwood-runtime",
        "ravenwood-utils",
        "services",
    ],
    libs: [
        "android.test.base",
        "android.test.runner",
    ],
    srcs: [
        "src/com/android/server/inputmethod/**/ClientControllerTest.java",
    ],
    sdk_version: "test_current",
    auto_gen_config: true,
}

android_test {
android_test {
    name: "FrameworksImeTests",
    name: "FrameworksImeTests",
    defaults: [
    defaults: [
@@ -88,6 +111,7 @@ android_test {
        "frameworks-base-testutils",
        "frameworks-base-testutils",
        "mockito-target-extended-minus-junit4",
        "mockito-target-extended-minus-junit4",
        "platform-test-annotations",
        "platform-test-annotations",
        "ravenwood-junit",
        "services.core",
        "services.core",
        "service-permission.stubs.system_server",
        "service-permission.stubs.system_server",
        "servicestests-core-utils",
        "servicestests-core-utils",
+97 −0

File added.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Original line Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VI
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT;
import static com.android.server.inputmethod.ClientController.ClientState;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
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_EXPLICIT;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
@@ -68,8 +69,7 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe
        super.setUp();
        super.setUp();
        mVisibilityApplier =
        mVisibilityApplier =
                (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
                (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier();
        mInputMethodManagerService.setAttachedClientForTesting(
        mInputMethodManagerService.setAttachedClientForTesting(mock(ClientState.class));
                mock(InputMethodManagerService.ClientState.class));
    }
    }


    @Test
    @Test