diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 58b93b3d947f6..3e23062594489 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -3547,6 +3547,8 @@ package android.view.inputmethod { public final class InputMethodInfo implements android.os.Parcelable { ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, boolean, @NonNull String); ctor public InputMethodInfo(@NonNull String, @NonNull String, @NonNull CharSequence, @NonNull String, int); + field public static final int COMPONENT_NAME_MAX_LENGTH = 1000; // 0x3e8 + field public static final int MAX_IMES_PER_PACKAGE = 20; // 0x14 } public final class InputMethodManager { diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index ec1badb637e42..8b55494c75d40 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -18,6 +18,7 @@ package android.view.inputmethod; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -83,6 +84,22 @@ public final class InputMethodInfo implements Parcelable { public static final String ACTION_STYLUS_HANDWRITING_SETTINGS = "android.view.inputmethod.action.STYLUS_HANDWRITING_SETTINGS"; + /** + * Maximal length of a component name + * @hide + */ + @TestApi + public static final int COMPONENT_NAME_MAX_LENGTH = 1000; + + /** + * The maximum amount of IMEs that are loaded per package (in order). + * If a package contains more IMEs, they will be ignored and cannot be enabled. + * @hide + */ + @TestApi + @SuppressLint("MinMaxConstant") + public static final int MAX_IMES_PER_PACKAGE = 20; + static final String TAG = "InputMethodInfo"; /** @@ -252,6 +269,13 @@ public final class InputMethodInfo implements Parcelable { com.android.internal.R.styleable.InputMethod); settingsActivityComponent = sa.getString( com.android.internal.R.styleable.InputMethod_settingsActivity); + if ((si.name != null && si.name.length() > COMPONENT_NAME_MAX_LENGTH) || ( + settingsActivityComponent != null + && settingsActivityComponent.length() > COMPONENT_NAME_MAX_LENGTH)) { + throw new XmlPullParserException( + "Activity name exceeds maximum of 1000 characters"); + } + isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false); isDefaultResId = sa.getResourceId( com.android.internal.R.styleable.InputMethod_isDefault, 0); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index f1422930dc8c9..1226007abfb00 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -2079,7 +2079,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, - methodList, directBootAwareness); + methodList, directBootAwareness, mSettings.getEnabledInputMethodNames()); settings = new InputMethodSettings(mContext, methodMap, userId, true /* copyOnWrite */); } // filter caller's access to input methods @@ -4136,7 +4136,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, - methodList, DirectBootAwareness.AUTO); + methodList, DirectBootAwareness.AUTO, mSettings.getEnabledInputMethodNames()); final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId, false); settings.setAdditionalInputMethodSubtypes(imiId, toBeAdded, additionalSubtypeMap, @@ -5033,7 +5033,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub static void queryInputMethodServicesInternal(Context context, @UserIdInt int userId, ArrayMap> additionalSubtypeMap, ArrayMap methodMap, ArrayList methodList, - @DirectBootAwareness int directBootAwareness) { + @DirectBootAwareness int directBootAwareness, List enabledInputMethodList) { final Context userAwareContext = context.getUserId() == userId ? context : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); @@ -5066,6 +5066,17 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub methodList.ensureCapacity(services.size()); methodMap.ensureCapacity(services.size()); + filterInputMethodServices(additionalSubtypeMap, methodMap, methodList, + enabledInputMethodList, userAwareContext, services); + } + + static void filterInputMethodServices( + ArrayMap> additionalSubtypeMap, + ArrayMap methodMap, ArrayList methodList, + List enabledInputMethodList, Context userAwareContext, + List services) { + final ArrayMap imiPackageCount = new ArrayMap<>(); + for (int i = 0; i < services.size(); ++i) { ResolveInfo ri = services.get(i); ServiceInfo si = ri.serviceInfo; @@ -5085,10 +5096,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (imi.isVrOnly()) { continue; // Skip VR-only IME, which isn't supported for now. } - methodList.add(imi); - methodMap.put(imi.getId(), imi); - if (DEBUG) { - Slog.d(TAG, "Found an input method " + imi); + final String packageName = si.packageName; + // only include IMEs which are from the system, enabled, or below the threshold + if (si.applicationInfo.isSystemApp() || enabledInputMethodList.contains(imi.getId()) + || imiPackageCount.getOrDefault(packageName, 0) + < InputMethodInfo.MAX_IMES_PER_PACKAGE) { + imiPackageCount.put(packageName, + 1 + imiPackageCount.getOrDefault(packageName, 0)); + + methodList.add(imi); + methodMap.put(imi.getId(), imi); + if (DEBUG) { + Slog.d(TAG, "Found an input method " + imi); + } + } else if (DEBUG) { + Slog.d(TAG, "Found an input method, but ignored due threshold: " + imi); } } catch (Exception e) { Slog.wtf(TAG, "Unable to load input method " + imeId, e); @@ -5110,7 +5132,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mMyPackageMonitor.clearKnownImePackageNamesLocked(); queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(), - mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO); + mAdditionalSubtypeMap, mMethodMap, mMethodList, DirectBootAwareness.AUTO, + mSettings.getEnabledInputMethodNames()); // Construct the set of possible IME packages for onPackageChanged() to avoid false // negatives when the package state remains to be the same but only the component state is @@ -5169,7 +5192,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub reenableMinimumNonAuxSystemImes); final int numImes = defaultEnabledIme.size(); for (int i = 0; i < numImes; ++i) { - final InputMethodInfo imi = defaultEnabledIme.get(i); + final InputMethodInfo imi = defaultEnabledIme.get(i); if (DEBUG) { Slog.d(TAG, "--- enable ime = " + imi); } @@ -5469,7 +5492,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList, DirectBootAwareness.AUTO); + methodMap, methodList, DirectBootAwareness.AUTO, + mSettings.getEnabledInputMethodNames()); return methodMap; } @@ -6429,7 +6453,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub new ArrayMap<>(); AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, - methodMap, methodList, DirectBootAwareness.AUTO); + methodMap, methodList, DirectBootAwareness.AUTO, + mSettings.getEnabledInputMethodNames()); final InputMethodSettings settings = new InputMethodSettings(mContext, methodMap, userId, false); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java index 559eb5341632b..17536fcb820e1 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java @@ -438,6 +438,15 @@ final class InputMethodUtils { mSubtypeSplitter); } + List getEnabledInputMethodNames() { + List result = new ArrayList<>(); + for (Pair> pair : + getEnabledInputMethodsAndSubtypeListLocked()) { + result.add(pair.first); + } + return result; + } + void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { if (reloadInputMethodStr) { getEnabledInputMethodsStr(); diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp index 05a8b11830f7a..07ddda39649a8 100644 --- a/services/tests/InputMethodSystemServerTests/Android.bp +++ b/services/tests/InputMethodSystemServerTests/Android.bp @@ -52,6 +52,10 @@ android_test { "android.test.runner", ], + data: [ + ":SimpleTestIme", + ], + certificate: "platform", platform_apis: true, test_suites: ["device-tests"], diff --git a/services/tests/InputMethodSystemServerTests/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/AndroidTest.xml index 92be78060da80..13719343ac498 100644 --- a/services/tests/InputMethodSystemServerTests/AndroidTest.xml +++ b/services/tests/InputMethodSystemServerTests/AndroidTest.xml @@ -21,6 +21,7 @@