summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/provider/Settings.java14
-rw-r--r--core/proto/android/providers/settings/secure.proto1
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java3
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java34
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodUtils.java62
-rw-r--r--services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java113
6 files changed, 227 insertions, 0 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 719c38392fb1..37469af1aa24 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -6291,6 +6291,20 @@ public final class Settings {
"selected_input_method_subtype";
/**
+ * The {@link android.view.inputmethod.InputMethodInfo.InputMethodInfo#getId() ID} of the
+ * default voice input method.
+ * <p>
+ * This stores the last known default voice IME. If the related system config value changes,
+ * this is reset by InputMethodManagerService.
+ * <p>
+ * This IME is not necessarily in the enabled IME list. That state is still stored in
+ * {@link #ENABLED_INPUT_METHODS}.
+ *
+ * @hide
+ */
+ public static final String DEFAULT_VOICE_INPUT_METHOD = "default_voice_input_method";
+
+ /**
* Setting to record the history of input method subtype, holding the pair of ID of IME
* and its last used subtype.
* @hide
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index dca6002d23f8..f0badbefcfeb 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -296,6 +296,7 @@ message SecureSettingsProto {
optional SettingProto subtype_history = 5 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto selected_input_method_subtype = 6 [ (android.privacy).dest = DEST_AUTOMATIC ];
optional SettingProto show_ime_with_hard_keyboard = 7 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ optional SettingProto default_voice_input_method = 8 [ (android.privacy).dest = DEST_AUTOMATIC ];
}
optional InputMethods input_methods = 26;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 7288371899ce..9c67e9c342be 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2150,6 +2150,9 @@ class SettingsProtoDumpUtil {
dumpSetting(s, p,
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
SecureSettingsProto.InputMethods.SHOW_IME_WITH_HARD_KEYBOARD);
+ dumpSetting(s, p,
+ Settings.Secure.DEFAULT_VOICE_INPUT_METHOD,
+ SecureSettingsProto.InputMethods.DEFAULT_VOICE_INPUT_METHOD);
p.end(inputMethodsToken);
dumpSetting(s, p,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c9364c62d8b7..27f8fd3e8f5c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -4830,6 +4830,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
setInputMethodEnabledLocked(defaultImiId, true);
}
}
+
+ updateDefaultVoiceImeIfNeededLocked();
+
// Here is not the perfect place to reset the switching controller. Ideally
// mSwitchingController and mSettings should be able to share the same state.
// TODO: Make sure that mSwitchingController and mSettings are sharing the
@@ -4842,6 +4845,37 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget();
}
+ @GuardedBy("mMethodMap")
+ private void updateDefaultVoiceImeIfNeededLocked() {
+ final String systemSpeechRecognizer =
+ mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer);
+ final String currentDefaultVoiceImeId = mSettings.getDefaultVoiceInputMethod();
+ final InputMethodInfo newSystemVoiceIme = InputMethodUtils.chooseSystemVoiceIme(
+ mMethodMap, systemSpeechRecognizer, currentDefaultVoiceImeId);
+ if (newSystemVoiceIme == null) {
+ if (DEBUG) {
+ Slog.i(TAG, "Found no valid default Voice IME. If the user is still locked,"
+ + " this may be expected.");
+ }
+ // Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings
+ // does not update the actual Secure Settings until the user is unlocked.
+ if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) {
+ mSettings.putDefaultVoiceInputMethod("");
+ // We don't support disabling the voice ime when a package is removed from the
+ // config.
+ }
+ return;
+ }
+ if (TextUtils.equals(currentDefaultVoiceImeId, newSystemVoiceIme.getId())) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.i(TAG, "Enabling the default Voice IME:" + newSystemVoiceIme);
+ }
+ setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true);
+ mSettings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId());
+ }
+
// ----------------------------------------------------------------------
private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 0e908d471f74..ac3c31d5cca4 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -337,6 +337,52 @@ final class InputMethodUtils {
return getDefaultEnabledImes(context, imis, false /* onlyMinimum */);
}
+ /**
+ * Chooses an eligible system voice IME from the given IMEs.
+ *
+ * @param methodMap Map from the IME ID to {@link InputMethodInfo}.
+ * @param systemSpeechRecognizerPackageName System speech recognizer configured by the system
+ * config.
+ * @param currentDefaultVoiceImeId IME ID currently set to
+ * {@link Settings.Secure#DEFAULT_VOICE_INPUT_METHOD}
+ * @return {@link InputMethodInfo} that is found in {@code methodMap} and most suitable for
+ * the system voice IME.
+ */
+ @Nullable
+ static InputMethodInfo chooseSystemVoiceIme(
+ @NonNull ArrayMap<String, InputMethodInfo> methodMap,
+ @Nullable String systemSpeechRecognizerPackageName,
+ @Nullable String currentDefaultVoiceImeId) {
+ if (TextUtils.isEmpty(systemSpeechRecognizerPackageName)) {
+ return null;
+ }
+ final InputMethodInfo defaultVoiceIme = methodMap.get(currentDefaultVoiceImeId);
+ // If the config matches the package of the setting, use the current one.
+ if (defaultVoiceIme != null && defaultVoiceIme.isSystem()
+ && defaultVoiceIme.getPackageName().equals(systemSpeechRecognizerPackageName)) {
+ return defaultVoiceIme;
+ }
+ InputMethodInfo firstMatchingIme = null;
+ final int methodCount = methodMap.size();
+ for (int i = 0; i < methodCount; ++i) {
+ final InputMethodInfo imi = methodMap.valueAt(i);
+ if (!imi.isSystem()) {
+ continue;
+ }
+ if (!TextUtils.equals(imi.getPackageName(), systemSpeechRecognizerPackageName)) {
+ continue;
+ }
+ if (firstMatchingIme != null) {
+ Slog.e(TAG, "At most one InputMethodService can be published in "
+ + "systemSpeechRecognizer: " + systemSpeechRecognizerPackageName
+ + ". Ignoring all of them.");
+ return null;
+ }
+ firstMatchingIme = imi;
+ }
+ return firstMatchingIme;
+ }
+
static boolean containsSubtypeOf(InputMethodInfo imi, @Nullable Locale locale,
boolean checkCountry, String mode) {
if (locale == null) {
@@ -1233,6 +1279,22 @@ final class InputMethodUtils {
return imi;
}
+ 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;
}
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 eebc25aab279..6f1268e5de24 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -23,6 +23,7 @@ import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import android.content.Context;
@@ -34,6 +35,7 @@ import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import android.os.Parcel;
+import android.util.ArrayMap;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
@@ -48,6 +50,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -793,6 +796,97 @@ public class InputMethodUtilsTest {
}
}
+ @Test
+ public void testChooseSystemVoiceIme() throws Exception {
+ final InputMethodInfo systemIme = createFakeInputMethodInfo("SystemIme", "fake.voice0",
+ true /* isSystem */);
+
+ // Returns null when the config value is null.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, null, ""));
+ }
+
+ // Returns null when the config value is empty.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, "", ""));
+ }
+
+ // Returns null when the configured package doesn't have an IME.
+ {
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(),
+ systemIme.getPackageName(), ""));
+ }
+
+ // Returns the right one when the current default is null.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ systemIme.getPackageName(), null));
+ }
+
+ // Returns the right one when the current default is empty.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ systemIme.getPackageName(), ""));
+ }
+
+ // Returns null when the current default isn't found.
+ {
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(new ArrayMap<>(),
+ systemIme.getPackageName(), systemIme.getId()));
+ }
+
+ // Returns null when there are multiple IMEs defined by the config package.
+ {
+ final InputMethodInfo secondIme = createFakeInputMethodInfo(systemIme.getPackageName(),
+ "fake.voice1", true /* isSystem */);
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ methodMap.put(secondIme.getId(), secondIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap, systemIme.getPackageName(),
+ ""));
+ }
+
+ // Returns the current one when the current default and config point to the same package.
+ {
+ final InputMethodInfo secondIme = createFakeInputMethodInfo("SystemIme", "fake.voice1",
+ true /* isSystem */);
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ methodMap.put(systemIme.getId(), systemIme);
+ methodMap.put(secondIme.getId(), secondIme);
+ assertEquals(systemIme, InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ systemIme.getPackageName(), systemIme.getId()));
+ }
+
+ // Doesn't return the current default if it isn't a system app.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final InputMethodInfo nonSystemIme = createFakeInputMethodInfo("NonSystemIme",
+ "fake.voice0", false /* isSystem */);
+ methodMap.put(nonSystemIme.getId(), nonSystemIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ nonSystemIme.getPackageName(), nonSystemIme.getId()));
+ }
+
+ // Returns null if the configured one isn't a system app.
+ {
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final InputMethodInfo nonSystemIme = createFakeInputMethodInfo(
+ "FakeDefaultAutoVoiceIme", "fake.voice0", false /* isSystem */);
+ methodMap.put(systemIme.getId(), systemIme);
+ methodMap.put(nonSystemIme.getId(), nonSystemIme);
+ assertNull(InputMethodUtils.chooseSystemVoiceIme(methodMap,
+ nonSystemIme.getPackageName(), ""));
+ }
+ }
+
private void assertDefaultEnabledImes(final ArrayList<InputMethodInfo> preinstalledImes,
final Locale systemLocale, String... expectedImeNames) {
final Context context = createTargetContextWithLocales(new LocaleList(systemLocale));
@@ -866,6 +960,25 @@ public class InputMethodUtilsTest {
}
private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
+ boolean isSystem) {
+ final ResolveInfo ri = new ResolveInfo();
+ final ServiceInfo si = new ServiceInfo();
+ final ApplicationInfo ai = new ApplicationInfo();
+ ai.packageName = packageName;
+ ai.enabled = true;
+ if (isSystem) {
+ ai.flags |= ApplicationInfo.FLAG_SYSTEM;
+ }
+ si.applicationInfo = ai;
+ si.enabled = true;
+ si.packageName = packageName;
+ si.name = name;
+ si.exported = true;
+ ri.serviceInfo = si;
+ return new InputMethodInfo(ri, false, "", Collections.emptyList(), 1, true);
+ }
+
+ private static InputMethodInfo createFakeInputMethodInfo(String packageName, String name,
CharSequence label, boolean isAuxIme, boolean isDefault,
List<InputMethodSubtype> subtypes) {
final ResolveInfo ri = new ResolveInfo();