| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.inputmethod; |
| |
| import com.android.settings.SettingsPreferenceFragment; |
| |
| import android.content.ContentResolver; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManager; |
| import android.content.res.Resources; |
| import android.preference.CheckBoxPreference; |
| import android.preference.Preference; |
| import android.preference.PreferenceScreen; |
| import android.provider.Settings; |
| import android.provider.Settings.SettingNotFoundException; |
| import android.text.TextUtils; |
| import android.util.Log; |
| import android.view.inputmethod.InputMethodInfo; |
| import android.view.inputmethod.InputMethodManager; |
| import android.view.inputmethod.InputMethodSubtype; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| |
| public class InputMethodAndSubtypeUtil { |
| |
| private static final boolean DEBUG = false; |
| static final String TAG = "InputMethdAndSubtypeUtil"; |
| |
| private static final char INPUT_METHOD_SEPARATER = ':'; |
| private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; |
| private static final int NOT_A_SUBTYPE_ID = -1; |
| private static final Locale ENGLISH_LOCALE = new Locale("en"); |
| |
| private static final TextUtils.SimpleStringSplitter sStringInputMethodSplitter |
| = new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); |
| |
| 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); |
| } |
| } |
| |
| public static void buildInputMethodsAndSubtypesString( |
| StringBuilder builder, HashMap<String, HashSet<String>> imsList) { |
| boolean needsAppendSeparator = false; |
| for (String imi: imsList.keySet()) { |
| if (needsAppendSeparator) { |
| builder.append(INPUT_METHOD_SEPARATER); |
| } else { |
| needsAppendSeparator = true; |
| } |
| buildEnabledInputMethodsString(builder, imi, imsList.get(imi)); |
| } |
| } |
| |
| public 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); |
| } |
| } |
| |
| private static int getInputMethodSubtypeSelected(ContentResolver resolver) { |
| try { |
| return Settings.Secure.getInt(resolver, |
| Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE); |
| } catch (SettingNotFoundException e) { |
| return NOT_A_SUBTYPE_ID; |
| } |
| } |
| |
| private static boolean isInputMethodSubtypeSelected(ContentResolver resolver) { |
| return getInputMethodSubtypeSelected(resolver) != NOT_A_SUBTYPE_ID; |
| } |
| |
| private static void putSelectedInputMethodSubtype(ContentResolver resolver, int hashCode) { |
| Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, hashCode); |
| } |
| |
| // Needs to modify InputMethodManageService if you want to change the format of saved string. |
| private static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList( |
| ContentResolver resolver) { |
| final String enabledInputMethodsStr = Settings.Secure.getString( |
| resolver, Settings.Secure.ENABLED_INPUT_METHODS); |
| HashMap<String, HashSet<String>> imsList |
| = new HashMap<String, HashSet<String>>(); |
| if (DEBUG) { |
| Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr); |
| } |
| |
| if (TextUtils.isEmpty(enabledInputMethodsStr)) { |
| return imsList; |
| } |
| sStringInputMethodSplitter.setString(enabledInputMethodsStr); |
| while (sStringInputMethodSplitter.hasNext()) { |
| String nextImsStr = sStringInputMethodSplitter.next(); |
| sStringInputMethodSubtypeSplitter.setString(nextImsStr); |
| if (sStringInputMethodSubtypeSplitter.hasNext()) { |
| HashSet<String> subtypeHashes = new HashSet<String>(); |
| // The first element is ime id. |
| String imeId = sStringInputMethodSubtypeSplitter.next(); |
| while (sStringInputMethodSubtypeSplitter.hasNext()) { |
| subtypeHashes.add(sStringInputMethodSubtypeSplitter.next()); |
| } |
| imsList.put(imeId, subtypeHashes); |
| } |
| } |
| return imsList; |
| } |
| |
| private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) { |
| HashSet<String> set = new HashSet<String>(); |
| String disabledIMEsStr = Settings.Secure.getString( |
| resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS); |
| if (TextUtils.isEmpty(disabledIMEsStr)) { |
| return set; |
| } |
| sStringInputMethodSplitter.setString(disabledIMEsStr); |
| while(sStringInputMethodSplitter.hasNext()) { |
| set.add(sStringInputMethodSplitter.next()); |
| } |
| return set; |
| } |
| |
| public static CharSequence getCurrentInputMethodName(Context context, ContentResolver resolver, |
| InputMethodManager imm, List<InputMethodInfo> imis, PackageManager pm) { |
| if (resolver == null || imis == null) return null; |
| final String currentInputMethodId = Settings.Secure.getString(resolver, |
| Settings.Secure.DEFAULT_INPUT_METHOD); |
| if (TextUtils.isEmpty(currentInputMethodId)) return null; |
| for (InputMethodInfo imi : imis) { |
| if (currentInputMethodId.equals(imi.getId())) { |
| final InputMethodSubtype subtype = imm.getCurrentInputMethodSubtype(); |
| final CharSequence imiLabel = imi.loadLabel(pm); |
| final CharSequence summary = subtype != null |
| ? TextUtils.concat(subtype.getDisplayName(context, |
| imi.getPackageName(), imi.getServiceInfo().applicationInfo), |
| (TextUtils.isEmpty(imiLabel) ? |
| "" : " - " + imiLabel)) |
| : imiLabel; |
| return summary; |
| } |
| } |
| return null; |
| } |
| |
| public static void saveInputMethodSubtypeList(SettingsPreferenceFragment context, |
| ContentResolver resolver, List<InputMethodInfo> inputMethodInfos, |
| boolean hasHardKeyboard) { |
| String currentInputMethodId = Settings.Secure.getString(resolver, |
| Settings.Secure.DEFAULT_INPUT_METHOD); |
| final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver); |
| HashMap<String, HashSet<String>> enabledIMEAndSubtypesMap = |
| getEnabledInputMethodsAndSubtypeList(resolver); |
| HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver); |
| |
| final int imiCount = inputMethodInfos.size(); |
| boolean needsToResetSelectedSubtype = false; |
| for (InputMethodInfo imi : inputMethodInfos) { |
| final String imiId = imi.getId(); |
| Preference pref = context.findPreference(imiId); |
| if (pref == null) continue; |
| // In the Configure input method screen or in the subtype enabler screen. |
| // pref is instance of CheckBoxPreference in the Configure input method screen. |
| final boolean isImeChecked = (pref instanceof CheckBoxPreference) ? |
| ((CheckBoxPreference) pref).isChecked() |
| : enabledIMEAndSubtypesMap.containsKey(imiId); |
| final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId); |
| final boolean systemIme = isSystemIme(imi); |
| if ((!hasHardKeyboard && isAlwaysCheckedIme(imi, context.getActivity(), imiCount)) |
| || isImeChecked) { |
| if (!enabledIMEAndSubtypesMap.containsKey(imiId)) { |
| // imiId has just been enabled |
| enabledIMEAndSubtypesMap.put(imiId, new HashSet<String>()); |
| } |
| HashSet<String> subtypesSet = enabledIMEAndSubtypesMap.get(imiId); |
| |
| boolean subtypePrefFound = false; |
| final int subtypeCount = imi.getSubtypeCount(); |
| for (int i = 0; i < subtypeCount; ++i) { |
| InputMethodSubtype subtype = imi.getSubtypeAt(i); |
| final String subtypeHashCodeStr = String.valueOf(subtype.hashCode()); |
| CheckBoxPreference subtypePref = (CheckBoxPreference) context.findPreference( |
| imiId + subtypeHashCodeStr); |
| // In the Configure input method screen which does not have subtype preferences. |
| if (subtypePref == null) continue; |
| if (!subtypePrefFound) { |
| // Once subtype checkbox is found, subtypeSet needs to be cleared. |
| // Because of system change, hashCode value could have been changed. |
| subtypesSet.clear(); |
| // If selected subtype preference is disabled, needs to reset. |
| needsToResetSelectedSubtype = true; |
| subtypePrefFound = true; |
| } |
| if (subtypePref.isChecked()) { |
| subtypesSet.add(subtypeHashCodeStr); |
| if (isCurrentInputMethod) { |
| if (selectedInputMethodSubtype == subtype.hashCode()) { |
| // Selected subtype is still enabled, there is no need to reset |
| // selected subtype. |
| needsToResetSelectedSubtype = false; |
| } |
| } |
| } else { |
| subtypesSet.remove(subtypeHashCodeStr); |
| } |
| } |
| } else { |
| enabledIMEAndSubtypesMap.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. |
| // If currentInputMethod is already uninstalled, InputMethodManagerService will |
| // find the applicable IME from the history and the system locale. |
| if (DEBUG) { |
| Log.d(TAG, "Current IME was uninstalled or disabled."); |
| } |
| currentInputMethodId = null; |
| } |
| } |
| // If it's a disabled system ime, add it to the disabled list so that it |
| // doesn't get enabled automatically on any changes to the package list |
| if (systemIme && hasHardKeyboard) { |
| if (disabledSystemIMEs.contains(imiId)) { |
| if (isImeChecked) { |
| disabledSystemIMEs.remove(imiId); |
| } |
| } else { |
| if (!isImeChecked) { |
| disabledSystemIMEs.add(imiId); |
| } |
| } |
| } |
| } |
| |
| StringBuilder builder = new StringBuilder(); |
| buildInputMethodsAndSubtypesString(builder, enabledIMEAndSubtypesMap); |
| StringBuilder disabledSysImesBuilder = new StringBuilder(); |
| buildDisabledSystemInputMethods(disabledSysImesBuilder, 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 default inputmethod settings. :" + currentInputMethodId); |
| Log.d(TAG, "--- Needs to reset the selected subtype :" + needsToResetSelectedSubtype); |
| Log.d(TAG, "--- Subtype is selected :" + isInputMethodSubtypeSelected(resolver)); |
| } |
| |
| // Redefines SelectedSubtype when all subtypes are unchecked or there is no subtype |
| // selected. And if the selected subtype of the current input method was disabled, |
| // We should reset the selected input method's subtype. |
| if (needsToResetSelectedSubtype || !isInputMethodSubtypeSelected(resolver)) { |
| if (DEBUG) { |
| Log.d(TAG, "--- Reset inputmethod subtype because it's not defined."); |
| } |
| putSelectedInputMethodSubtype(resolver, NOT_A_SUBTYPE_ID); |
| } |
| |
| Settings.Secure.putString(resolver, |
| Settings.Secure.ENABLED_INPUT_METHODS, builder.toString()); |
| if (disabledSysImesBuilder.length() > 0) { |
| Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, |
| disabledSysImesBuilder.toString()); |
| } |
| // If the current input method is unset, InputMethodManagerService will find the applicable |
| // IME from the history and the system locale. |
| Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD, |
| currentInputMethodId != null ? currentInputMethodId : ""); |
| } |
| |
| public static void loadInputMethodSubtypeList( |
| SettingsPreferenceFragment context, ContentResolver resolver, |
| List<InputMethodInfo> inputMethodInfos, |
| final Map<String, List<Preference>> inputMethodPrefsMap) { |
| HashMap<String, HashSet<String>> enabledSubtypes = |
| getEnabledInputMethodsAndSubtypeList(resolver); |
| |
| for (InputMethodInfo imi : inputMethodInfos) { |
| final String imiId = imi.getId(); |
| Preference pref = context.findPreference(imiId); |
| if (pref != null && pref instanceof CheckBoxPreference) { |
| CheckBoxPreference checkBoxPreference = (CheckBoxPreference) pref; |
| boolean isEnabled = enabledSubtypes.containsKey(imiId); |
| checkBoxPreference.setChecked(isEnabled); |
| if (inputMethodPrefsMap != null) { |
| for (Preference childPref: inputMethodPrefsMap.get(imiId)) { |
| childPref.setEnabled(isEnabled); |
| } |
| } |
| setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled); |
| } |
| } |
| updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes); |
| } |
| |
| public static void setSubtypesPreferenceEnabled(SettingsPreferenceFragment context, |
| List<InputMethodInfo> inputMethodProperties, String id, boolean enabled) { |
| PreferenceScreen preferenceScreen = context.getPreferenceScreen(); |
| for (InputMethodInfo imi : inputMethodProperties) { |
| if (id.equals(imi.getId())) { |
| final int subtypeCount = imi.getSubtypeCount(); |
| for (int i = 0; i < subtypeCount; ++i) { |
| InputMethodSubtype subtype = imi.getSubtypeAt(i); |
| CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference( |
| id + subtype.hashCode()); |
| if (pref != null) { |
| pref.setEnabled(enabled); |
| } |
| } |
| } |
| } |
| } |
| |
| public static void updateSubtypesPreferenceChecked(SettingsPreferenceFragment context, |
| List<InputMethodInfo> inputMethodProperties, |
| HashMap<String, HashSet<String>> enabledSubtypes) { |
| PreferenceScreen preferenceScreen = context.getPreferenceScreen(); |
| for (InputMethodInfo imi : inputMethodProperties) { |
| String id = imi.getId(); |
| if (!enabledSubtypes.containsKey(id)) break; |
| final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id); |
| final int subtypeCount = imi.getSubtypeCount(); |
| for (int i = 0; i < subtypeCount; ++i) { |
| InputMethodSubtype subtype = imi.getSubtypeAt(i); |
| String hashCode = String.valueOf(subtype.hashCode()); |
| if (DEBUG) { |
| Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", " |
| + enabledSubtypesSet.contains(hashCode)); |
| } |
| CheckBoxPreference pref = (CheckBoxPreference) preferenceScreen.findPreference( |
| id + hashCode); |
| if (pref != null) { |
| pref.setChecked(enabledSubtypesSet.contains(hashCode)); |
| } |
| } |
| } |
| } |
| |
| public static boolean isSystemIme(InputMethodInfo property) { |
| return (property.getServiceInfo().applicationInfo.flags |
| & ApplicationInfo.FLAG_SYSTEM) != 0; |
| } |
| |
| public static boolean isAuxiliaryIme(InputMethodInfo imi) { |
| return imi.isAuxiliaryIme(); |
| } |
| |
| public static boolean isAlwaysCheckedIme(InputMethodInfo imi, Context context, int imiCount) { |
| if (imiCount <= 1) { |
| return true; |
| } |
| if (!isSystemIme(imi)) { |
| return false; |
| } |
| if (isAuxiliaryIme(imi)) { |
| return false; |
| } |
| if (isValidDefaultIme(imi, context)) { |
| return true; |
| } |
| return containsSubtypeOf(imi, ENGLISH_LOCALE.getLanguage()); |
| } |
| |
| private static boolean isValidDefaultIme(InputMethodInfo imi, Context context) { |
| if (imi.getIsDefaultResourceId() != 0) { |
| try { |
| Resources res = context.createPackageContext( |
| imi.getPackageName(), 0).getResources(); |
| if (res.getBoolean(imi.getIsDefaultResourceId()) |
| && containsSubtypeOf(imi, context.getResources().getConfiguration(). |
| locale.getLanguage())) { |
| return true; |
| } |
| } catch (PackageManager.NameNotFoundException ex) { |
| } catch (Resources.NotFoundException ex) { |
| } |
| } |
| return false; |
| } |
| |
| private static boolean containsSubtypeOf(InputMethodInfo imi, String language) { |
| final int N = imi.getSubtypeCount(); |
| for (int i = 0; i < N; ++i) { |
| if (imi.getSubtypeAt(i).getLocale().startsWith(language)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| } |