Preserve enabled subtypes across disabling and re-enabling an IME

Bug: 16464156
Change-Id: I2ea4d010e47e5f48b2df11775b0c262330b28e89
This commit is contained in:
Tadashi G. Takaoka
2014-07-21 17:58:47 -07:00
parent 935f97cd9b
commit 9a8b4f239f
3 changed files with 136 additions and 61 deletions

View File

@@ -22,6 +22,7 @@ import android.content.ComponentName;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo; import android.content.pm.ServiceInfo;
@@ -38,6 +39,7 @@ import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener; import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.provider.Settings; import android.provider.Settings;
import android.provider.Settings.System; import android.provider.Settings.System;
@@ -67,6 +69,8 @@ import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.TreeSet; import java.util.TreeSet;
@@ -80,6 +84,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method"; private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method";
private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector"; private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector";
private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings"; private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
private static final String KEY_PREVIOUSLY_ENABLED_SUBTYPES = "previously_enabled_subtypes";
// false: on ICS or later // false: on ICS or later
private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false; private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false;
@@ -471,17 +476,72 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
@Override @Override
public void onSaveInputMethodPreference(final InputMethodPreference pref) { public void onSaveInputMethodPreference(final InputMethodPreference pref) {
final InputMethodInfo imi = pref.getInputMethodInfo();
if (!pref.isChecked()) {
// An IME is being disabled. Save enabled subtypes of the IME to shared preference to be
// able to re-enable these subtypes when the IME gets re-enabled.
saveEnabledSubtypesOf(imi);
}
final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard
== Configuration.KEYBOARD_QWERTY; == Configuration.KEYBOARD_QWERTY;
InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(), InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
mImm.getInputMethodList(), hasHardwareKeyboard); mImm.getInputMethodList(), hasHardwareKeyboard);
// Update input method settings and preference list. // Update input method settings and preference list.
mInputMethodSettingValues.refreshAllInputMethodAndSubtypes(); mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
if (pref.isChecked()) {
// An IME is being enabled. Load the previously enabled subtypes from shared preference
// and enable these subtypes.
restorePreviouslyEnabledSubtypesOf(imi);
}
for (final InputMethodPreference p : mInputMethodPreferenceList) { for (final InputMethodPreference p : mInputMethodPreferenceList) {
p.updatePreferenceViews(); p.updatePreferenceViews();
} }
} }
private void saveEnabledSubtypesOf(final InputMethodInfo imi) {
final HashSet<String> enabledSubtypeIdSet = new HashSet<>();
final List<InputMethodSubtype> enabledSubtypes = mImm.getEnabledInputMethodSubtypeList(
imi, true /* allowsImplicitlySelectedSubtypes */);
for (final InputMethodSubtype subtype : enabledSubtypes) {
final String subtypeId = Integer.toString(subtype.hashCode());
enabledSubtypeIdSet.add(subtypeId);
}
final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
loadPreviouslyEnabledSubtypeIdsMap();
final String imiId = imi.getId();
imeToEnabledSubtypeIdsMap.put(imiId, enabledSubtypeIdSet);
savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
}
private void restorePreviouslyEnabledSubtypesOf(final InputMethodInfo imi) {
final HashMap<String, HashSet<String>> imeToEnabledSubtypeIdsMap =
loadPreviouslyEnabledSubtypeIdsMap();
final String imiId = imi.getId();
final HashSet<String> enabledSubtypeIdSet = imeToEnabledSubtypeIdsMap.remove(imiId);
if (enabledSubtypeIdSet == null) {
return;
}
savePreviouslyEnabledSubtypeIdsMap(imeToEnabledSubtypeIdsMap);
InputMethodAndSubtypeUtil.enableInputMethodSubtypesOf(
getContentResolver(), imiId, enabledSubtypeIdSet);
}
private HashMap<String, HashSet<String>> loadPreviouslyEnabledSubtypeIdsMap() {
final Context context = getActivity();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final String imesAndSubtypesString = prefs.getString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, null);
return InputMethodAndSubtypeUtil.parseInputMethodsAndSubtypesString(imesAndSubtypesString);
}
private void savePreviouslyEnabledSubtypeIdsMap(
final HashMap<String, HashSet<String>> subtypesMap) {
final Context context = getActivity();
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
final String imesAndSubtypesString = InputMethodAndSubtypeUtil
.buildInputMethodsAndSubtypesString(subtypesMap);
prefs.edit().putString(KEY_PREVIOUSLY_ENABLED_SUBTYPES, imesAndSubtypesString).apply();
}
private void updateCurrentImeName() { private void updateCurrentImeName() {
final Context context = getActivity(); final Context context = getActivity();
if (context == null || mImm == null) return; if (context == null || mImm == null) return;

View File

@@ -52,40 +52,33 @@ class InputMethodAndSubtypeUtil {
private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
= new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER); = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
private static void buildEnabledInputMethodsString(StringBuilder builder, String imi, // InputMethods and subtypes are saved in the settings as follows:
HashSet<String> subtypes) { // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
builder.append(imi); static String buildInputMethodsAndSubtypesString(
// Inputmethod and subtypes are saved in the settings as follows: final HashMap<String, HashSet<String>> imeToSubtypesMap) {
// ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 final StringBuilder builder = new StringBuilder();
for (String subtypeId: subtypes) { for (final String imi : imeToSubtypesMap.keySet()) {
builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); if (builder.length() > 0) {
builder.append(INPUT_METHOD_SEPARATER);
}
final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
builder.append(imi);
for (final String subtypeId : subtypeIdSet) {
builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
}
} }
return builder.toString();
} }
private static void buildInputMethodsAndSubtypesString(StringBuilder builder, private static String buildInputMethodsString(final HashSet<String> imiList) {
HashMap<String, HashSet<String>> imsList) { final StringBuilder builder = new StringBuilder();
boolean needsAppendSeparator = false; for (final String imi : imiList) {
for (String imi: imsList.keySet()) { if (builder.length() > 0) {
if (needsAppendSeparator) {
builder.append(INPUT_METHOD_SEPARATER); builder.append(INPUT_METHOD_SEPARATER);
} else {
needsAppendSeparator = true;
} }
buildEnabledInputMethodsString(builder, imi, imsList.get(imi)); builder.append(imi);
}
}
private static void buildDisabledSystemInputMethods(StringBuilder builder,
HashSet<String> imes) {
boolean needsAppendSeparator = false;
for (String ime: imes) {
if (needsAppendSeparator) {
builder.append(INPUT_METHOD_SEPARATER);
} else {
needsAppendSeparator = true;
}
builder.append(ime);
} }
return builder.toString();
} }
private static int getInputMethodSubtypeSelected(ContentResolver resolver) { private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
@@ -110,29 +103,44 @@ class InputMethodAndSubtypeUtil {
ContentResolver resolver) { ContentResolver resolver) {
final String enabledInputMethodsStr = Settings.Secure.getString( final String enabledInputMethodsStr = Settings.Secure.getString(
resolver, Settings.Secure.ENABLED_INPUT_METHODS); resolver, Settings.Secure.ENABLED_INPUT_METHODS);
HashMap<String, HashSet<String>> imsList = new HashMap<>();
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr); Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
} }
return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
}
if (TextUtils.isEmpty(enabledInputMethodsStr)) { static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
return imsList; final String inputMethodsAndSubtypesString) {
final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
return subtypesMap;
} }
sStringInputMethodSplitter.setString(enabledInputMethodsStr); sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
while (sStringInputMethodSplitter.hasNext()) { while (sStringInputMethodSplitter.hasNext()) {
String nextImsStr = sStringInputMethodSplitter.next(); final String nextImsStr = sStringInputMethodSplitter.next();
sStringInputMethodSubtypeSplitter.setString(nextImsStr); sStringInputMethodSubtypeSplitter.setString(nextImsStr);
if (sStringInputMethodSubtypeSplitter.hasNext()) { if (sStringInputMethodSubtypeSplitter.hasNext()) {
HashSet<String> subtypeHashes = new HashSet<>(); final HashSet<String> subtypeIdSet = new HashSet<>();
// The first element is ime id. // The first element is {@link InputMethodInfoId}.
String imeId = sStringInputMethodSubtypeSplitter.next(); final String imiId = sStringInputMethodSubtypeSplitter.next();
while (sStringInputMethodSubtypeSplitter.hasNext()) { while (sStringInputMethodSubtypeSplitter.hasNext()) {
subtypeHashes.add(sStringInputMethodSubtypeSplitter.next()); subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
} }
imsList.put(imeId, subtypeHashes); subtypesMap.put(imiId, subtypeIdSet);
} }
} }
return imsList; return subtypesMap;
}
static void enableInputMethodSubtypesOf(final ContentResolver resolver, final String imiId,
final HashSet<String> enabledSubtypeIdSet) {
final HashMap<String, HashSet<String>> enabledImeAndSubtypeIdsMap =
getEnabledInputMethodsAndSubtypeList(resolver);
enabledImeAndSubtypeIdsMap.put(imiId, enabledSubtypeIdSet);
final String enabledImesAndSubtypesString = buildInputMethodsAndSubtypesString(
enabledImeAndSubtypeIdsMap);
Settings.Secure.putString(resolver,
Settings.Secure.ENABLED_INPUT_METHODS, enabledImesAndSubtypesString);
} }
private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) { private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
@@ -155,40 +163,44 @@ class InputMethodAndSubtypeUtil {
String currentInputMethodId = Settings.Secure.getString(resolver, String currentInputMethodId = Settings.Secure.getString(resolver,
Settings.Secure.DEFAULT_INPUT_METHOD); Settings.Secure.DEFAULT_INPUT_METHOD);
final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver); final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
HashMap<String, HashSet<String>> enabledIMEAndSubtypesMap = final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
getEnabledInputMethodsAndSubtypeList(resolver); getEnabledInputMethodsAndSubtypeList(resolver);
HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver); final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
boolean needsToResetSelectedSubtype = false; boolean needsToResetSelectedSubtype = false;
for (InputMethodInfo imi : inputMethodInfos) { for (final InputMethodInfo imi : inputMethodInfos) {
final String imiId = imi.getId(); final String imiId = imi.getId();
Preference pref = context.findPreference(imiId); final Preference pref = context.findPreference(imiId);
if (pref == null) continue; if (pref == null) {
continue;
}
// In the choose input method screen or in the subtype enabler screen, // In the choose input method screen or in the subtype enabler screen,
// <code>pref</code> is an instance of TwoStatePreference. // <code>pref</code> is an instance of TwoStatePreference.
final boolean isImeChecked = (pref instanceof TwoStatePreference) ? final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
((TwoStatePreference) pref).isChecked() ((TwoStatePreference) pref).isChecked()
: enabledIMEAndSubtypesMap.containsKey(imiId); : enabledIMEsAndSubtypesMap.containsKey(imiId);
final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId); final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
final boolean systemIme = InputMethodUtils.isSystemIme(imi); final boolean systemIme = InputMethodUtils.isSystemIme(imi);
if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance( if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
context.getActivity()).isAlwaysCheckedIme(imi, context.getActivity())) context.getActivity()).isAlwaysCheckedIme(imi, context.getActivity()))
|| isImeChecked) { || isImeChecked) {
if (!enabledIMEAndSubtypesMap.containsKey(imiId)) { if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
// imiId has just been enabled // imiId has just been enabled
enabledIMEAndSubtypesMap.put(imiId, new HashSet<String>()); enabledIMEsAndSubtypesMap.put(imiId, new HashSet<String>());
} }
HashSet<String> subtypesSet = enabledIMEAndSubtypesMap.get(imiId); final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
boolean subtypePrefFound = false; boolean subtypePrefFound = false;
final int subtypeCount = imi.getSubtypeCount(); final int subtypeCount = imi.getSubtypeCount();
for (int i = 0; i < subtypeCount; ++i) { for (int i = 0; i < subtypeCount; ++i) {
InputMethodSubtype subtype = imi.getSubtypeAt(i); final InputMethodSubtype subtype = imi.getSubtypeAt(i);
final String subtypeHashCodeStr = String.valueOf(subtype.hashCode()); final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
final TwoStatePreference subtypePref = (TwoStatePreference) context final TwoStatePreference subtypePref = (TwoStatePreference) context
.findPreference(imiId + subtypeHashCodeStr); .findPreference(imiId + subtypeHashCodeStr);
// In the Configure input method screen which does not have subtype preferences. // In the Configure input method screen which does not have subtype preferences.
if (subtypePref == null) continue; if (subtypePref == null) {
continue;
}
if (!subtypePrefFound) { if (!subtypePrefFound) {
// Once subtype preference is found, subtypeSet needs to be cleared. // Once subtype preference is found, subtypeSet needs to be cleared.
// Because of system change, hashCode value could have been changed. // Because of system change, hashCode value could have been changed.
@@ -211,7 +223,7 @@ class InputMethodAndSubtypeUtil {
} }
} }
} else { } else {
enabledIMEAndSubtypesMap.remove(imiId); enabledIMEsAndSubtypesMap.remove(imiId);
if (isCurrentInputMethod) { if (isCurrentInputMethod) {
// We are processing the current input method, but found that it's not enabled. // We are processing the current input method, but found that it's not enabled.
// This means that the current input method has been uninstalled. // This means that the current input method has been uninstalled.
@@ -238,14 +250,13 @@ class InputMethodAndSubtypeUtil {
} }
} }
StringBuilder builder = new StringBuilder(); final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
buildInputMethodsAndSubtypesString(builder, enabledIMEAndSubtypesMap); enabledIMEsAndSubtypesMap);
StringBuilder disabledSysImesBuilder = new StringBuilder(); final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
buildDisabledSystemInputMethods(disabledSysImesBuilder, disabledSystemIMEs);
if (DEBUG) { if (DEBUG) {
Log.d(TAG, "--- Save enabled inputmethod settings. :" + builder.toString()); Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
Log.d(TAG, "--- Save disable system inputmethod settings. :" Log.d(TAG, "--- Save disabled system inputmethod settings. :"
+ disabledSysImesBuilder.toString()); + disabledSystemIMEsString);
Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId); Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype); Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver)); Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
@@ -262,10 +273,10 @@ class InputMethodAndSubtypeUtil {
} }
Settings.Secure.putString(resolver, Settings.Secure.putString(resolver,
Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); Settings.Secure.ENABLED_INPUT_METHODS, enabledIMEsAndSubtypesString);
if (disabledSysImesBuilder.length() > 0) { if (disabledSystemIMEsString.length() > 0) {
Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
disabledSysImesBuilder.toString()); disabledSystemIMEsString);
} }
// If the current input method is unset, InputMethodManagerService will find the applicable // If the current input method is unset, InputMethodManagerService will find the applicable
// IME from the history and the system locale. // IME from the history and the system locale.

View File

@@ -112,6 +112,10 @@ class InputMethodPreference extends SwitchPreference implements OnPreferenceClic
setOnPreferenceChangeListener(this); setOnPreferenceChangeListener(this);
} }
public InputMethodInfo getInputMethodInfo() {
return mImi;
}
private boolean isImeEnabler() { private boolean isImeEnabler() {
// If this {@link SwitchPreference} doesn't have a widget layout, we explicitly hide the // If this {@link SwitchPreference} doesn't have a widget layout, we explicitly hide the
// switch widget at constructor. // switch widget at constructor.