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 com.android.internal.inputmethod.InputMethodUtils;
     20 import com.android.settings.SettingsPreferenceFragment;
     21 
     22 import android.content.ContentResolver;
     23 import android.preference.CheckBoxPreference;
     24 import android.preference.Preference;
     25 import android.preference.PreferenceScreen;
     26 import android.provider.Settings;
     27 import android.provider.Settings.SettingNotFoundException;
     28 import android.text.TextUtils;
     29 import android.util.Log;
     30 import android.view.inputmethod.InputMethodInfo;
     31 import android.view.inputmethod.InputMethodSubtype;
     32 
     33 import java.util.HashMap;
     34 import java.util.HashSet;
     35 import java.util.List;
     36 import java.util.Map;
     37 
     38 public class InputMethodAndSubtypeUtil {
     39 
     40     private static final boolean DEBUG = false;
     41     static final String TAG = "InputMethdAndSubtypeUtil";
     42 
     43     private static final char INPUT_METHOD_SEPARATER = ':';
     44     private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
     45     private static final int NOT_A_SUBTYPE_ID = -1;
     46 
     47     private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter
     48             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
     49 
     50     private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
     51             = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
     52 
     53     private static void buildEnabledInputMethodsString(
     54             StringBuilder builder, String imi, HashSet<String> subtypes) {
     55         builder.append(imi);
     56         // Inputmethod and subtypes are saved in the settings as follows:
     57         // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
     58         for (String subtypeId: subtypes) {
     59             builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
     60         }
     61     }
     62 
     63     public static void buildInputMethodsAndSubtypesString(
     64             StringBuilder builder, HashMap<String, HashSet<String>> imsList) {
     65         boolean needsAppendSeparator = false;
     66         for (String imi: imsList.keySet()) {
     67             if (needsAppendSeparator) {
     68                 builder.append(INPUT_METHOD_SEPARATER);
     69             } else {
     70                 needsAppendSeparator = true;
     71             }
     72             buildEnabledInputMethodsString(builder, imi, imsList.get(imi));
     73         }
     74     }
     75 
     76     public static void buildDisabledSystemInputMethods(
     77             StringBuilder builder, HashSet<String> imes) {
     78         boolean needsAppendSeparator = false;
     79         for (String ime: imes) {
     80             if (needsAppendSeparator) {
     81                 builder.append(INPUT_METHOD_SEPARATER);
     82             } else {
     83                 needsAppendSeparator = true;
     84             }
     85             builder.append(ime);
     86         }
     87     }
     88 
     89     private static int getInputMethodSubtypeSelected(ContentResolver resolver) {
     90         try {
     91             return Settings.Secure.getInt(resolver,
     92                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
     93         } catch (SettingNotFoundException e) {
     94             return NOT_A_SUBTYPE_ID;
     95         }
     96     }
     97 
     98     private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) {
     99         return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID;
    100     }
    101 
    102     private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) {
    103         Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode);
    104     }
    105 
    106     // Needs to modify InputMethodManageService if you want to change the format of saved string.
    107     private static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
    108             ContentResolver resolver) {
    109         final String enabledInputMethodsStr = Settings.Secure.getString(
    110                 resolver, Settings.Secure.ENABLED_INPUT_METHODS);
    111         HashMap<String, HashSet<String>> imsList
    112                 = new HashMap<String, HashSet<String>>();
    113         if (DEBUG) {
    114             Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
    115         }
    116 
    117         if (TextUtils.isEmpty(enabledInputMethodsStr)) {
    118             return imsList;
    119         }
    120         sStringInputMethodSplitter.setString(enabledInputMethodsStr);
    121         while (sStringInputMethodSplitter.hasNext()) {
    122             String nextImsStr = sStringInputMethodSplitter.next();
    123             sStringInputMethodSubtypeSplitter.setString(nextImsStr);
    124             if (sStringInputMethodSubtypeSplitter.hasNext()) {
    125                 HashSet<String> subtypeHashes = new HashSet<String>();
    126                 // The first element is ime id.
    127                 String imeId = sStringInputMethodSubtypeSplitter.next();
    128                 while (sStringInputMethodSubtypeSplitter.hasNext()) {
    129                     subtypeHashes.add(sStringInputMethodSubtypeSplitter.next());
    130                 }
    131                 imsList.put(imeId, subtypeHashes);
    132             }
    133         }
    134         return imsList;
    135     }
    136 
    137     private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
    138         HashSet<String> set = new HashSet<String>();
    139         String disabledIMEsStr = Settings.Secure.getString(
    140                 resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS);
    141         if (TextUtils.isEmpty(disabledIMEsStr)) {
    142             return set;
    143         }
    144         sStringInputMethodSplitter.setString(disabledIMEsStr);
    145         while(sStringInputMethodSplitter.hasNext()) {
    146             set.add(sStringInputMethodSplitter.next());
    147         }
    148         return set;
    149     }
    150 
    151     public static void saveInputMethodSubtypeList(SettingsPreferenceFragment context,
    152             ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
    153             boolean hasHardKeyboard) {
    154         String currentInputMethodId = Settings.Secure.getString(resolver,
    155                 Settings.Secure.DEFAULT_INPUT_METHOD);
    156         final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
    157         HashMap<String, HashSet<String>> enabledIMEAndSubtypesMap =
    158                 getEnabledInputMethodsAndSubtypeList(resolver);
    159         HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
    160 
    161         final int imiCount = inputMethodInfos.size();
    162         boolean needsToResetSelectedSubtype = false;
    163         for (InputMethodInfo imi : inputMethodInfos) {
    164             final String imiId = imi.getId();
    165             Preference pref = context.findPreference(imiId);
    166             if (pref == null) continue;
    167             // In the Configure input method screen or in the subtype enabler screen.
    168             // pref is instance of CheckBoxPreference in the Configure input method screen.
    169             final boolean isImeChecked = (pref instanceof CheckBoxPreference) ?
    170                     ((CheckBoxPreference) pref).isChecked()
    171                     : enabledIMEAndSubtypesMap.containsKey(imiId);
    172             final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
    173             final boolean systemIme = InputMethodUtils.isSystemIme(imi);
    174             if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
    175                     context.getActivity()).isAlwaysCheckedIme(imi, context.getActivity()))
    176                     || isImeChecked) {
    177                 if (!enabledIMEAndSubtypesMap.containsKey(imiId)) {
    178                     // imiId has just been enabled
    179                     enabledIMEAndSubtypesMap.put(imiId, new HashSet<String>());
    180                 }
    181                 HashSet<String> subtypesSet = enabledIMEAndSubtypesMap.get(imiId);
    182 
    183                 boolean subtypePrefFound = false;
    184                 final int subtypeCount = imi.getSubtypeCount();
    185                 for (int i = 0; i < subtypeCount; ++i) {
    186                     InputMethodSubtype subtype = imi.getSubtypeAt(i);
    187                     final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
    188                     CheckBoxPreference subtypePref = (CheckBoxPreference) context.findPreference(
    189                             imiId + subtypeHashCodeStr);
    190                     // In the Configure input method screen which does not have subtype preferences.
    191                     if (subtypePref == null) continue;
    192                     if (!subtypePrefFound) {
    193                         // Once subtype checkbox is found, subtypeSet needs to be cleared.
    194                         // Because of system change, hashCode value could have been changed.
    195                         subtypesSet.clear();
    196                         // If selected subtype preference is disabled, needs to reset.
    197                         needsToResetSelectedSubtype = true;
    198                         subtypePrefFound = true;
    199                     }
    200                     if (subtypePref.isChecked()) {
    201                         subtypesSet.add(subtypeHashCodeStr);
    202                         if (isCurrentInputMethod) {
    203                             if (selectedInputMethodSubtype == subtype.hashCode()) {
    204                                 // Selected subtype is still enabled, there is no need to reset
    205                                 // selected subtype.
    206                                 needsToResetSelectedSubtype = false;
    207                             }
    208                         }
    209                     } else {
    210                         subtypesSet.remove(subtypeHashCodeStr);
    211                     }
    212                 }
    213             } else {
    214                 enabledIMEAndSubtypesMap.remove(imiId);
    215                 if (isCurrentInputMethod) {
    216                     // We are processing the current input method, but found that it's not enabled.
    217                     // This means that the current input method has been uninstalled.
    218                     // If currentInputMethod is already uninstalled, InputMethodManagerService will
    219                     // find the applicable IME from the history and the system locale.
    220                     if (DEBUG) {
    221                         Log.d(TAG, "Current IME was uninstalled or disabled.");
    222                     }
    223                     currentInputMethodId = null;
    224                 }
    225             }
    226             // If it's a disabled system ime, add it to the disabled list so that it
    227             // doesn't get enabled automatically on any changes to the package list
    228             if (systemIme && hasHardKeyboard) {
    229                 if (disabledSystemIMEs.contains(imiId)) {
    230                     if (isImeChecked) {
    231                         disabledSystemIMEs.remove(imiId);
    232                     }
    233                 } else {
    234                     if (!isImeChecked) {
    235                         disabledSystemIMEs.add(imiId);
    236                     }
    237                 }
    238             }
    239         }
    240 
    241         StringBuilder builder = new StringBuilder();
    242         buildInputMethodsAndSubtypesString(builder, enabledIMEAndSubtypesMap);
    243         StringBuilder disabledSysImesBuilder = new StringBuilder();
    244         buildDisabledSystemInputMethods(disabledSysImesBuilder, disabledSystemIMEs);
    245         if (DEBUG) {
    246             Log.d(TAG, "--- Save enabled inputmethod settings. :" + builder.toString());
    247             Log.d(TAG, "--- Save disable system inputmethod settings. :"
    248                     + disabledSysImesBuilder.toString());
    249             Log.d(TAG, "--- Save default inputmethod settings. :" + currentInputMethodId);
    250             Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype);
    251             Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver));
    252         }
    253 
    254         // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype
    255         // selected. And if the selected subtype of the current input method was disabled,
    256         // We should reset the selected input method's subtype.
    257         if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) {
    258             if (DEBUG) {
    259                 Log.d(TAG, "--- Reset inputmethod subtype because it's not defined.");
    260             }
    261             putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID);
    262         }
    263 
    264         Settings.Secure.putString(resolver,
    265                 Settings.Secure.ENABLED_INPUT_METHODS, builder.toString());
    266         if (disabledSysImesBuilder.length() > 0) {
    267             Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
    268                     disabledSysImesBuilder.toString());
    269         }
    270         // If the current input method is unset, InputMethodManagerService will find the applicable
    271         // IME from the history and the system locale.
    272         Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD,
    273                 currentInputMethodId != null ? currentInputMethodId : "");
    274     }
    275 
    276     public static void loadInputMethodSubtypeList(
    277             SettingsPreferenceFragment context, ContentResolver resolver,
    278             List<InputMethodInfo> inputMethodInfos,
    279             final Map<String, List<Preference>> inputMethodPrefsMap) {
    280         HashMap<String, HashSet<String>> enabledSubtypes =
    281                 getEnabledInputMethodsAndSubtypeList(resolver);
    282 
    283         for (InputMethodInfo imi : inputMethodInfos) {
    284             final String imiId = imi.getId();
    285             Preference pref = context.findPreference(imiId);
    286             if (pref != null && pref instanceof CheckBoxPreference) {
    287                 CheckBoxPreference checkBoxPreference = (CheckBoxPreference) pref;
    288                 boolean isEnabled = enabledSubtypes.containsKey(imiId);
    289                 checkBoxPreference.setChecked(isEnabled);
    290                 if (inputMethodPrefsMap != null) {
    291                     for (Preference childPref: inputMethodPrefsMap.get(imiId)) {
    292                         childPref.setEnabled(isEnabled);
    293                     }
    294                 }
    295                 setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
    296             }
    297         }
    298         updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
    299     }
    300 
    301     public static void setSubtypesPreferenceEnabled(SettingsPreferenceFragment context,
    302             List<InputMethodInfo> inputMethodProperties, String id, boolean enabled) {
    303         PreferenceScreen preferenceScreen = context.getPreferenceScreen();
    304         for (InputMethodInfo imi : inputMethodProperties) {
    305             if (id.equals(imi.getId())) {
    306                 final int subtypeCount = imi.getSubtypeCount();
    307                 for (int i = 0; i < subtypeCount; ++i) {
    308                     InputMethodSubtype subtype = imi.getSubtypeAt(i);
    309                     CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference(
    310                             id + subtype.hashCode());
    311                     if (pref != null) {
    312                         pref.setEnabled(enabled);
    313                     }
    314                 }
    315             }
    316         }
    317     }
    318 
    319     public static void updateSubtypesPreferenceChecked(SettingsPreferenceFragment context,
    320             List<InputMethodInfo> inputMethodProperties,
    321             HashMap<String, HashSet<String>> enabledSubtypes) {
    322         PreferenceScreen preferenceScreen = context.getPreferenceScreen();
    323         for (InputMethodInfo imi : inputMethodProperties) {
    324             String id = imi.getId();
    325             if (!enabledSubtypes.containsKey(id)) break;
    326             final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
    327             final int subtypeCount = imi.getSubtypeCount();
    328             for (int i = 0; i < subtypeCount; ++i) {
    329                 InputMethodSubtype subtype = imi.getSubtypeAt(i);
    330                 String hashCode = String.valueOf(subtype.hashCode());
    331                 if (DEBUG) {
    332                     Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
    333                             + enabledSubtypesSet.contains(hashCode));
    334                 }
    335                 CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference(
    336                         id + hashCode);
    337                 if (pref != null) {
    338                     pref.setChecked(enabledSubtypesSet.contains(hashCode));
    339                 }
    340             }
    341         }
    342     }
    343 }
    344