| /* |
| * 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.R; |
| import com.android.settings.SettingsPreferenceFragment; |
| |
| import android.app.AlertDialog; |
| import android.content.Context; |
| import android.content.DialogInterface; |
| import android.content.Intent; |
| import android.content.pm.PackageManager; |
| import android.content.res.Configuration; |
| import android.os.Bundle; |
| import android.preference.CheckBoxPreference; |
| import android.preference.Preference; |
| import android.preference.PreferenceCategory; |
| import android.preference.PreferenceScreen; |
| 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.text.Collator; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| |
| public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment { |
| private static final String TAG =InputMethodAndSubtypeEnabler.class.getSimpleName(); |
| private AlertDialog mDialog = null; |
| private boolean mHaveHardKeyboard; |
| final private HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap = |
| new HashMap<String, List<Preference>>(); |
| final private HashMap<String, CheckBoxPreference> mSubtypeAutoSelectionCBMap = |
| new HashMap<String, CheckBoxPreference>(); |
| private InputMethodManager mImm; |
| private List<InputMethodInfo> mInputMethodProperties; |
| private String mInputMethodId; |
| private String mTitle; |
| private String mSystemLocale = ""; |
| private Collator mCollator = Collator.getInstance(); |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| super.onCreate(icicle); |
| mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); |
| final Configuration config = getResources().getConfiguration(); |
| mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY); |
| |
| final Bundle arguments = getArguments(); |
| // Input method id should be available from an Intent when this preference is launched as a |
| // single Activity (see InputMethodAndSubtypeEnablerActivity). It should be available |
| // from a preference argument when the preference is launched as a part of the other |
| // Activity (like a right pane of 2-pane Settings app) |
| mInputMethodId = getActivity().getIntent().getStringExtra( |
| android.provider.Settings.EXTRA_INPUT_METHOD_ID); |
| if (mInputMethodId == null && (arguments != null)) { |
| final String inputMethodId = |
| arguments.getString(android.provider.Settings.EXTRA_INPUT_METHOD_ID); |
| if (inputMethodId != null) { |
| mInputMethodId = inputMethodId; |
| } |
| } |
| mTitle = getActivity().getIntent().getStringExtra(Intent.EXTRA_TITLE); |
| if (mTitle == null && (arguments != null)) { |
| final String title = arguments.getString(Intent.EXTRA_TITLE); |
| if (title != null) { |
| mTitle = title; |
| } |
| } |
| |
| final Locale locale = config.locale; |
| mSystemLocale = locale.toString(); |
| mCollator = Collator.getInstance(locale); |
| onCreateIMM(); |
| setPreferenceScreen(createPreferenceHierarchy()); |
| } |
| |
| @Override |
| public void onActivityCreated(Bundle icicle) { |
| super.onActivityCreated(icicle); |
| if (!TextUtils.isEmpty(mTitle)) { |
| getActivity().setTitle(mTitle); |
| } |
| } |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| InputMethodAndSubtypeUtil.loadInputMethodSubtypeList( |
| this, getContentResolver(), mInputMethodProperties, mInputMethodAndSubtypePrefsMap); |
| updateAutoSelectionCB(); |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| // Clear all subtypes of all IMEs to make sure |
| clearImplicitlyEnabledSubtypes(null); |
| InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(), |
| mInputMethodProperties, mHaveHardKeyboard); |
| } |
| |
| @Override |
| public boolean onPreferenceTreeClick( |
| PreferenceScreen preferenceScreen, Preference preference) { |
| |
| if (preference instanceof CheckBoxPreference) { |
| final CheckBoxPreference chkPref = (CheckBoxPreference) preference; |
| |
| for (String imiId: mSubtypeAutoSelectionCBMap.keySet()) { |
| if (mSubtypeAutoSelectionCBMap.get(imiId) == chkPref) { |
| // We look for the first preference item in subtype enabler. |
| // The first item is used for turning on/off subtype auto selection. |
| // We are in the subtype enabler and trying selecting subtypes automatically. |
| setSubtypeAutoSelectionEnabled(imiId, chkPref.isChecked()); |
| return super.onPreferenceTreeClick(preferenceScreen, preference); |
| } |
| } |
| |
| final String id = chkPref.getKey(); |
| if (chkPref.isChecked()) { |
| InputMethodInfo selImi = null; |
| final int N = mInputMethodProperties.size(); |
| for (int i = 0; i < N; i++) { |
| InputMethodInfo imi = mInputMethodProperties.get(i); |
| if (id.equals(imi.getId())) { |
| selImi = imi; |
| if (InputMethodAndSubtypeUtil.isSystemIme(imi)) { |
| InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled( |
| this, mInputMethodProperties, id, true); |
| // This is a built-in IME, so no need to warn. |
| return super.onPreferenceTreeClick(preferenceScreen, preference); |
| } |
| break; |
| } |
| } |
| if (selImi == null) { |
| return super.onPreferenceTreeClick(preferenceScreen, preference); |
| } |
| chkPref.setChecked(false); |
| if (mDialog == null) { |
| mDialog = (new AlertDialog.Builder(getActivity())) |
| .setTitle(android.R.string.dialog_alert_title) |
| .setIconAttribute(android.R.attr.alertDialogIcon) |
| .setCancelable(true) |
| .setPositiveButton(android.R.string.ok, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| chkPref.setChecked(true); |
| InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled( |
| InputMethodAndSubtypeEnabler.this, |
| mInputMethodProperties, id, true); |
| } |
| |
| }) |
| .setNegativeButton(android.R.string.cancel, |
| new DialogInterface.OnClickListener() { |
| @Override |
| public void onClick(DialogInterface dialog, int which) { |
| } |
| |
| }) |
| .create(); |
| } else { |
| if (mDialog.isShowing()) { |
| mDialog.dismiss(); |
| } |
| } |
| mDialog.setMessage(getResources().getString( |
| R.string.ime_security_warning, |
| selImi.getServiceInfo().applicationInfo.loadLabel(getPackageManager()))); |
| mDialog.show(); |
| } else { |
| InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled( |
| this, mInputMethodProperties, id, false); |
| updateAutoSelectionCB(); |
| } |
| } |
| return super.onPreferenceTreeClick(preferenceScreen, preference); |
| } |
| |
| @Override |
| public void onDestroy() { |
| super.onDestroy(); |
| if (mDialog != null) { |
| mDialog.dismiss(); |
| mDialog = null; |
| } |
| } |
| |
| private void onCreateIMM() { |
| InputMethodManager imm = (InputMethodManager) getSystemService( |
| Context.INPUT_METHOD_SERVICE); |
| |
| // TODO: Change mInputMethodProperties to Map |
| mInputMethodProperties = imm.getInputMethodList(); |
| } |
| |
| private PreferenceScreen createPreferenceHierarchy() { |
| // Root |
| final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity()); |
| final Context context = getActivity(); |
| |
| int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size()); |
| |
| for (int i = 0; i < N; ++i) { |
| final InputMethodInfo imi = mInputMethodProperties.get(i); |
| final int subtypeCount = imi.getSubtypeCount(); |
| if (subtypeCount <= 1) continue; |
| final String imiId = imi.getId(); |
| // Add this subtype to the list when no IME is specified or when the IME of this |
| // subtype is the specified IME. |
| if (!TextUtils.isEmpty(mInputMethodId) && !mInputMethodId.equals(imiId)) { |
| continue; |
| } |
| final PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(context); |
| root.addPreference(keyboardSettingsCategory); |
| final PackageManager pm = getPackageManager(); |
| final CharSequence label = imi.loadLabel(pm); |
| |
| keyboardSettingsCategory.setTitle(label); |
| keyboardSettingsCategory.setKey(imiId); |
| // TODO: Use toggle Preference if images are ready. |
| final CheckBoxPreference autoCB = new CheckBoxPreference(context); |
| mSubtypeAutoSelectionCBMap.put(imiId, autoCB); |
| keyboardSettingsCategory.addPreference(autoCB); |
| |
| final PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(context); |
| activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes); |
| root.addPreference(activeInputMethodsCategory); |
| |
| boolean isAutoSubtype = false; |
| CharSequence autoSubtypeLabel = null; |
| final ArrayList<Preference> subtypePreferences = new ArrayList<Preference>(); |
| if (subtypeCount > 0) { |
| for (int j = 0; j < subtypeCount; ++j) { |
| final InputMethodSubtype subtype = imi.getSubtypeAt(j); |
| final CharSequence subtypeLabel = subtype.getDisplayName(context, |
| imi.getPackageName(), imi.getServiceInfo().applicationInfo); |
| if (subtype.overridesImplicitlyEnabledSubtype()) { |
| if (!isAutoSubtype) { |
| isAutoSubtype = true; |
| autoSubtypeLabel = subtypeLabel; |
| } |
| } else { |
| final CheckBoxPreference chkbxPref = new SubtypeCheckBoxPreference( |
| context, subtype.getLocale(), mSystemLocale, mCollator); |
| chkbxPref.setKey(imiId + subtype.hashCode()); |
| chkbxPref.setTitle(subtypeLabel); |
| subtypePreferences.add(chkbxPref); |
| } |
| } |
| Collections.sort(subtypePreferences); |
| for (int j = 0; j < subtypePreferences.size(); ++j) { |
| activeInputMethodsCategory.addPreference(subtypePreferences.get(j)); |
| } |
| mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences); |
| } |
| if (isAutoSubtype) { |
| if (TextUtils.isEmpty(autoSubtypeLabel)) { |
| Log.w(TAG, "Title for auto subtype is empty."); |
| autoCB.setTitle("---"); |
| } else { |
| autoCB.setTitle(autoSubtypeLabel); |
| } |
| } else { |
| autoCB.setTitle(R.string.use_system_language_to_select_input_method_subtypes); |
| } |
| } |
| return root; |
| } |
| |
| private boolean isNoSubtypesExplicitlySelected(String imiId) { |
| boolean allSubtypesOff = true; |
| final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); |
| for (Preference subtypePref: subtypePrefs) { |
| if (subtypePref instanceof CheckBoxPreference |
| && ((CheckBoxPreference)subtypePref).isChecked()) { |
| allSubtypesOff = false; |
| break; |
| } |
| } |
| return allSubtypesOff; |
| } |
| |
| private void setSubtypeAutoSelectionEnabled(String imiId, boolean autoSelectionEnabled) { |
| CheckBoxPreference autoSelectionCB = mSubtypeAutoSelectionCBMap.get(imiId); |
| if (autoSelectionCB == null) return; |
| autoSelectionCB.setChecked(autoSelectionEnabled); |
| final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); |
| for (Preference subtypePref: subtypePrefs) { |
| if (subtypePref instanceof CheckBoxPreference) { |
| // When autoSelectionEnabled is true, all subtype prefs need to be disabled with |
| // implicitly checked subtypes. In case of false, all subtype prefs need to be |
| // enabled. |
| subtypePref.setEnabled(!autoSelectionEnabled); |
| if (autoSelectionEnabled) { |
| ((CheckBoxPreference)subtypePref).setChecked(false); |
| } |
| } |
| } |
| if (autoSelectionEnabled) { |
| InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(), |
| mInputMethodProperties, mHaveHardKeyboard); |
| setCheckedImplicitlyEnabledSubtypes(imiId); |
| } |
| } |
| |
| private void setCheckedImplicitlyEnabledSubtypes(String targetImiId) { |
| updateImplicitlyEnabledSubtypes(targetImiId, true); |
| } |
| |
| private void clearImplicitlyEnabledSubtypes(String targetImiId) { |
| updateImplicitlyEnabledSubtypes(targetImiId, false); |
| } |
| |
| private void updateImplicitlyEnabledSubtypes(String targetImiId, boolean check) { |
| // When targetImiId is null, apply to all subtypes of all IMEs |
| for (InputMethodInfo imi: mInputMethodProperties) { |
| String imiId = imi.getId(); |
| if (targetImiId != null && !targetImiId.equals(imiId)) continue; |
| final CheckBoxPreference autoCB = mSubtypeAutoSelectionCBMap.get(imiId); |
| // No need to update implicitly enabled subtypes when the user has unchecked the |
| // "subtype auto selection". |
| if (autoCB == null || !autoCB.isChecked()) continue; |
| final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); |
| final List<InputMethodSubtype> implicitlyEnabledSubtypes = |
| mImm.getEnabledInputMethodSubtypeList(imi, true); |
| if (subtypePrefs == null || implicitlyEnabledSubtypes == null) continue; |
| for (Preference subtypePref: subtypePrefs) { |
| if (subtypePref instanceof CheckBoxPreference) { |
| CheckBoxPreference cb = (CheckBoxPreference)subtypePref; |
| cb.setChecked(false); |
| if (check) { |
| for (InputMethodSubtype subtype: implicitlyEnabledSubtypes) { |
| String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode(); |
| if (cb.getKey().equals(implicitlyEnabledSubtypePrefKey)) { |
| cb.setChecked(true); |
| break; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| private void updateAutoSelectionCB() { |
| for (String imiId: mInputMethodAndSubtypePrefsMap.keySet()) { |
| setSubtypeAutoSelectionEnabled(imiId, isNoSubtypesExplicitlySelected(imiId)); |
| } |
| setCheckedImplicitlyEnabledSubtypes(null); |
| } |
| |
| private static class SubtypeCheckBoxPreference extends CheckBoxPreference { |
| private final boolean mIsSystemLocale; |
| private final boolean mIsSystemLanguage; |
| private final Collator mCollator; |
| |
| public SubtypeCheckBoxPreference( |
| Context context, String subtypeLocale, String systemLocale, Collator collator) { |
| super(context); |
| if (TextUtils.isEmpty(subtypeLocale)) { |
| mIsSystemLocale = false; |
| mIsSystemLanguage = false; |
| } else { |
| mIsSystemLocale = subtypeLocale.equals(systemLocale); |
| mIsSystemLanguage = mIsSystemLocale |
| || subtypeLocale.startsWith(systemLocale.substring(0, 2)); |
| } |
| mCollator = collator; |
| } |
| |
| @Override |
| public int compareTo(Preference p) { |
| if (p instanceof SubtypeCheckBoxPreference) { |
| final SubtypeCheckBoxPreference pref = ((SubtypeCheckBoxPreference)p); |
| final CharSequence t0 = getTitle(); |
| final CharSequence t1 = pref.getTitle(); |
| if (TextUtils.equals(t0, t1)) { |
| return 0; |
| } |
| if (mIsSystemLocale) { |
| return -1; |
| } |
| if (pref.mIsSystemLocale) { |
| return 1; |
| } |
| if (mIsSystemLanguage) { |
| return -1; |
| } |
| if (pref.mIsSystemLanguage) { |
| return 1; |
| } |
| if (TextUtils.isEmpty(t0)) { |
| return 1; |
| } |
| if (TextUtils.isEmpty(t1)) { |
| return -1; |
| } |
| return mCollator.compare(t0.toString(), t1.toString()); |
| } else { |
| Log.w(TAG, "Illegal preference type."); |
| return super.compareTo(p); |
| } |
| } |
| } |
| } |