/* * 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.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; 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 = ""; @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; } } mSystemLocale = config.locale.toString(); 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) .setIcon(android.R.drawable.ic_dialog_alert) .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); 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; public SubtypeCheckBoxPreference( Context context, String subtypeLocale, String systemLocale) { super(context); if (TextUtils.isEmpty(subtypeLocale)) { mIsSystemLocale = false; mIsSystemLanguage = false; } else { mIsSystemLocale = subtypeLocale.equals(systemLocale); mIsSystemLanguage = mIsSystemLocale || subtypeLocale.startsWith(systemLocale.substring(0, 2)); } } @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 t0.toString().compareTo(t1.toString()); } else { Log.w(TAG, "Illegal preference type."); return -1; } } } }