Home | History | Annotate | Download | only in inputmethod
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.settings.inputmethod;
     18 
     19 import android.content.ContentResolver;
     20 import android.content.SharedPreferences;
     21 import android.preference.Preference;
     22 import android.preference.PreferenceScreen;
     23 import android.preference.TwoStatePreference;
     24 import android.provider.Settings;
     25 import android.provider.Settings.SettingNotFoundException;
     26 import android.text.TextUtils;
     27 import android.util.Log;
     28 import android.view.inputmethod.InputMethodInfo;
     29 import android.view.inputmethod.InputMethodSubtype;
     30 
     31 import com.android.internal.inputmethod.InputMethodUtils;
     32 import com.android.settings.SettingsPreferenceFragment;
     33 
     34 import java.util.HashMap;
     35 import java.util.HashSet;
     36 import java.util.List;
     37 import java.util.Map;
     38 
     39 // TODO: Consolidate this with {@link InputMethodSettingValuesWrapper}.
     40 class InputMethodAndSubtypeUtil {
     41 
     42     private static final boolean DEBUG = false;
     43     static final String TAG = "InputMethdAndSubtypeUtil";
     44 
     45     private static final char INPUT_METHOD_SEPARATER = ':';
     46     private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
     47     private static final int NOT_A_SUBTYPE_ID = -1;
     48 
     49     private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
     50             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
     51 
     52     private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
     53             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
     54 
     55     // InputMethods and subtypes are saved in the settings as follows:
     56     // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
     57     static String buildInputMethodsAndSubtypesString(
     58             final HashMap<String, HashSet<String>> imeToSubtypesMap) {
     59         final StringBuilder builder = new StringBuilder();
     60         for (final String imi : imeToSubtypesMap.keySet()) {
     61             if (builder.length() > 0) {
     62                 builder.append(INPUT_METHOD_SEPARATER);
     63             }
     64             final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
     65             builder.append(imi);
     66             for (final String subtypeId : subtypeIdSet) {
     67                 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
     68             }
     69         }
     70         return builder.toString();
     71     }
     72 
     73     private static String buildInputMethodsString(final HashSet<String> imiList) {
     74         final StringBuilder builder = new StringBuilder();
     75         for (final String imi : imiList) {
     76             if (builder.length() > 0) {
     77                 builder.append(INPUT_METHOD_SEPARATER);
     78             }
     79             builder.append(imi);
     80         }
     81         return builder.toString();
     82     }
     83 
     84     private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
     85         try {
     86             return Settings.Secure.getInt(resolver,
     87                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
     88         } catch (SettingNotFoundException e) {
     89             return NOT_A_SUBTYPE_ID;
     90         }
     91     }
     92 
     93     private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
     94         return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
     95     }
     96 
     97     private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
     98         Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
     99     }
    100 
    101     // Needs to modify InputMethodManageService if you want to change the format of saved string.
    102     private static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
    103             ContentResolver resolver) {
    104         final String enabledInputMethodsStr = Settings.Secure.getString(
    105                 resolver, Settings.Secure.ENABLED_INPUT_METHODS);
    106         if (DEBUG) {
    107             Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
    108         }
    109         return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
    110     }
    111 
    112     static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
    113             final String inputMethodsAndSubtypesString) {
    114         final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
    115         if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
    116             return subtypesMap;
    117         }
    118         sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
    119         while (sStringInputMethodSplitter.hasNext()) {
    120             final String nextImsStr = sStringInputMethodSplitter.next();
    121             sStringInputMethodSubtypeSplitter.setString(nextImsStr);
    122             if (sStringInputMethodSubtypeSplitter.hasNext()) {
    123                 final HashSet<String> subtypeIdSet = new HashSet<>();
    124                 // The first element is {@link InputMethodInfoId}.
    125                 final String imiId = sStringInputMethodSubtypeSplitter.next();
    126                 while (sStringInputMethodSubtypeSplitter.hasNext()) {
    127                     subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
    128                 }
    129                 subtypesMap.put(imiId, subtypeIdSet);
    130             }
    131         }
    132         return subtypesMap;
    133     }
    134 
    135     static void enableInputMethodSubtypesOf(final ContentResolver resolver, final String imiId,
    136             final HashSet<String> enabledSubtypeIdSet) {
    137         final HashMap<String, HashSet<String>> enabledImeAndSubtypeIdsMap =
    138                 getEnabledInputMethodsAndSubtypeList(resolver);
    139         enabledImeAndSubtypeIdsMap.put(imiId, enabledSubtypeIdSet);
    140         final String enabledImesAndSubtypesString = buildInputMethodsAndSubtypesString(
    141                 enabledImeAndSubtypeIdsMap);
    142         Settings.Secure.putString(resolver,
    143                 Settings.Secure.ENABLED_INPUT_METHODS, enabledImesAndSubtypesString);
    144     }
    145 
    146     private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
    147         HashSet<String> set = new HashSet<>();
    148         String disabledIMEsStr = Settings.Secure.getString(
    149                 resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
    150         if (TextUtils.isEmpty(disabledIMEsStr)) {
    151             return set;
    152         }
    153         sStringInputMethodSplitter.setString(disabledIMEsStr);
    154         while(sStringInputMethodSplitter.hasNext()) {
    155             set.add(sStringInputMethodSplitter.next());
    156         }
    157         return set;
    158     }
    159 
    160     static void saveInputMethodSubtypeList(SettingsPreferenceFragment context,
    161             ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
    162             boolean hasHardKeyboard) {
    163         String currentInputMethodId = Settings.Secure.getString(resolver,
    164                 Settings.Secure.DEFAULT_INPUT_METHOD);
    165         final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
    166         final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
    167                 getEnabledInputMethodsAndSubtypeList(resolver);
    168         final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
    169 
    170         boolean needsToResetSelectedSubtype = false;
    171         for (final InputMethodInfo imi : inputMethodInfos) {
    172             final String imiId = imi.getId();
    173             final Preference pref = context.findPreference(imiId);
    174             if (pref == null) {
    175                 continue;
    176             }
    177             // In the choose input method screen or in the subtype enabler screen,
    178             // <code>pref</code> is an instance of TwoStatePreference.
    179             final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
    180                     ((TwoStatePreference) pref).isChecked()
    181                     : enabledIMEsAndSubtypesMap.containsKey(imiId);
    182             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
    183             final boolean systemIme = InputMethodUtils.isSystemIme(imi);
    184             if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
    185                     context.getActivity()).isAlwaysCheckedIme(imi, context.getActivity()))
    186                     || isImeChecked) {
    187                 if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
    188                     // imiId has just been enabled
    189                     enabledIMEsAndSubtypesMap.put(imiId, new HashSet<String>());
    190                 }
    191                 final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
    192 
    193                 boolean subtypePrefFound = false;
    194                 final int subtypeCount = imi.getSubtypeCount();
    195                 for (int i = 0; i < subtypeCount; ++i) {
    196                     final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    197                     final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
    198                     final TwoStatePreference subtypePref = (TwoStatePreference) context
    199                             .findPreference(imiId + subtypeHashCodeStr);
    200                     // In the Configure input method screen which does not have subtype preferences.
    201                     if (subtypePref == null) {
    202                         continue;
    203                     }
    204                     if (!subtypePrefFound) {
    205                         // Once subtype preference is found, subtypeSet needs to be cleared.
    206                         // Because of system change, hashCode value could have been changed.
    207                         subtypesSet.clear();
    208                         // If selected subtype preference is disabled, needs to reset.
    209                         needsToResetSelectedSubtype = true;
    210                         subtypePrefFound = true;
    211                     }
    212                     if (subtypePref.isChecked()) {
    213                         subtypesSet.add(subtypeHashCodeStr);
    214                         if (isCurrentInputMethod) {
    215                             if (selectedInputMethodSubtype == subtype.hashCode()) {
    216                                 // Selected subtype is still enabled, there is no need to reset
    217                                 // selected subtype.
    218                                 needsToResetSelectedSubtype = false;
    219                             }
    220                         }
    221                     } else {
    222                         subtypesSet.remove(subtypeHashCodeStr);
    223                     }
    224                 }
    225             } else {
    226                 enabledIMEsAndSubtypesMap.remove(imiId);
    227                 if (isCurrentInputMethod) {
    228                     // We are processing the current input method, but found that it's not enabled.
    229                     // This means that the current input method has been uninstalled.
    230                     // If currentInputMethod is already uninstalled, InputMethodManagerService will
    231                     // find the applicable IME from the history and the system locale.
    232                     if (DEBUG) {
    233                         Log.d(TAG, "Current IME was uninstalled or disabled.");
    234                     }
    235                     currentInputMethodId = null;
    236                 }
    237             }
    238             // If it's a disabled system ime, add it to the disabled list so that it
    239             // doesn't get enabled automatically on any changes to the package list
    240             if (systemIme && hasHardKeyboard) {
    241                 if (disabledSystemIMEs.contains(imiId)) {
    242                     if (isImeChecked) {
    243                         disabledSystemIMEs.remove(imiId);
    244                     }
    245                 } else {
    246                     if (!isImeChecked) {
    247                         disabledSystemIMEs.add(imiId);
    248                     }
    249                 }
    250             }
    251         }
    252 
    253         final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
    254                 enabledIMEsAndSubtypesMap);
    255         final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
    256         if (DEBUG) {
    257             Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
    258             Log.d(TAG, "--- Save disabled system inputmethod settings. :"
    259                     + disabledSystemIMEsString);
    260             Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
    261             Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
    262             Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
    263         }
    264 
    265         // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype
    266         // selected. And if the selected subtype of the current input method was disabled,
    267         // We should reset the selected input method's subtype.
    268         if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) {
    269             if (DEBUG) {
    270                 Log.d(TAG, "--- Reset inputmethod subtype because it's not defined.");
    271             }
    272             putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID);
    273         }
    274 
    275         Settings.Secure.putString(resolver,
    276                 Settings.Secure.ENABLED_INPUT_METHODS, enabledIMEsAndSubtypesString);
    277         if (disabledSystemIMEsString.length() > 0) {
    278             Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
    279                     disabledSystemIMEsString);
    280         }
    281         // If the current input method is unset, InputMethodManagerService will find the applicable
    282         // IME from the history and the system locale.
    283         Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
    284                 currentInputMethodId != null ? currentInputMethodId : "");
    285     }
    286 
    287     static void loadInputMethodSubtypeList(final SettingsPreferenceFragment context,
    288             final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos,
    289             final Map<String, List<Preference>> inputMethodPrefsMap) {
    290         final HashMap<String, HashSet<String>> enabledSubtypes =
    291                 getEnabledInputMethodsAndSubtypeList(resolver);
    292 
    293         for (final InputMethodInfo imi : inputMethodInfos) {
    294             final String imiId = imi.getId();
    295             final Preference pref = context.findPreference(imiId);
    296             if (pref instanceof TwoStatePreference) {
    297                 final TwoStatePreference subtypePref = (TwoStatePreference) pref;
    298                 final boolean isEnabled = enabledSubtypes.containsKey(imiId);
    299                 subtypePref.setChecked(isEnabled);
    300                 if (inputMethodPrefsMap != null) {
    301                     for (final Preference childPref: inputMethodPrefsMap.get(imiId)) {
    302                         childPref.setEnabled(isEnabled);
    303                     }
    304                 }
    305                 setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
    306             }
    307         }
    308         updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
    309     }
    310 
    311     static void setSubtypesPreferenceEnabled(final SettingsPreferenceFragment context,
    312             final List<InputMethodInfo> inputMethodProperties, final String id,
    313             final boolean enabled) {
    314         final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
    315         for (final InputMethodInfo imi : inputMethodProperties) {
    316             if (id.equals(imi.getId())) {
    317                 final int subtypeCount = imi.getSubtypeCount();
    318                 for (int i = 0; i < subtypeCount; ++i) {
    319                     final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    320                     final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
    321                             .findPreference(id + subtype.hashCode());
    322                     if (pref != null) {
    323                         pref.setEnabled(enabled);
    324                     }
    325                 }
    326             }
    327         }
    328     }
    329 
    330     private static void updateSubtypesPreferenceChecked(final SettingsPreferenceFragment context,
    331             final List<InputMethodInfo> inputMethodProperties,
    332             final HashMap<String, HashSet<String>> enabledSubtypes) {
    333         final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
    334         for (final InputMethodInfo imi : inputMethodProperties) {
    335             final String id = imi.getId();
    336             if (!enabledSubtypes.containsKey(id)) {
    337                 // There is no need to enable/disable subtypes of disabled IMEs.
    338                 continue;
    339             }
    340             final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
    341             final int subtypeCount = imi.getSubtypeCount();
    342             for (int i = 0; i < subtypeCount; ++i) {
    343                 final InputMethodSubtype subtype = imi.getSubtypeAt(i);
    344                 final String hashCode = String.valueOf(subtype.hashCode());
    345                 if (DEBUG) {
    346                     Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
    347                             + enabledSubtypesSet.contains(hashCode));
    348                 }
    349                 final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
    350                         .findPreference(id + hashCode);
    351                 if (pref != null) {
    352                     pref.setChecked(enabledSubtypesSet.contains(hashCode));
    353                 }
    354             }
    355         }
    356     }
    357 
    358     static void removeUnnecessaryNonPersistentPreference(final Preference pref) {
    359         final String key = pref.getKey();
    360         if (pref.isPersistent() || key == null) {
    361             return;
    362         }
    363         final SharedPreferences prefs = pref.getSharedPreferences();
    364         if (prefs != null && prefs.contains(key)) {
    365             prefs.edit().remove(key).apply();
    366         }
    367     }
    368 }
    369