summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java1
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSettings.java686
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java1
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodUtils.java646
-rw-r--r--services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java108
-rw-r--r--services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java77
7 files changed, 797 insertions, 725 deletions
diff --git a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
index 898d5a5e0644..ad27c5252cbe 100644
--- a/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
+++ b/services/core/java/com/android/server/inputmethod/HardwareKeyboardShortcutController.java
@@ -53,8 +53,7 @@ final class HardwareKeyboardShortcutController {
@GuardedBy("ImfLock.class")
void reset(@NonNull ArrayMap<String, InputMethodInfo> methodMap) {
mSubtypeHandles.clear();
- final InputMethodUtils.InputMethodSettings settings =
- new InputMethodUtils.InputMethodSettings(methodMap, mUserId);
+ final InputMethodSettings settings = new InputMethodSettings(methodMap, mUserId);
for (final InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) {
if (!imi.shouldShowInInputMethodPicker()) {
continue;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 4db9ead05636..622a2de4ab9d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -183,7 +183,6 @@ import com.android.server.companion.virtual.VirtualDeviceManagerInternal;
import com.android.server.input.InputManagerInternal;
import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
-import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
import com.android.server.pm.UserManagerInternal;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.utils.PriorityDump;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSettings.java b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
new file mode 100644
index 000000000000..4c7d7557de7d
--- /dev/null
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSettings.java
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) 2024 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.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageManagerInternal;
+import android.os.LocaleList;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.IntArray;
+import android.util.Pair;
+import android.util.Printer;
+import android.util.Slog;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+
+/**
+ * Utility class for putting and getting settings for InputMethod.
+ *
+ * <p>This is used in two ways:</p>
+ * <ul>
+ * <li>Singleton instance in {@link InputMethodManagerService}, which is updated on
+ * user-switch to follow the current user.</li>
+ * <li>On-demand instances when we need settings for non-current users.</li>
+ * </ul>
+ */
+final class InputMethodSettings {
+ public static final boolean DEBUG = false;
+ private static final String TAG = "InputMethodSettings";
+
+ private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
+ private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
+ private static final char INPUT_METHOD_SEPARATOR = InputMethodUtils.INPUT_METHOD_SEPARATOR;
+ private static final char INPUT_METHOD_SUBTYPE_SEPARATOR =
+ InputMethodUtils.INPUT_METHOD_SUBTYPE_SEPARATOR;
+
+ private final ArrayMap<String, InputMethodInfo> mMethodMap;
+ @UserIdInt
+ private final int mCurrentUserId;
+
+ private static void buildEnabledInputMethodsSettingString(
+ StringBuilder builder, Pair<String, ArrayList<String>> ime) {
+ builder.append(ime.first);
+ // Inputmethod and subtypes are saved in the settings as follows:
+ // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+ for (String subtypeId : ime.second) {
+ builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
+ }
+ }
+
+ InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
+ mMethodMap = methodMap;
+ mCurrentUserId = userId;
+ String ime = getSelectedInputMethod();
+ String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
+ if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
+ putSelectedInputMethod(defaultDeviceIme);
+ putSelectedDefaultDeviceInputMethod(null);
+ }
+ }
+
+ private void putString(@NonNull String key, @Nullable String str) {
+ SecureSettingsWrapper.putString(key, str, mCurrentUserId);
+ }
+
+ @Nullable
+ private String getString(@NonNull String key, @Nullable String defaultValue) {
+ return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
+ }
+
+ private void putInt(String key, int value) {
+ SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
+ }
+
+ private int getInt(String key, int defaultValue) {
+ return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
+ }
+
+ ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
+ return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */);
+ }
+
+ @NonNull
+ ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked(
+ @Nullable Predicate<InputMethodInfo> matchingCondition) {
+ return createEnabledInputMethodListLocked(
+ getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition);
+ }
+
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
+ InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
+ List<InputMethodSubtype> enabledSubtypes =
+ getEnabledInputMethodSubtypeListLocked(imi);
+ if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
+ enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
+ SystemLocaleWrapper.get(mCurrentUserId), imi);
+ }
+ return InputMethodSubtype.sort(imi, enabledSubtypes);
+ }
+
+ List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
+ List<Pair<String, ArrayList<String>>> imsList =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
+ if (imi != null) {
+ for (Pair<String, ArrayList<String>> imsPair : imsList) {
+ InputMethodInfo info = mMethodMap.get(imsPair.first);
+ if (info != null && info.getId().equals(imi.getId())) {
+ final int subtypeCount = info.getSubtypeCount();
+ for (int i = 0; i < subtypeCount; ++i) {
+ InputMethodSubtype ims = info.getSubtypeAt(i);
+ for (String s : imsPair.second) {
+ if (String.valueOf(ims.hashCode()).equals(s)) {
+ enabledSubtypes.add(ims);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ return enabledSubtypes;
+ }
+
+ List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
+ final String enabledInputMethodsStr = getEnabledInputMethodsStr();
+ final TextUtils.SimpleStringSplitter inputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter subtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
+ if (TextUtils.isEmpty(enabledInputMethodsStr)) {
+ return imsList;
+ }
+ inputMethodSplitter.setString(enabledInputMethodsStr);
+ while (inputMethodSplitter.hasNext()) {
+ String nextImsStr = inputMethodSplitter.next();
+ subtypeSplitter.setString(nextImsStr);
+ if (subtypeSplitter.hasNext()) {
+ ArrayList<String> subtypeHashes = new ArrayList<>();
+ // The first element is ime id.
+ String imeId = subtypeSplitter.next();
+ while (subtypeSplitter.hasNext()) {
+ subtypeHashes.add(subtypeSplitter.next());
+ }
+ imsList.add(new Pair<>(imeId, subtypeHashes));
+ }
+ }
+ return imsList;
+ }
+
+ /**
+ * Build and put a string of EnabledInputMethods with removing specified Id.
+ *
+ * @return the specified id was removed or not.
+ */
+ boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
+ StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
+ boolean isRemoved = false;
+ boolean needsAppendSeparator = false;
+ for (Pair<String, ArrayList<String>> ims : imsList) {
+ String curId = ims.first;
+ if (curId.equals(id)) {
+ // We are disabling this input method, and it is
+ // currently enabled. Skip it to remove from the
+ // new list.
+ isRemoved = true;
+ } else {
+ if (needsAppendSeparator) {
+ builder.append(INPUT_METHOD_SEPARATOR);
+ } else {
+ needsAppendSeparator = true;
+ }
+ buildEnabledInputMethodsSettingString(builder, ims);
+ }
+ }
+ if (isRemoved) {
+ // Update the setting with the new list of input methods.
+ putEnabledInputMethodsStr(builder.toString());
+ }
+ return isRemoved;
+ }
+
+ private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
+ List<Pair<String, ArrayList<String>>> imsList,
+ Predicate<InputMethodInfo> matchingCondition) {
+ final ArrayList<InputMethodInfo> res = new ArrayList<>();
+ for (Pair<String, ArrayList<String>> ims : imsList) {
+ InputMethodInfo info = mMethodMap.get(ims.first);
+ if (info != null && !info.isVrOnly()
+ && (matchingCondition == null || matchingCondition.test(info))) {
+ res.add(info);
+ }
+ }
+ return res;
+ }
+
+ void putEnabledInputMethodsStr(@Nullable String str) {
+ if (DEBUG) {
+ Slog.d(TAG, "putEnabledInputMethodStr: " + str);
+ }
+ if (TextUtils.isEmpty(str)) {
+ // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
+ // empty data scenario.
+ putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
+ } else {
+ putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
+ }
+ }
+
+ @NonNull
+ String getEnabledInputMethodsStr() {
+ return getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
+ }
+
+ private void saveSubtypeHistory(
+ List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
+ StringBuilder builder = new StringBuilder();
+ boolean isImeAdded = false;
+ if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
+ builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
+ newSubtypeId);
+ isImeAdded = true;
+ }
+ for (Pair<String, String> ime : savedImes) {
+ String imeId = ime.first;
+ String subtypeId = ime.second;
+ if (TextUtils.isEmpty(subtypeId)) {
+ subtypeId = NOT_A_SUBTYPE_ID_STR;
+ }
+ if (isImeAdded) {
+ builder.append(INPUT_METHOD_SEPARATOR);
+ } else {
+ isImeAdded = true;
+ }
+ builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
+ subtypeId);
+ }
+ // Remove the last INPUT_METHOD_SEPARATOR
+ putSubtypeHistoryStr(builder.toString());
+ }
+
+ private void addSubtypeToHistory(String imeId, String subtypeId) {
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> ime : subtypeHistory) {
+ if (ime.first.equals(imeId)) {
+ if (DEBUG) {
+ Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
+ + ime.second);
+ }
+ // We should break here
+ subtypeHistory.remove(ime);
+ break;
+ }
+ }
+ if (DEBUG) {
+ Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
+ }
+ saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
+ }
+
+ private void putSubtypeHistoryStr(@NonNull String str) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSubtypeHistoryStr: " + str);
+ }
+ if (TextUtils.isEmpty(str)) {
+ // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
+ // data scenario.
+ putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
+ } else {
+ putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
+ }
+ }
+
+ Pair<String, String> getLastInputMethodAndSubtypeLocked() {
+ // Gets the first one from the history
+ return getLastSubtypeForInputMethodLockedInternal(null);
+ }
+
+ @Nullable
+ InputMethodSubtype getLastInputMethodSubtypeLocked() {
+ final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked();
+ // TODO: Handle the case of the last IME with no subtypes
+ if (lastIme == null || TextUtils.isEmpty(lastIme.first)
+ || TextUtils.isEmpty(lastIme.second)) {
+ return null;
+ }
+ final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
+ if (lastImi == null) return null;
+ try {
+ final int lastSubtypeHash = Integer.parseInt(lastIme.second);
+ final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
+ lastSubtypeHash);
+ if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
+ return null;
+ }
+ return lastImi.getSubtypeAt(lastSubtypeId);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ String getLastSubtypeForInputMethodLocked(String imeId) {
+ Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
+ if (ime != null) {
+ return ime.second;
+ } else {
+ return null;
+ }
+ }
+
+ private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
+ List<Pair<String, ArrayList<String>>> enabledImes =
+ getEnabledInputMethodsAndSubtypeListLocked();
+ List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
+ for (Pair<String, String> imeAndSubtype : subtypeHistory) {
+ final String imeInTheHistory = imeAndSubtype.first;
+ // If imeId is empty, returns the first IME and subtype in the history
+ if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
+ final String subtypeInTheHistory = imeAndSubtype.second;
+ final String subtypeHashCode =
+ getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
+ enabledImes, imeInTheHistory, subtypeInTheHistory);
+ if (!TextUtils.isEmpty(subtypeHashCode)) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Enabled subtype found in the history: " + subtypeHashCode);
+ }
+ return new Pair<>(imeInTheHistory, subtypeHashCode);
+ }
+ }
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "No enabled IME found in the history");
+ }
+ return null;
+ }
+
+ private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
+ ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
+ final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
+ for (Pair<String, ArrayList<String>> enabledIme : enabledImes) {
+ if (enabledIme.first.equals(imeId)) {
+ final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
+ final InputMethodInfo imi = mMethodMap.get(imeId);
+ if (explicitlyEnabledSubtypes.size() == 0) {
+ // If there are no explicitly enabled subtypes, applicable subtypes are
+ // enabled implicitly.
+ // If IME is enabled and no subtypes are enabled, applicable subtypes
+ // are enabled implicitly, so needs to treat them to be enabled.
+ if (imi != null && imi.getSubtypeCount() > 0) {
+ List<InputMethodSubtype> implicitlyEnabledSubtypes =
+ SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
+ imi);
+ final int numSubtypes = implicitlyEnabledSubtypes.size();
+ for (int i = 0; i < numSubtypes; ++i) {
+ final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
+ if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
+ return subtypeHashCode;
+ }
+ }
+ }
+ } else {
+ for (String s : explicitlyEnabledSubtypes) {
+ if (s.equals(subtypeHashCode)) {
+ // If both imeId and subtypeId are enabled, return subtypeId.
+ try {
+ final int hashCode = Integer.parseInt(subtypeHashCode);
+ // Check whether the subtype id is valid or not
+ if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) {
+ return s;
+ } else {
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ } catch (NumberFormatException e) {
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ }
+ }
+ }
+ // If imeId was enabled but subtypeId was disabled.
+ return NOT_A_SUBTYPE_ID_STR;
+ }
+ }
+ // If both imeId and subtypeId are disabled, return null
+ return null;
+ }
+
+ private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
+ ArrayList<Pair<String, String>> imsList = new ArrayList<>();
+ final String subtypeHistoryStr = getSubtypeHistoryStr();
+ if (TextUtils.isEmpty(subtypeHistoryStr)) {
+ return imsList;
+ }
+ final TextUtils.SimpleStringSplitter inputMethodSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter subtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ inputMethodSplitter.setString(subtypeHistoryStr);
+ while (inputMethodSplitter.hasNext()) {
+ String nextImsStr = inputMethodSplitter.next();
+ subtypeSplitter.setString(nextImsStr);
+ if (subtypeSplitter.hasNext()) {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ // The first element is ime id.
+ String imeId = subtypeSplitter.next();
+ while (subtypeSplitter.hasNext()) {
+ subtypeId = subtypeSplitter.next();
+ break;
+ }
+ imsList.add(new Pair<>(imeId, subtypeId));
+ }
+ }
+ return imsList;
+ }
+
+ @NonNull
+ private String getSubtypeHistoryStr() {
+ final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
+ if (DEBUG) {
+ Slog.d(TAG, "getSubtypeHistoryStr: " + history);
+ }
+ return history;
+ }
+
+ void putSelectedInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
+ + mCurrentUserId);
+ }
+ putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
+ }
+
+ void putSelectedSubtype(int subtypeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
+ + mCurrentUserId);
+ }
+ putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+ }
+
+ @Nullable
+ String getSelectedInputMethod() {
+ final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
+ if (DEBUG) {
+ Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
+ }
+ return imi;
+ }
+
+ @Nullable
+ String getSelectedDefaultDeviceInputMethod() {
+ final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
+ if (DEBUG) {
+ Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
+ + mCurrentUserId);
+ }
+ return imi;
+ }
+
+ void putSelectedDefaultDeviceInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
+ + mCurrentUserId);
+ }
+ putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
+ }
+
+ void putDefaultVoiceInputMethod(String imeId) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
+ }
+ putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
+ }
+
+ @Nullable
+ String getDefaultVoiceInputMethod() {
+ final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null);
+ if (DEBUG) {
+ Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi);
+ }
+ return imi;
+ }
+
+ boolean isSubtypeSelected() {
+ return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+ }
+
+ private int getSelectedInputMethodSubtypeHashCode() {
+ return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+ NOT_A_SUBTYPE_ID);
+ }
+
+ @UserIdInt
+ public int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ int getSelectedInputMethodSubtypeId(String selectedImiId) {
+ final InputMethodInfo imi = mMethodMap.get(selectedImiId);
+ if (imi == null) {
+ return NOT_A_SUBTYPE_ID;
+ }
+ final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+ return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
+ }
+
+ void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
+ InputMethodSubtype currentSubtype) {
+ String subtypeId = NOT_A_SUBTYPE_ID_STR;
+ if (currentSubtype != null) {
+ subtypeId = String.valueOf(currentSubtype.hashCode());
+ }
+ if (InputMethodUtils.canAddToLastInputMethod(currentSubtype)) {
+ addSubtypeToHistory(curMethodId, subtypeId);
+ }
+ }
+
+ /**
+ * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
+ * non-current users.
+ *
+ * <p>TODO: Address code duplication between this and
+ * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
+ *
+ * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
+ */
+ @Nullable
+ InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
+ final String selectedMethodId = getSelectedInputMethod();
+ if (selectedMethodId == null) {
+ return null;
+ }
+ final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
+ if (imi == null || imi.getSubtypeCount() == 0) {
+ return null;
+ }
+
+ final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
+ if (subtypeHashCode != NOT_A_SUBTYPE_ID) {
+ final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
+ subtypeHashCode);
+ if (subtypeIndex >= 0) {
+ return imi.getSubtypeAt(subtypeIndex);
+ }
+ }
+
+ // If there are no selected subtypes, the framework will try to find the most applicable
+ // subtype from explicitly or implicitly enabled subtypes.
+ final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+ getEnabledInputMethodSubtypeListLocked(imi, true);
+ // If there is only one explicitly or implicitly enabled subtype, just returns it.
+ if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
+ return null;
+ }
+ if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+ return explicitlyOrImplicitlyEnabledSubtypes.get(0);
+ }
+ final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
+ final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
+ locale, true);
+ if (subtype != null) {
+ return subtype;
+ }
+ return SubtypeUtils.findLastResortApplicableSubtypeLocked(
+ explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
+ }
+
+ boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
+ @NonNull ArrayList<InputMethodSubtype> subtypes,
+ @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
+ @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
+ final InputMethodInfo imi = mMethodMap.get(imeId);
+ if (imi == null) {
+ return false;
+ }
+ if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
+ imi.getPackageName())) {
+ return false;
+ }
+
+ if (subtypes.isEmpty()) {
+ additionalSubtypeMap.remove(imi.getId());
+ } else {
+ additionalSubtypeMap.put(imi.getId(), subtypes);
+ }
+ AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId());
+ return true;
+ }
+
+ boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
+ @NonNull int[] subtypeHashCodes) {
+ final InputMethodInfo imi = mMethodMap.get(imeId);
+ if (imi == null) {
+ return false;
+ }
+
+ final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length);
+ for (int subtypeHashCode : subtypeHashCodes) {
+ if (subtypeHashCode == NOT_A_SUBTYPE_ID) {
+ continue; // NOT_A_SUBTYPE_ID must not be saved
+ }
+ if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) {
+ continue; // this subtype does not exist in InputMethodInfo.
+ }
+ if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) {
+ continue; // The entry is already added. No need to add anymore.
+ }
+ validSubtypeHashCodes.add(subtypeHashCode);
+ }
+
+ final String originalEnabledImesString = getEnabledInputMethodsStr();
+ final String updatedEnabledImesString = updateEnabledImeString(
+ originalEnabledImesString, imi.getId(), validSubtypeHashCodes);
+ if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) {
+ return false;
+ }
+
+ putEnabledInputMethodsStr(updatedEnabledImesString);
+ return true;
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ static String updateEnabledImeString(@NonNull String enabledImesString,
+ @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) {
+ final TextUtils.SimpleStringSplitter imeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
+ final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+ new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
+
+ final StringBuilder sb = new StringBuilder();
+
+ imeSplitter.setString(enabledImesString);
+ boolean needsImeSeparator = false;
+ while (imeSplitter.hasNext()) {
+ final String nextImsStr = imeSplitter.next();
+ imeSubtypeSplitter.setString(nextImsStr);
+ if (imeSubtypeSplitter.hasNext()) {
+ if (needsImeSeparator) {
+ sb.append(INPUT_METHOD_SEPARATOR);
+ }
+ if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) {
+ sb.append(imeId);
+ for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) {
+ sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR);
+ sb.append(enabledSubtypeHashCodes.get(i));
+ }
+ } else {
+ sb.append(nextImsStr);
+ }
+ needsImeSeparator = true;
+ }
+ }
+ return sb.toString();
+ }
+
+ void dumpLocked(final Printer pw, final String prefix) {
+ pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 4439b0683afa..43058080d84d 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -31,7 +31,6 @@ import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
import java.util.ArrayList;
import java.util.Collections;
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 58a68f2a0166..361cdbbc15bf 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -28,15 +28,10 @@ import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Build;
-import android.os.LocaleList;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
-import android.util.ArrayMap;
import android.util.ArraySet;
-import android.util.IntArray;
-import android.util.Pair;
-import android.util.Printer;
import android.util.Slog;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
@@ -51,10 +46,8 @@ import com.android.server.textservices.TextServicesManagerInternal;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import java.util.StringJoiner;
import java.util.function.Consumer;
-import java.util.function.Predicate;
/**
* This class provides random static utility methods for {@link InputMethodManagerService} and its
@@ -68,12 +61,11 @@ final class InputMethodUtils {
public static final boolean DEBUG = false;
static final int NOT_A_SUBTYPE_ID = -1;
private static final String TAG = "InputMethodUtils";
- private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
// The string for enabled input method is saved as follows:
// example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
- private static final char INPUT_METHOD_SEPARATOR = ':';
- private static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
+ static final char INPUT_METHOD_SEPARATOR = ':';
+ static final char INPUT_METHOD_SUBTYPE_SEPARATOR = ';';
private InputMethodUtils() {
// This utility class is not publicly instantiable.
@@ -200,640 +192,6 @@ final class InputMethodUtils {
UserHandle.getUserId(uid));
}
- /**
- * Utility class for putting and getting settings for InputMethod.
- *
- * This is used in two ways:
- * - Singleton instance in {@link InputMethodManagerService}, which is updated on user-switch to
- * follow the current user.
- * - On-demand instances when we need settings for non-current users.
- *
- * TODO: Move all putters and getters of settings to this class.
- */
- @UserHandleAware
- public static class InputMethodSettings {
- private final ArrayMap<String, InputMethodInfo> mMethodMap;
-
- @UserIdInt
- private final int mCurrentUserId;
-
- private static void buildEnabledInputMethodsSettingString(
- StringBuilder builder, Pair<String, ArrayList<String>> ime) {
- builder.append(ime.first);
- // Inputmethod and subtypes are saved in the settings as follows:
- // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
- for (String subtypeId: ime.second) {
- builder.append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(subtypeId);
- }
- }
-
- InputMethodSettings(ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
- mMethodMap = methodMap;
- mCurrentUserId = userId;
- String ime = getSelectedInputMethod();
- String defaultDeviceIme = getSelectedDefaultDeviceInputMethod();
- if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) {
- putSelectedInputMethod(defaultDeviceIme);
- putSelectedDefaultDeviceInputMethod(null);
- }
- }
-
- private void putString(@NonNull String key, @Nullable String str) {
- SecureSettingsWrapper.putString(key, str, mCurrentUserId);
- }
-
- @Nullable
- private String getString(@NonNull String key, @Nullable String defaultValue) {
- return SecureSettingsWrapper.getString(key, defaultValue, mCurrentUserId);
- }
-
- private void putInt(String key, int value) {
- SecureSettingsWrapper.putInt(key, value, mCurrentUserId);
- }
-
- private int getInt(String key, int defaultValue) {
- return SecureSettingsWrapper.getInt(key, defaultValue, mCurrentUserId);
- }
-
- ArrayList<InputMethodInfo> getEnabledInputMethodListLocked() {
- return getEnabledInputMethodListWithFilterLocked(null /* matchingCondition */);
- }
-
- @NonNull
- ArrayList<InputMethodInfo> getEnabledInputMethodListWithFilterLocked(
- @Nullable Predicate<InputMethodInfo> matchingCondition) {
- return createEnabledInputMethodListLocked(
- getEnabledInputMethodsAndSubtypeListLocked(), matchingCondition);
- }
-
- List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
- InputMethodInfo imi, boolean allowsImplicitlyEnabledSubtypes) {
- List<InputMethodSubtype> enabledSubtypes =
- getEnabledInputMethodSubtypeListLocked(imi);
- if (allowsImplicitlyEnabledSubtypes && enabledSubtypes.isEmpty()) {
- enabledSubtypes = SubtypeUtils.getImplicitlyApplicableSubtypesLocked(
- SystemLocaleWrapper.get(mCurrentUserId), imi);
- }
- return InputMethodSubtype.sort(imi, enabledSubtypes);
- }
-
- List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi) {
- List<Pair<String, ArrayList<String>>> imsList =
- getEnabledInputMethodsAndSubtypeListLocked();
- ArrayList<InputMethodSubtype> enabledSubtypes = new ArrayList<>();
- if (imi != null) {
- for (Pair<String, ArrayList<String>> imsPair : imsList) {
- InputMethodInfo info = mMethodMap.get(imsPair.first);
- if (info != null && info.getId().equals(imi.getId())) {
- final int subtypeCount = info.getSubtypeCount();
- for (int i = 0; i < subtypeCount; ++i) {
- InputMethodSubtype ims = info.getSubtypeAt(i);
- for (String s: imsPair.second) {
- if (String.valueOf(ims.hashCode()).equals(s)) {
- enabledSubtypes.add(ims);
- }
- }
- }
- break;
- }
- }
- }
- return enabledSubtypes;
- }
-
- List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
- final String enabledInputMethodsStr = getEnabledInputMethodsStr();
- final TextUtils.SimpleStringSplitter inputMethodSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
- final TextUtils.SimpleStringSplitter subtypeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
- final ArrayList<Pair<String, ArrayList<String>>> imsList = new ArrayList<>();
- if (TextUtils.isEmpty(enabledInputMethodsStr)) {
- return imsList;
- }
- inputMethodSplitter.setString(enabledInputMethodsStr);
- while (inputMethodSplitter.hasNext()) {
- String nextImsStr = inputMethodSplitter.next();
- subtypeSplitter.setString(nextImsStr);
- if (subtypeSplitter.hasNext()) {
- ArrayList<String> subtypeHashes = new ArrayList<>();
- // The first element is ime id.
- String imeId = subtypeSplitter.next();
- while (subtypeSplitter.hasNext()) {
- subtypeHashes.add(subtypeSplitter.next());
- }
- imsList.add(new Pair<>(imeId, subtypeHashes));
- }
- }
- return imsList;
- }
-
- /**
- * Build and put a string of EnabledInputMethods with removing specified Id.
- * @return the specified id was removed or not.
- */
- boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
- StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
- boolean isRemoved = false;
- boolean needsAppendSeparator = false;
- for (Pair<String, ArrayList<String>> ims: imsList) {
- String curId = ims.first;
- if (curId.equals(id)) {
- // We are disabling this input method, and it is
- // currently enabled. Skip it to remove from the
- // new list.
- isRemoved = true;
- } else {
- if (needsAppendSeparator) {
- builder.append(INPUT_METHOD_SEPARATOR);
- } else {
- needsAppendSeparator = true;
- }
- buildEnabledInputMethodsSettingString(builder, ims);
- }
- }
- if (isRemoved) {
- // Update the setting with the new list of input methods.
- putEnabledInputMethodsStr(builder.toString());
- }
- return isRemoved;
- }
-
- private ArrayList<InputMethodInfo> createEnabledInputMethodListLocked(
- List<Pair<String, ArrayList<String>>> imsList,
- Predicate<InputMethodInfo> matchingCondition) {
- final ArrayList<InputMethodInfo> res = new ArrayList<>();
- for (Pair<String, ArrayList<String>> ims: imsList) {
- InputMethodInfo info = mMethodMap.get(ims.first);
- if (info != null && !info.isVrOnly()
- && (matchingCondition == null || matchingCondition.test(info))) {
- res.add(info);
- }
- }
- return res;
- }
-
- void putEnabledInputMethodsStr(@Nullable String str) {
- if (DEBUG) {
- Slog.d(TAG, "putEnabledInputMethodStr: " + str);
- }
- if (TextUtils.isEmpty(str)) {
- // OK to coalesce to null, since getEnabledInputMethodsStr() can take care of the
- // empty data scenario.
- putString(Settings.Secure.ENABLED_INPUT_METHODS, null);
- } else {
- putString(Settings.Secure.ENABLED_INPUT_METHODS, str);
- }
- }
-
- @NonNull
- String getEnabledInputMethodsStr() {
- return getString(Settings.Secure.ENABLED_INPUT_METHODS, "");
- }
-
- private void saveSubtypeHistory(
- List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
- StringBuilder builder = new StringBuilder();
- boolean isImeAdded = false;
- if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
- builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
- newSubtypeId);
- isImeAdded = true;
- }
- for (Pair<String, String> ime: savedImes) {
- String imeId = ime.first;
- String subtypeId = ime.second;
- if (TextUtils.isEmpty(subtypeId)) {
- subtypeId = NOT_A_SUBTYPE_ID_STR;
- }
- if (isImeAdded) {
- builder.append(INPUT_METHOD_SEPARATOR);
- } else {
- isImeAdded = true;
- }
- builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATOR).append(
- subtypeId);
- }
- // Remove the last INPUT_METHOD_SEPARATOR
- putSubtypeHistoryStr(builder.toString());
- }
-
- private void addSubtypeToHistory(String imeId, String subtypeId) {
- List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
- for (Pair<String, String> ime: subtypeHistory) {
- if (ime.first.equals(imeId)) {
- if (DEBUG) {
- Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
- + ime.second);
- }
- // We should break here
- subtypeHistory.remove(ime);
- break;
- }
- }
- if (DEBUG) {
- Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
- }
- saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
- }
-
- private void putSubtypeHistoryStr(@NonNull String str) {
- if (DEBUG) {
- Slog.d(TAG, "putSubtypeHistoryStr: " + str);
- }
- if (TextUtils.isEmpty(str)) {
- // OK to coalesce to null, since getSubtypeHistoryStr() can take care of the empty
- // data scenario.
- putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, null);
- } else {
- putString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
- }
- }
-
- Pair<String, String> getLastInputMethodAndSubtypeLocked() {
- // Gets the first one from the history
- return getLastSubtypeForInputMethodLockedInternal(null);
- }
-
- @Nullable
- InputMethodSubtype getLastInputMethodSubtypeLocked() {
- final Pair<String, String> lastIme = getLastInputMethodAndSubtypeLocked();
- // TODO: Handle the case of the last IME with no subtypes
- if (lastIme == null || TextUtils.isEmpty(lastIme.first)
- || TextUtils.isEmpty(lastIme.second)) return null;
- final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
- if (lastImi == null) return null;
- try {
- final int lastSubtypeHash = Integer.parseInt(lastIme.second);
- final int lastSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(lastImi,
- lastSubtypeHash);
- if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
- return null;
- }
- return lastImi.getSubtypeAt(lastSubtypeId);
- } catch (NumberFormatException e) {
- return null;
- }
- }
-
- String getLastSubtypeForInputMethodLocked(String imeId) {
- Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
- if (ime != null) {
- return ime.second;
- } else {
- return null;
- }
- }
-
- private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
- List<Pair<String, ArrayList<String>>> enabledImes =
- getEnabledInputMethodsAndSubtypeListLocked();
- List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
- for (Pair<String, String> imeAndSubtype : subtypeHistory) {
- final String imeInTheHistory = imeAndSubtype.first;
- // If imeId is empty, returns the first IME and subtype in the history
- if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
- final String subtypeInTheHistory = imeAndSubtype.second;
- final String subtypeHashCode =
- getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
- enabledImes, imeInTheHistory, subtypeInTheHistory);
- if (!TextUtils.isEmpty(subtypeHashCode)) {
- if (DEBUG) {
- Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
- }
- return new Pair<>(imeInTheHistory, subtypeHashCode);
- }
- }
- }
- if (DEBUG) {
- Slog.d(TAG, "No enabled IME found in the history");
- }
- return null;
- }
-
- private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
- ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
- final LocaleList localeList = SystemLocaleWrapper.get(mCurrentUserId);
- for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
- if (enabledIme.first.equals(imeId)) {
- final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
- final InputMethodInfo imi = mMethodMap.get(imeId);
- if (explicitlyEnabledSubtypes.size() == 0) {
- // If there are no explicitly enabled subtypes, applicable subtypes are
- // enabled implicitly.
- // If IME is enabled and no subtypes are enabled, applicable subtypes
- // are enabled implicitly, so needs to treat them to be enabled.
- if (imi != null && imi.getSubtypeCount() > 0) {
- List<InputMethodSubtype> implicitlyEnabledSubtypes =
- SubtypeUtils.getImplicitlyApplicableSubtypesLocked(localeList,
- imi);
- final int numSubtypes = implicitlyEnabledSubtypes.size();
- for (int i = 0; i < numSubtypes; ++i) {
- final InputMethodSubtype st = implicitlyEnabledSubtypes.get(i);
- if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
- return subtypeHashCode;
- }
- }
- }
- } else {
- for (String s: explicitlyEnabledSubtypes) {
- if (s.equals(subtypeHashCode)) {
- // If both imeId and subtypeId are enabled, return subtypeId.
- try {
- final int hashCode = Integer.parseInt(subtypeHashCode);
- // Check whether the subtype id is valid or not
- if (SubtypeUtils.isValidSubtypeId(imi, hashCode)) {
- return s;
- } else {
- return NOT_A_SUBTYPE_ID_STR;
- }
- } catch (NumberFormatException e) {
- return NOT_A_SUBTYPE_ID_STR;
- }
- }
- }
- }
- // If imeId was enabled but subtypeId was disabled.
- return NOT_A_SUBTYPE_ID_STR;
- }
- }
- // If both imeId and subtypeId are disabled, return null
- return null;
- }
-
- private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
- ArrayList<Pair<String, String>> imsList = new ArrayList<>();
- final String subtypeHistoryStr = getSubtypeHistoryStr();
- if (TextUtils.isEmpty(subtypeHistoryStr)) {
- return imsList;
- }
- final TextUtils.SimpleStringSplitter inputMethodSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
- final TextUtils.SimpleStringSplitter subtypeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
- inputMethodSplitter.setString(subtypeHistoryStr);
- while (inputMethodSplitter.hasNext()) {
- String nextImsStr = inputMethodSplitter.next();
- subtypeSplitter.setString(nextImsStr);
- if (subtypeSplitter.hasNext()) {
- String subtypeId = NOT_A_SUBTYPE_ID_STR;
- // The first element is ime id.
- String imeId = subtypeSplitter.next();
- while (subtypeSplitter.hasNext()) {
- subtypeId = subtypeSplitter.next();
- break;
- }
- imsList.add(new Pair<>(imeId, subtypeId));
- }
- }
- return imsList;
- }
-
- @NonNull
- private String getSubtypeHistoryStr() {
- final String history = getString(Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, "");
- if (DEBUG) {
- Slog.d(TAG, "getSubtypeHistoryStr: " + history);
- }
- return history;
- }
-
- void putSelectedInputMethod(String imeId) {
- if (DEBUG) {
- Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
- + mCurrentUserId);
- }
- putString(Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
- }
-
- void putSelectedSubtype(int subtypeId) {
- if (DEBUG) {
- Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
- + mCurrentUserId);
- }
- putInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
- }
-
- @Nullable
- String getSelectedInputMethod() {
- final String imi = getString(Settings.Secure.DEFAULT_INPUT_METHOD, null);
- if (DEBUG) {
- Slog.d(TAG, "getSelectedInputMethodStr: " + imi);
- }
- return imi;
- }
-
- @Nullable
- String getSelectedDefaultDeviceInputMethod() {
- final String imi = getString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null);
- if (DEBUG) {
- Slog.d(TAG, "getSelectedDefaultDeviceInputMethodStr: " + imi + ", "
- + mCurrentUserId);
- }
- return imi;
- }
-
- void putSelectedDefaultDeviceInputMethod(String imeId) {
- if (DEBUG) {
- Slog.d(TAG, "putSelectedDefaultDeviceInputMethodStr: " + imeId + ", "
- + mCurrentUserId);
- }
- putString(Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, imeId);
- }
-
- void putDefaultVoiceInputMethod(String imeId) {
- if (DEBUG) {
- Slog.d(TAG, "putDefaultVoiceInputMethodStr: " + imeId + ", " + mCurrentUserId);
- }
- putString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, imeId);
- }
-
- @Nullable
- String getDefaultVoiceInputMethod() {
- final String imi = getString(Settings.Secure.DEFAULT_VOICE_INPUT_METHOD, null);
- if (DEBUG) {
- Slog.d(TAG, "getDefaultVoiceInputMethodStr: " + imi);
- }
- return imi;
- }
-
- boolean isSubtypeSelected() {
- return getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
- }
-
- private int getSelectedInputMethodSubtypeHashCode() {
- return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
- }
-
- @UserIdInt
- public int getCurrentUserId() {
- return mCurrentUserId;
- }
-
- int getSelectedInputMethodSubtypeId(String selectedImiId) {
- final InputMethodInfo imi = mMethodMap.get(selectedImiId);
- if (imi == null) {
- return NOT_A_SUBTYPE_ID;
- }
- final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
- return SubtypeUtils.getSubtypeIdFromHashCode(imi, subtypeHashCode);
- }
-
- void saveCurrentInputMethodAndSubtypeToHistory(String curMethodId,
- InputMethodSubtype currentSubtype) {
- String subtypeId = NOT_A_SUBTYPE_ID_STR;
- if (currentSubtype != null) {
- subtypeId = String.valueOf(currentSubtype.hashCode());
- }
- if (canAddToLastInputMethod(currentSubtype)) {
- addSubtypeToHistory(curMethodId, subtypeId);
- }
- }
-
- /**
- * A variant of {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()} for
- * non-current users.
- *
- * <p>TODO: Address code duplication between this and
- * {@link InputMethodManagerService#getCurrentInputMethodSubtypeLocked()}.</p>
- *
- * @return {@link InputMethodSubtype} if exists. {@code null} otherwise.
- */
- @Nullable
- InputMethodSubtype getCurrentInputMethodSubtypeForNonCurrentUsers() {
- final String selectedMethodId = getSelectedInputMethod();
- if (selectedMethodId == null) {
- return null;
- }
- final InputMethodInfo imi = mMethodMap.get(selectedMethodId);
- if (imi == null || imi.getSubtypeCount() == 0) {
- return null;
- }
-
- final int subtypeHashCode = getSelectedInputMethodSubtypeHashCode();
- if (subtypeHashCode != InputMethodUtils.NOT_A_SUBTYPE_ID) {
- final int subtypeIndex = SubtypeUtils.getSubtypeIdFromHashCode(imi,
- subtypeHashCode);
- if (subtypeIndex >= 0) {
- return imi.getSubtypeAt(subtypeIndex);
- }
- }
-
- // If there are no selected subtypes, the framework will try to find the most applicable
- // subtype from explicitly or implicitly enabled subtypes.
- final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
- getEnabledInputMethodSubtypeListLocked(imi, true);
- // If there is only one explicitly or implicitly enabled subtype, just returns it.
- if (explicitlyOrImplicitlyEnabledSubtypes.isEmpty()) {
- return null;
- }
- if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
- return explicitlyOrImplicitlyEnabledSubtypes.get(0);
- }
- final String locale = SystemLocaleWrapper.get(mCurrentUserId).get(0).toString();
- final InputMethodSubtype subtype = SubtypeUtils.findLastResortApplicableSubtypeLocked(
- explicitlyOrImplicitlyEnabledSubtypes, SubtypeUtils.SUBTYPE_MODE_KEYBOARD,
- locale, true);
- if (subtype != null) {
- return subtype;
- }
- return SubtypeUtils.findLastResortApplicableSubtypeLocked(
- explicitlyOrImplicitlyEnabledSubtypes, null, locale, true);
- }
-
- boolean setAdditionalInputMethodSubtypes(@NonNull String imeId,
- @NonNull ArrayList<InputMethodSubtype> subtypes,
- @NonNull ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap,
- @NonNull PackageManagerInternal packageManagerInternal, int callingUid) {
- final InputMethodInfo imi = mMethodMap.get(imeId);
- if (imi == null) {
- return false;
- }
- if (!InputMethodUtils.checkIfPackageBelongsToUid(packageManagerInternal, callingUid,
- imi.getPackageName())) {
- return false;
- }
-
- if (subtypes.isEmpty()) {
- additionalSubtypeMap.remove(imi.getId());
- } else {
- additionalSubtypeMap.put(imi.getId(), subtypes);
- }
- AdditionalSubtypeUtils.save(additionalSubtypeMap, mMethodMap, getCurrentUserId());
- return true;
- }
-
- boolean setEnabledInputMethodSubtypes(@NonNull String imeId,
- @NonNull int[] subtypeHashCodes) {
- final InputMethodInfo imi = mMethodMap.get(imeId);
- if (imi == null) {
- return false;
- }
-
- final IntArray validSubtypeHashCodes = new IntArray(subtypeHashCodes.length);
- for (int subtypeHashCode : subtypeHashCodes) {
- if (subtypeHashCode == NOT_A_SUBTYPE_ID) {
- continue; // NOT_A_SUBTYPE_ID must not be saved
- }
- if (!SubtypeUtils.isValidSubtypeId(imi, subtypeHashCode)) {
- continue; // this subtype does not exist in InputMethodInfo.
- }
- if (validSubtypeHashCodes.indexOf(subtypeHashCode) >= 0) {
- continue; // The entry is already added. No need to add anymore.
- }
- validSubtypeHashCodes.add(subtypeHashCode);
- }
-
- final String originalEnabledImesString = getEnabledInputMethodsStr();
- final String updatedEnabledImesString = updateEnabledImeString(
- originalEnabledImesString, imi.getId(), validSubtypeHashCodes);
- if (TextUtils.equals(originalEnabledImesString, updatedEnabledImesString)) {
- return false;
- }
-
- putEnabledInputMethodsStr(updatedEnabledImesString);
- return true;
- }
-
- @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
- static String updateEnabledImeString(@NonNull String enabledImesString,
- @NonNull String imeId, @NonNull IntArray enabledSubtypeHashCodes) {
- final TextUtils.SimpleStringSplitter imeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATOR);
- final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
- new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR);
-
- final StringBuilder sb = new StringBuilder();
-
- imeSplitter.setString(enabledImesString);
- boolean needsImeSeparator = false;
- while (imeSplitter.hasNext()) {
- final String nextImsStr = imeSplitter.next();
- imeSubtypeSplitter.setString(nextImsStr);
- if (imeSubtypeSplitter.hasNext()) {
- if (needsImeSeparator) {
- sb.append(INPUT_METHOD_SEPARATOR);
- }
- if (TextUtils.equals(imeId, imeSubtypeSplitter.next())) {
- sb.append(imeId);
- for (int i = 0; i < enabledSubtypeHashCodes.size(); ++i) {
- sb.append(INPUT_METHOD_SUBTYPE_SEPARATOR);
- sb.append(enabledSubtypeHashCodes.get(i));
- }
- } else {
- sb.append(nextImsStr);
- }
- needsImeSeparator = true;
- }
- }
- return sb.toString();
- }
-
- public void dumpLocked(final Printer pw, final String prefix) {
- pw.println(prefix + "mCurrentUserId=" + mCurrentUserId);
- }
- }
-
static boolean isSoftInputModeStateVisibleAllowed(int targetSdkVersion,
@StartInputFlags int startInputFlags) {
if (targetSdkVersion < Build.VERSION_CODES.P) {
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
new file mode 100644
index 000000000000..a55d1c409fb6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodSettingsTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+
+import android.text.TextUtils;
+import android.util.IntArray;
+
+import androidx.annotation.NonNull;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public final class InputMethodSettingsTest {
+ private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
+ @NonNull String initialEnabledImeStr, @NonNull String imeId,
+ @NonNull String enabledSubtypeHashCodesStr) {
+ assertEquals(expectedEnabledImeStr,
+ InputMethodSettings.updateEnabledImeString(initialEnabledImeStr,
+ imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
+ }
+
+ private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
+ final IntArray subtypes = new IntArray();
+ final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
+ new TextUtils.SimpleStringSplitter(';');
+ if (TextUtils.isEmpty(subtypeHashCodesStr)) {
+ return subtypes;
+ }
+ imeSubtypeSplitter.setString(subtypeHashCodesStr);
+ while (imeSubtypeSplitter.hasNext()) {
+ subtypes.add(Integer.parseInt(imeSubtypeSplitter.next()));
+ }
+ return subtypes;
+ }
+
+ @Test
+ public void updateEnabledImeStringTest() {
+ // No change cases
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1", "com.android/.ime1", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1", "com.android/.ime2", "");
+
+ // To enable subtypes
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1", "com.android/.ime2", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1;1",
+ "com.android/.ime1", "com.android/.ime1", "1");
+
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1;1;2;3",
+ "com.android/.ime1", "com.android/.ime1", "1;2;3");
+
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1;1;2;3:com.android/.ime2",
+ "com.android/.ime1:com.android/.ime2", "com.android/.ime1", "1;2;3");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime0:com.android/.ime1;1;2;3",
+ "com.android/.ime0:com.android/.ime1", "com.android/.ime1", "1;2;3");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2",
+ "com.android/.ime0:com.android/.ime1:com.android/.ime2", "com.android/.ime1",
+ "1;2;3");
+
+ // To reset enabled subtypes
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1;1", "com.android/.ime1", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1",
+ "com.android/.ime1;1;2;3", "com.android/.ime1", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime1:com.android/.ime2",
+ "com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1", "");
+
+ verifyUpdateEnabledImeString(
+ "com.android/.ime0:com.android/.ime1",
+ "com.android/.ime0:com.android/.ime1;1;2;3", "com.android/.ime1", "");
+ verifyUpdateEnabledImeString(
+ "com.android/.ime0:com.android/.ime1:com.android/.ime2",
+ "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1",
+ "");
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 95a9610b15d7..9688ef6cc83b 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -34,9 +34,7 @@ import android.content.res.Configuration;
import android.os.Build;
import android.os.LocaleList;
import android.os.Parcel;
-import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.IntArray;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -1211,28 +1209,6 @@ public class InputMethodUtilsTest {
StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR));
}
- private static IntArray createSubtypeHashCodeArrayFromStr(String subtypeHashCodesStr) {
- final IntArray subtypes = new IntArray();
- final TextUtils.SimpleStringSplitter imeSubtypeSplitter =
- new TextUtils.SimpleStringSplitter(';');
- if (TextUtils.isEmpty(subtypeHashCodesStr)) {
- return subtypes;
- }
- imeSubtypeSplitter.setString(subtypeHashCodesStr);
- while (imeSubtypeSplitter.hasNext()) {
- subtypes.add(Integer.parseInt(imeSubtypeSplitter.next()));
- }
- return subtypes;
- }
-
- private static void verifyUpdateEnabledImeString(@NonNull String expectedEnabledImeStr,
- @NonNull String initialEnabledImeStr, @NonNull String imeId,
- @NonNull String enabledSubtypeHashCodesStr) {
- assertEquals(expectedEnabledImeStr,
- InputMethodUtils.InputMethodSettings.updateEnabledImeString(initialEnabledImeStr,
- imeId, createSubtypeHashCodeArrayFromStr(enabledSubtypeHashCodesStr)));
- }
-
private static void verifySplitEnabledImeStr(@NonNull String enabledImeStr,
@NonNull String... expected) {
final ArrayList<String> actual = new ArrayList<>();
@@ -1280,57 +1256,4 @@ public class InputMethodUtilsTest {
"com.android/.ime1:com.android/.ime2", "com.android/.ime3"))
.isEqualTo("com.android/.ime1:com.android/.ime2:com.android/.ime3");
}
-
- @Test
- public void updateEnabledImeStringTest() {
- // No change cases
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1", "com.android/.ime1", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1", "com.android/.ime2", "");
-
- // To enable subtypes
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1", "com.android/.ime2", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime1;1",
- "com.android/.ime1", "com.android/.ime1", "1");
-
- verifyUpdateEnabledImeString(
- "com.android/.ime1;1;2;3",
- "com.android/.ime1", "com.android/.ime1", "1;2;3");
-
- verifyUpdateEnabledImeString(
- "com.android/.ime1;1;2;3:com.android/.ime2",
- "com.android/.ime1:com.android/.ime2", "com.android/.ime1", "1;2;3");
- verifyUpdateEnabledImeString(
- "com.android/.ime0:com.android/.ime1;1;2;3",
- "com.android/.ime0:com.android/.ime1", "com.android/.ime1", "1;2;3");
- verifyUpdateEnabledImeString(
- "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2",
- "com.android/.ime0:com.android/.ime1:com.android/.ime2", "com.android/.ime1",
- "1;2;3");
-
- // To reset enabled subtypes
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1;1", "com.android/.ime1", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime1",
- "com.android/.ime1;1;2;3", "com.android/.ime1", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime1:com.android/.ime2",
- "com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1", "");
-
- verifyUpdateEnabledImeString(
- "com.android/.ime0:com.android/.ime1",
- "com.android/.ime0:com.android/.ime1;1;2;3", "com.android/.ime1", "");
- verifyUpdateEnabledImeString(
- "com.android/.ime0:com.android/.ime1:com.android/.ime2",
- "com.android/.ime0:com.android/.ime1;1;2;3:com.android/.ime2", "com.android/.ime1",
- "");
- }
}