Merge "Introduce a default Voice IME concept." into sc-dev
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user