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