Preserve enabled subtypes across disabling and re-enabling an IME

Bug: 16464156
Change-Id: I2ea4d010e47e5f48b2df11775b0c262330b28e89
diff --git a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
index 878c3c1..1601cd0 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndLanguageSettings.java
@@ -22,6 +22,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.SharedPreferences;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
@@ -38,6 +39,7 @@
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceCategory;
+import android.preference.PreferenceManager;
 import android.preference.PreferenceScreen;
 import android.provider.Settings;
 import android.provider.Settings.System;
@@ -67,6 +69,8 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.TreeSet;
 
@@ -80,6 +84,7 @@
     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_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
+    private static final String KEY_PREVIOUSLY_ENABLED_SUBTYPES = "previously_enabled_subtypes";
     // false: on ICS or later
     private static final boolean SHOW_INPUT_METHOD_SWITCHER_SETTINGS = false;
 
@@ -471,17 +476,72 @@
 
     @Override
     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
                 == Configuration.KEYBOARD_QWERTY;
         InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
                 mImm.getInputMethodList(), hasHardwareKeyboard);
         // Update input method settings and preference list.
         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) {
             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() {
         final Context context = getActivity();
         if (context == null || mImm == null) return;
diff --git a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
index 3f37db7..b184066 100644
--- a/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/src/com/android/settings/inputmethod/InputMethodAndSubtypeUtil.java
@@ -52,40 +52,33 @@
     private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
 
-    private static void buildEnabledInputMethodsString(StringBuilder builder, String imi,
-            HashSet<String> subtypes) {
-        builder.append(imi);
-        // Inputmethod and subtypes are saved in the settings as follows:
-        // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
-        for (String subtypeId: subtypes) {
-            builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
+    // InputMethods and subtypes are saved in the settings as follows:
+    // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
+    static String buildInputMethodsAndSubtypesString(
+            final HashMap<String, HashSet<String>> imeToSubtypesMap) {
+        final StringBuilder builder = new StringBuilder();
+        for (final String imi : imeToSubtypesMap.keySet()) {
+            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,
-            HashMap<String, HashSet<String>> imsList) {
-        boolean needsAppendSeparator = false;
-        for (String imi: imsList.keySet()) {
-            if (needsAppendSeparator) {
+    private static String buildInputMethodsString(final HashSet<String> imiList) {
+        final StringBuilder builder = new StringBuilder();
+        for (final String imi : imiList) {
+            if (builder.length() > 0) {
                 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) {
@@ -110,29 +103,44 @@
             ContentResolver resolver) {
         final String enabledInputMethodsStr = Settings.Secure.getString(
                 resolver, Settings.Secure.ENABLED_INPUT_METHODS);
-        HashMap<String, HashSet<String>> imsList = new HashMap<>();
         if (DEBUG) {
             Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
         }
+        return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
+    }
 
-        if (TextUtils.isEmpty(enabledInputMethodsStr)) {
-            return imsList;
+    static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
+            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()) {
-            String nextImsStr = sStringInputMethodSplitter.next();
+            final String nextImsStr = sStringInputMethodSplitter.next();
             sStringInputMethodSubtypeSplitter.setString(nextImsStr);
             if (sStringInputMethodSubtypeSplitter.hasNext()) {
-                HashSet<String> subtypeHashes = new HashSet<>();
-                // The first element is ime id.
-                String imeId = sStringInputMethodSubtypeSplitter.next();
+                final HashSet<String> subtypeIdSet = new HashSet<>();
+                // The first element is {@link InputMethodInfoId}.
+                final String imiId = sStringInputMethodSubtypeSplitter.next();
                 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) {
@@ -155,40 +163,44 @@
         String currentInputMethodId = Settings.Secure.getString(resolver,
                 Settings.Secure.DEFAULT_INPUT_METHOD);
         final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
-        HashMap<String, HashSet<String>> enabledIMEAndSubtypesMap =
+        final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
                 getEnabledInputMethodsAndSubtypeList(resolver);
-        HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
+        final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
 
         boolean needsToResetSelectedSubtype = false;
-        for (InputMethodInfo imi : inputMethodInfos) {
+        for (final InputMethodInfo imi : inputMethodInfos) {
             final String imiId = imi.getId();
-            Preference pref = context.findPreference(imiId);
-            if (pref == null) continue;
+            final Preference pref = context.findPreference(imiId);
+            if (pref == null) {
+                continue;
+            }
             // In the choose input method screen or in the subtype enabler screen,
             // <code>pref</code> is an instance of TwoStatePreference.
             final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
                     ((TwoStatePreference) pref).isChecked()
-                    : enabledIMEAndSubtypesMap.containsKey(imiId);
+                    : enabledIMEsAndSubtypesMap.containsKey(imiId);
             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
             final boolean systemIme = InputMethodUtils.isSystemIme(imi);
             if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
                     context.getActivity()).isAlwaysCheckedIme(imi, context.getActivity()))
                     || isImeChecked) {
-                if (!enabledIMEAndSubtypesMap.containsKey(imiId)) {
+                if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
                     // 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;
                 final int subtypeCount = imi.getSubtypeCount();
                 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 TwoStatePreference subtypePref = (TwoStatePreference) context
                             .findPreference(imiId + subtypeHashCodeStr);
                     // In the Configure input method screen which does not have subtype preferences.
-                    if (subtypePref == null) continue;
+                    if (subtypePref == null) {
+                        continue;
+                    }
                     if (!subtypePrefFound) {
                         // Once subtype preference is found, subtypeSet needs to be cleared.
                         // Because of system change, hashCode value could have been changed.
@@ -211,7 +223,7 @@
                     }
                 }
             } else {
-                enabledIMEAndSubtypesMap.remove(imiId);
+                enabledIMEsAndSubtypesMap.remove(imiId);
                 if (isCurrentInputMethod) {
                     // We are processing the current input method, but found that it's not enabled.
                     // This means that the current input method has been uninstalled.
@@ -238,14 +250,13 @@
             }
         }
 
-        StringBuilder builder = new StringBuilder();
-        buildInputMethodsAndSubtypesString(builder, enabledIMEAndSubtypesMap);
-        StringBuilder disabledSysImesBuilder = new StringBuilder();
-        buildDisabledSystemInputMethods(disabledSysImesBuilder, disabledSystemIMEs);
+        final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
+                enabledIMEsAndSubtypesMap);
+        final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
         if (DEBUG) {
-            Log.d(TAG, "--- Save enabled inputmethod settings. :" + builder.toString());
-            Log.d(TAG, "--- Save disable system inputmethod settings. :"
-                    + disabledSysImesBuilder.toString());
+            Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
+            Log.d(TAG, "--- Save disabled system inputmethod settings. :"
+                    + disabledSystemIMEsString);
             Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
             Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
             Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
@@ -262,10 +273,10 @@
         }
 
         Settings.Secure.putString(resolver,
-                Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
-        if (disabledSysImesBuilder.length() > 0) {
+                Settings.Secure.ENABLED_INPUT_METHODS, enabledIMEsAndSubtypesString);
+        if (disabledSystemIMEsString.length() > 0) {
             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
         // IME from the history and the system locale.
diff --git a/src/com/android/settings/inputmethod/InputMethodPreference.java b/src/com/android/settings/inputmethod/InputMethodPreference.java
index a65e5e5..111f79b 100755
--- a/src/com/android/settings/inputmethod/InputMethodPreference.java
+++ b/src/com/android/settings/inputmethod/InputMethodPreference.java
@@ -112,6 +112,10 @@
         setOnPreferenceChangeListener(this);
     }
 
+    public InputMethodInfo getInputMethodInfo() {
+        return mImi;
+    }
+
     private boolean isImeEnabler() {
         // If this {@link SwitchPreference} doesn't have a widget layout, we explicitly hide the
         // switch widget at constructor.