Loading services/core/java/com/android/server/inputmethod/ClientController.java 0 → 100644 +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; } } } services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +55 −123 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); } } Loading @@ -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; Loading Loading @@ -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()); } } Loading Loading @@ -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()); Loading Loading @@ -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) { Loading @@ -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) { Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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) { Loading services/tests/InputMethodSystemServerTests/Android.bp +24 −0 Original line number Original line Diff line number Diff line Loading @@ -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", Loading @@ -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: [ Loading @@ -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", Loading services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java 0 → 100644 +97 −0 File added.Preview size limit exceeded, changes collapsed. Show changes services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +2 −2 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading Loading
services/core/java/com/android/server/inputmethod/ClientController.java 0 → 100644 +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; } } }
services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +55 −123 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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; Loading @@ -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. Loading Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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); Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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) { Loading Loading @@ -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; Loading Loading @@ -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); Loading Loading @@ -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); } } Loading @@ -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; Loading Loading @@ -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()); } } Loading Loading @@ -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()); Loading Loading @@ -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) { Loading @@ -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) { Loading Loading @@ -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); Loading Loading @@ -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); Loading Loading @@ -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) { Loading
services/tests/InputMethodSystemServerTests/Android.bp +24 −0 Original line number Original line Diff line number Diff line Loading @@ -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", Loading @@ -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: [ Loading @@ -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", Loading
services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ClientControllerTest.java 0 → 100644 +97 −0 File added.Preview size limit exceeded, changes collapsed. Show changes
services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +2 −2 Original line number Original line Diff line number Diff line Loading @@ -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; Loading Loading @@ -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 Loading