Merge "Introduce a default Voice IME concept." into sc-dev

This commit is contained in:
Ahaan Ugale
2021-03-25 02:59:41 +00:00
committed by Android (Google) Code Review
6 changed files with 227 additions and 0 deletions

View File

@@ -6290,6 +6290,20 @@ public final class Settings {
public static final String SELECTED_INPUT_METHOD_SUBTYPE =
"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.

View File

@@ -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;

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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));
@@ -865,6 +959,25 @@ public class InputMethodUtilsTest {
assertEquals(expected.hashCode(), actual.hashCode());
}
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) {