Reorganize language & input settings

Bug: 14860252
Bug: 16115751
Change-Id: I198aabebc08421764b78e137e7f26d2a7772d452
This commit is contained in:
Tadashi G. Takaoka
2014-07-09 16:26:12 +09:00
parent 45ece1cf60
commit 74e7c3e360
6 changed files with 292 additions and 353 deletions

View File

@@ -3116,8 +3116,10 @@
<string name="input_methods_settings_title">Text input</string> <string name="input_methods_settings_title">Text input</string>
<!-- Setting name for Input Method chooser --> <!-- Setting name for Input Method chooser -->
<string name="input_method">Input method</string> <string name="input_method">Input method</string>
<!-- Title for the option to press to choose the current input method [CHAR LIMIT=35] --> <!-- Title for the option to press to enable or disable keyboards, also known as input methods [CHAR LIMIT=35] -->
<string name="current_input_method">Default</string> <string name="choose_input_methods">Choose Keyboards</string>
<!-- Title for the option to press to choose the current keyboard, also known as input method [CHAR LIMIT=35] -->
<string name="current_input_method">Current Keyboard</string>
<!-- Title for setting the visibility of input method selector [CHAR LIMIT=35] --> <!-- Title for setting the visibility of input method selector [CHAR LIMIT=35] -->
<string name="input_method_selector">Input method selector</string> <string name="input_method_selector">Input method selector</string>
<!-- An option to always show input method selector automatically when needed [CHAR LIMIT=25] --> <!-- An option to always show input method selector automatically when needed [CHAR LIMIT=25] -->

View File

@@ -32,10 +32,17 @@
android:key="key_user_dictionary_settings" android:key="key_user_dictionary_settings"
android:title="@string/user_dict_settings_title" /> android:title="@string/user_dict_settings_title" />
<PreferenceCategory android:key="keyboard_settings_category" <PreferenceCategory
android:key="keyboard_settings_category"
android:title="@string/keyboard_settings_category"> android:title="@string/keyboard_settings_category">
<PreferenceScreen android:key="current_input_method" <!-- An intent for this preference will be populated programmatically. -->
<PreferenceScreen
android:key="choose_input_methods"
android:title="@string/choose_input_methods" />
<PreferenceScreen
android:key="current_input_method"
android:title="@string/current_input_method" /> android:title="@string/current_input_method" />
<!-- Enabled input method list will be populated programmatically here. -->
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory

View File

@@ -16,26 +16,15 @@
package com.android.settings.inputmethod; package com.android.settings.inputmethod;
import android.content.ComponentName;
import android.content.pm.ServiceInfo;
import com.android.settings.R;
import com.android.settings.Settings.KeyboardLayoutPickerActivity;
import com.android.settings.Settings.SpellCheckersSettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.UserDictionarySettings;
import com.android.settings.Utils;
import com.android.settings.VoiceInputOutputSettings;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
import android.app.Activity; import android.app.Activity;
import android.app.Fragment; import android.app.Fragment;
import android.content.ComponentName;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo; import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.ContentObserver; import android.database.ContentObserver;
@@ -47,7 +36,6 @@ import android.os.Handler;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.ListPreference; import android.preference.ListPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener; import android.preference.Preference.OnPreferenceClickListener;
import android.preference.PreferenceCategory; import android.preference.PreferenceCategory;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
@@ -60,17 +48,33 @@ import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype;
import android.widget.BaseAdapter;
import com.android.settings.R;
import com.android.settings.Settings.KeyboardLayoutPickerActivity;
import com.android.settings.Settings.SpellCheckersSettingsActivity;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.SubSettings;
import com.android.settings.UserDictionarySettings;
import com.android.settings.Utils;
import com.android.settings.VoiceInputOutputSettings;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.search.SearchIndexableRaw;
import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.TreeSet; import java.util.TreeSet;
public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener, implements Preference.OnPreferenceChangeListener, InputManager.InputDeviceListener,
KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable { KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable,
InputMethodPreference.onSavePreferenceListener {
private static final String KEY_PHONE_LANGUAGE = "phone_language"; private static final String KEY_PHONE_LANGUAGE = "phone_language";
private static final String KEY_CHOOSE_INPUT_METHODS = "choose_input_methods";
private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method"; private static final String KEY_CURRENT_INPUT_METHOD = "current_input_method";
private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector"; private static final String KEY_INPUT_METHOD_SELECTOR = "input_method_selector";
private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings"; private static final String KEY_USER_DICTIONARY_SETTINGS = "key_user_dictionary_settings";
@@ -95,37 +99,29 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList = new ArrayList<>(); private final ArrayList<PreferenceScreen> mHardKeyboardPreferenceList = new ArrayList<>();
private InputManager mIm; private InputManager mIm;
private InputMethodManager mImm; private InputMethodManager mImm;
private boolean mIsOnlyImeSettings; private boolean mShowsOnlyFullImeAndKeyboardList;
private Handler mHandler; private Handler mHandler;
private SettingsObserver mSettingsObserver; private SettingsObserver mSettingsObserver;
private Intent mIntentWaitingForResult; private Intent mIntentWaitingForResult;
private InputMethodSettingValuesWrapper mInputMethodSettingValues; private InputMethodSettingValuesWrapper mInputMethodSettingValues;
private final OnPreferenceChangeListener mOnImePreferenceChangedListener =
new OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference arg0, Object arg1) {
InputMethodSettingValuesWrapper.getInstance(
arg0.getContext()).refreshAllInputMethodAndSubtypes();
((BaseAdapter)getPreferenceScreen().getRootAdapter()).notifyDataSetChanged();
updateInputMethodPreferenceViews();
return true;
}
};
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
super.onCreate(icicle); super.onCreate(icicle);
addPreferencesFromResource(R.xml.language_settings); addPreferencesFromResource(R.xml.language_settings);
final Activity activity = getActivity();
mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(activity);
try { try {
mDefaultInputMethodSelectorVisibility = Integer.valueOf( mDefaultInputMethodSelectorVisibility = Integer.valueOf(
getString(R.string.input_method_selector_visibility_default_value)); getString(R.string.input_method_selector_visibility_default_value));
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
} }
if (getActivity().getAssets().getLocales().length == 1) { if (activity.getAssets().getLocales().length == 1) {
// No "Select language" pref if there's only one system locale available. // No "Select language" pref if there's only one system locale available.
getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE)); getPreferenceScreen().removePreference(findPreference(KEY_PHONE_LANGUAGE));
} else { } else {
@@ -149,45 +145,42 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
"game_controller_settings_category"); "game_controller_settings_category");
// Filter out irrelevant features if invoked from IME settings button. // Filter out irrelevant features if invoked from IME settings button.
mIsOnlyImeSettings = Settings.ACTION_INPUT_METHOD_SETTINGS.equals( mShowsOnlyFullImeAndKeyboardList = Settings.ACTION_INPUT_METHOD_SETTINGS.equals(
getActivity().getIntent().getAction()); activity.getIntent().getAction());
getActivity().getIntent().setAction(null); activity.getIntent().setAction(null);
if (mIsOnlyImeSettings) { if (mShowsOnlyFullImeAndKeyboardList) {
getPreferenceScreen().removeAll(); getPreferenceScreen().removeAll();
getPreferenceScreen().addPreference(mHardKeyboardCategory); getPreferenceScreen().addPreference(mHardKeyboardCategory);
if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) { if (SHOW_INPUT_METHOD_SWITCHER_SETTINGS) {
getPreferenceScreen().addPreference(mShowInputMethodSelectorPref); getPreferenceScreen().addPreference(mShowInputMethodSelectorPref);
} }
mKeyboardSettingsCategory.removeAll();
getPreferenceScreen().addPreference(mKeyboardSettingsCategory); getPreferenceScreen().addPreference(mKeyboardSettingsCategory);
} } else {
final Preference pref = findPreference(KEY_CHOOSE_INPUT_METHODS);
// Build IME preference category. final Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); intent.setClass(activity, SubSettings.class);
mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(getActivity()); intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, getClass().getName());
intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID,
mKeyboardSettingsCategory.removeAll(); R.string.choose_input_methods);
if (!mIsOnlyImeSettings) { pref.setIntent(intent);
final PreferenceScreen currentIme = new PreferenceScreen(getActivity(), null);
currentIme.setKey(KEY_CURRENT_INPUT_METHOD);
currentIme.setTitle(getResources().getString(R.string.current_input_method));
mKeyboardSettingsCategory.addPreference(currentIme);
} }
// Build hard keyboard and game controller preference categories. // Build hard keyboard and game controller preference categories.
mIm = (InputManager)getActivity().getSystemService(Context.INPUT_SERVICE); mIm = (InputManager)activity.getSystemService(Context.INPUT_SERVICE);
updateInputDevices(); updateInputDevices();
// Spell Checker // Spell Checker
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClass(getActivity(), SpellCheckersSettingsActivity.class);
final SpellCheckersPreference scp = ((SpellCheckersPreference)findPreference( final SpellCheckersPreference scp = ((SpellCheckersPreference)findPreference(
"spellcheckers_settings")); "spellcheckers_settings"));
if (scp != null) { if (scp != null) {
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClass(activity, SpellCheckersSettingsActivity.class);
scp.setFragmentIntent(this, intent); scp.setFragmentIntent(this, intent);
} }
mHandler = new Handler(); mHandler = new Handler();
mSettingsObserver = new SettingsObserver(mHandler, getActivity()); mSettingsObserver = new SettingsObserver(mHandler, activity);
} }
private void updateInputMethodSelectorSummary(int value) { private void updateInputMethodSelectorSummary(int value) {
@@ -246,7 +239,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
mSettingsObserver.resume(); mSettingsObserver.resume();
mIm.registerInputDeviceListener(this, null); mIm.registerInputDeviceListener(this, null);
if (!mIsOnlyImeSettings) { if (!mShowsOnlyFullImeAndKeyboardList) {
if (mLanguagePref != null) { if (mLanguagePref != null) {
String localeName = getLocaleName(getResources()); String localeName = getLocaleName(getResources());
mLanguagePref.setSummary(localeName); mLanguagePref.setSummary(localeName);
@@ -419,31 +412,33 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
private void updateInputMethodPreferenceViews() { private void updateInputMethodPreferenceViews() {
synchronized (mInputMethodPreferenceList) { synchronized (mInputMethodPreferenceList) {
// Clear existing "InputMethodPreference"s // Clear existing "InputMethodPreference"s
for (final InputMethodPreference imp : mInputMethodPreferenceList) { for (final InputMethodPreference pref : mInputMethodPreferenceList) {
mKeyboardSettingsCategory.removePreference(imp); mKeyboardSettingsCategory.removePreference(pref);
} }
mInputMethodPreferenceList.clear(); mInputMethodPreferenceList.clear();
final List<InputMethodInfo> imis = mInputMethodSettingValues.getInputMethodList(); final Context context = getActivity();
final List<InputMethodInfo> imis = mShowsOnlyFullImeAndKeyboardList
? mInputMethodSettingValues.getInputMethodList()
: mImm.getEnabledInputMethodList();
final int N = (imis == null ? 0 : imis.size()); final int N = (imis == null ? 0 : imis.size());
for (int i = 0; i < N; ++i) { for (int i = 0; i < N; ++i) {
final InputMethodInfo imi = imis.get(i); final InputMethodInfo imi = imis.get(i);
final InputMethodPreference pref = getInputMethodPreference(imi); final InputMethodPreference pref = new InputMethodPreference(
pref.setOnImePreferenceChangeListener(mOnImePreferenceChangedListener); context, imi, mShowsOnlyFullImeAndKeyboardList /* hasSwitch */, this);
mInputMethodPreferenceList.add(pref); mInputMethodPreferenceList.add(pref);
} }
final Collator collator = Collator.getInstance();
if (!mInputMethodPreferenceList.isEmpty()) { Collections.sort(mInputMethodPreferenceList, new Comparator<InputMethodPreference>() {
Collections.sort(mInputMethodPreferenceList); @Override
for (int i = 0; i < N; ++i) { public int compare(InputMethodPreference lhs, InputMethodPreference rhs) {
mKeyboardSettingsCategory.addPreference(mInputMethodPreferenceList.get(i)); return lhs.compareTo(rhs, collator);
}
}
// update views status
for (Preference pref : mInputMethodPreferenceList) {
if (pref instanceof InputMethodPreference) {
((InputMethodPreference) pref).updatePreferenceViews();
} }
});
for (int i = 0; i < N; ++i) {
final InputMethodPreference pref = mInputMethodPreferenceList.get(i);
mKeyboardSettingsCategory.addPreference(pref);
InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
pref.updatePreferenceViews();
} }
} }
updateCurrentImeName(); updateCurrentImeName();
@@ -456,6 +451,19 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
mInputMethodSettingValues.getInputMethodList(), null); mInputMethodSettingValues.getInputMethodList(), null);
} }
@Override
public void onSaveInputMethodPreference(final InputMethodPreference pref) {
final boolean hasHardwareKeyboard = getResources().getConfiguration().keyboard
== Configuration.KEYBOARD_QWERTY;
InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(),
mImm.getInputMethodList(), hasHardwareKeyboard);
// Update input method settings and preference list.
mInputMethodSettingValues.refreshAllInputMethodAndSubtypes();
for (final InputMethodPreference p : mInputMethodPreferenceList) {
p.updatePreferenceViews();
}
}
private void updateCurrentImeName() { private void updateCurrentImeName() {
final Context context = getActivity(); final Context context = getActivity();
if (context == null || mImm == null) return; if (context == null || mImm == null) return;
@@ -471,26 +479,6 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
} }
} }
private InputMethodPreference getInputMethodPreference(InputMethodInfo imi) {
final PackageManager pm = getPackageManager();
final CharSequence label = imi.loadLabel(pm);
// IME settings
final Intent intent;
final String settingsActivity = imi.getSettingsActivity();
if (!TextUtils.isEmpty(settingsActivity)) {
intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(imi.getPackageName(), settingsActivity);
} else {
intent = null;
}
// Add a check box for enabling/disabling IME
final InputMethodPreference pref = new InputMethodPreference(this, intent, mImm, imi);
pref.setKey(imi.getId());
pref.setTitle(label);
return pref;
}
private void updateInputDevices() { private void updateInputDevices() {
updateHardKeyboards(); updateHardKeyboards();
updateGameControllers(); updateGameControllers();
@@ -643,7 +631,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
if (context.getAssets().getLocales().length > 1) { if (context.getAssets().getLocales().length > 1) {
String localeName = getLocaleName(resources); String localeName = getLocaleName(resources);
SearchIndexableRaw indexable = new SearchIndexableRaw(context); SearchIndexableRaw indexable = new SearchIndexableRaw(context);
indexable.key = "phone_language"; indexable.key = KEY_PHONE_LANGUAGE;
indexable.title = context.getString(R.string.phone_language); indexable.title = context.getString(R.string.phone_language);
indexable.summaryOn = localeName; indexable.summaryOn = localeName;
indexable.summaryOff = localeName; indexable.summaryOff = localeName;
@@ -681,7 +669,7 @@ public class InputMethodAndLanguageSettings extends SettingsPreferenceFragment
// Current IME. // Current IME.
String currImeName = immValues.getCurrentInputMethodName(context).toString(); String currImeName = immValues.getCurrentInputMethodName(context).toString();
indexable = new SearchIndexableRaw(context); indexable = new SearchIndexableRaw(context);
indexable.key = "current_input_method"; indexable.key = KEY_CURRENT_INPUT_METHOD;
indexable.title = context.getString(R.string.current_input_method); indexable.title = context.getString(R.string.current_input_method);
indexable.summaryOn = currImeName; indexable.summaryOn = currImeName;
indexable.summaryOff = currImeName; indexable.summaryOff = currImeName;

View File

@@ -16,13 +16,12 @@
package com.android.settings.inputmethod; package com.android.settings.inputmethod;
import com.android.internal.inputmethod.InputMethodUtils;
import com.android.settings.SettingsPreferenceFragment;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.SharedPreferences;
import android.preference.CheckBoxPreference; import android.preference.CheckBoxPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.PreferenceScreen; import android.preference.PreferenceScreen;
import android.preference.TwoStatePreference;
import android.provider.Settings; import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException; import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils; import android.text.TextUtils;
@@ -30,6 +29,9 @@ import android.util.Log;
import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype;
import com.android.internal.inputmethod.InputMethodUtils;
import com.android.settings.SettingsPreferenceFragment;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@@ -163,10 +165,10 @@ class InputMethodAndSubtypeUtil {
final String imiId = imi.getId(); final String imiId = imi.getId();
Preference pref = context.findPreference(imiId); Preference pref = context.findPreference(imiId);
if (pref == null) continue; if (pref == null) continue;
// In the Configure input method screen or in the subtype enabler screen. // In the choose input method screen or in the subtype enabler screen,
// pref is instance of CheckBoxPreference in the Configure input method screen. // <code>pref</code> is an instance of TwoStatePreference.
final boolean isImeChecked = (pref instanceof CheckBoxPreference) ? final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
((CheckBoxPreference) pref).isChecked() ((TwoStatePreference) pref).isChecked()
: enabledIMEAndSubtypesMap.containsKey(imiId); : enabledIMEAndSubtypesMap.containsKey(imiId);
final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId); final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
final boolean systemIme = InputMethodUtils.isSystemIme(imi); final boolean systemIme = InputMethodUtils.isSystemIme(imi);
@@ -338,4 +340,15 @@ class InputMethodAndSubtypeUtil {
} }
} }
} }
static void removeUnnecessaryNonPersistentPreference(final Preference pref) {
final String key = pref.getKey();
if (pref.isPersistent() || key == null) {
return;
}
final SharedPreferences prefs = pref.getSharedPreferences();
if (prefs != null && prefs.contains(key)) {
prefs.edit().remove(key).apply();
}
}
} }

View File

@@ -16,304 +16,233 @@
package com.android.settings.inputmethod; package com.android.settings.inputmethod;
import com.android.internal.inputmethod.InputMethodUtils;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.SettingsPreferenceFragment;
import com.android.settings.Utils;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Fragment;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference; import android.preference.Preference;
import android.preference.Preference.OnPreferenceChangeListener;
import android.preference.Preference.OnPreferenceClickListener;
import android.preference.SwitchPreference;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.InputMethodSubtype;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.android.internal.inputmethod.InputMethodUtils;
import com.android.settings.R;
import java.text.Collator; import java.text.Collator;
import java.util.ArrayList;
import java.util.List; import java.util.List;
// TODO: Make this non-persistent. /**
class InputMethodPreference extends CheckBoxPreference { * Input method preference.
*
* This preference represents an IME. It is used for two purposes. 1) An instance with a switch
* is used to enable or disable the IME. 2) An instance without a switch is used to invoke the
* setting activity of the IME.
*/
class InputMethodPreference extends SwitchPreference implements OnPreferenceClickListener,
OnPreferenceChangeListener {
private static final String TAG = InputMethodPreference.class.getSimpleName(); private static final String TAG = InputMethodPreference.class.getSimpleName();
private final SettingsPreferenceFragment mFragment; private static final String EMPTY_TEXT = "";
interface onSavePreferenceListener {
/**
* Called when this preference needs to be saved its state.
*
* Note that this preference is non-persistent and needs explicitly to be saved its state.
* Because changing one IME state may change other IMEs' state, this is a place to update
* other IMEs' state as well.
*
* @param pref This preference.
*/
public void onSaveInputMethodPreference(InputMethodPreference pref);
}
private final InputMethodInfo mImi; private final InputMethodInfo mImi;
private final InputMethodManager mImm; private final boolean mHasPriorityInSorting;
private final boolean mIsValidSystemNonAuxAsciiCapableIme; private final onSavePreferenceListener mOnSaveListener;
private final Intent mSettingsIntent; private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
private final boolean mIsSystemIme;
private final Collator mCollator;
private AlertDialog mDialog = null; private AlertDialog mDialog = null;
private ImageView mInputMethodSettingsButton;
private TextView mTitleText;
private TextView mSummaryText;
private View mInputMethodPref;
private OnPreferenceChangeListener mOnImePreferenceChangeListener;
private final OnClickListener mPrefOnclickListener = new OnClickListener() { /**
@Override * A preference entry of an input method.
public void onClick(View arg0) { *
if (!isEnabled()) { * @param context The Context this is associated with.
return; * @param imi The {@link InputMethodInfo} of this preference.
} * @param isImeEnabler true if this preference is the IME enabler that has enable/disable
if (isChecked()) { * switches for all available IMEs, not the list of enabled IMEs.
setChecked(false, true /* save */); * @param onSaveListener The listener called when this preference has been changed and needs
} else { * to save the state to shared preference.
if (mIsSystemIme) { */
setChecked(true, true /* save */); InputMethodPreference(final Context context, final InputMethodInfo imi,
} else { final boolean isImeEnabler, final onSavePreferenceListener onSaveListener) {
showSecurityWarnDialog(mImi, InputMethodPreference.this); super(context);
} setPersistent(false);
}
}
};
public InputMethodPreference(SettingsPreferenceFragment fragment, Intent settingsIntent,
InputMethodManager imm, InputMethodInfo imi) {
super(fragment.getActivity());
setLayoutResource(R.layout.preference_inputmethod);
setWidgetLayoutResource(R.layout.preference_inputmethod_widget);
mFragment = fragment;
mSettingsIntent = settingsIntent;
mImm = imm;
mImi = imi; mImi = imi;
mIsSystemIme = InputMethodUtils.isSystemIme(imi); mOnSaveListener = onSaveListener;
mCollator = Collator.getInstance(fragment.getResources().getConfiguration().locale); if (!isImeEnabler) {
final Context context = fragment.getActivity(); // Hide switch widget.
mIsValidSystemNonAuxAsciiCapableIme = InputMethodSettingValuesWrapper setWidgetLayoutResource(0 /* widgetLayoutResId */);
.getInstance(context).isValidSystemNonAuxAsciiCapableIme(imi, context); }
updatePreferenceViews(); // Disable on/off switch texts.
setSwitchTextOn(EMPTY_TEXT);
setSwitchTextOff(EMPTY_TEXT);
setKey(imi.getId());
setTitle(imi.loadLabel(context.getPackageManager()));
final String settingsActivity = imi.getSettingsActivity();
if (TextUtils.isEmpty(settingsActivity)) {
setIntent(null);
} else {
// Set an intent to invoke settings activity of an input method.
final Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(imi.getPackageName(), settingsActivity);
setIntent(intent);
}
mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
mHasPriorityInSorting = InputMethodUtils.isSystemIme(imi)
&& mInputMethodSettingValues.isValidSystemNonAuxAsciiCapableIme(imi, context);
setOnPreferenceClickListener(this);
setOnPreferenceChangeListener(this);
}
private boolean isImeEnabler() {
// If this {@link SwitchPreference} doesn't have a widget layout, we explicitly hide the
// switch widget at constructor.
return getWidgetLayoutResource() != 0;
} }
@Override @Override
protected void onBindView(View view) { public boolean onPreferenceChange(final Preference preference, final Object newValue) {
super.onBindView(view); // Always returns false to prevent default behavior.
mInputMethodPref = view.findViewById(R.id.inputmethod_pref); // See {@link TwoStatePreference#onClick()}.
mInputMethodPref.setOnClickListener(mPrefOnclickListener); if (!isImeEnabler()) {
mInputMethodSettingsButton = (ImageView)view.findViewById(R.id.inputmethod_settings); // Prevent disabling an IME because this preference is for invoking a settings activity.
mTitleText = (TextView)view.findViewById(android.R.id.title);
mSummaryText = (TextView)view.findViewById(android.R.id.summary);
final boolean hasSubtypes = mImi.getSubtypeCount() > 1;
final String imiId = mImi.getId();
if (hasSubtypes) {
mInputMethodPref.setOnLongClickListener(new OnLongClickListener() {
@Override
public boolean onLongClick(View arg0) {
final Bundle bundle = new Bundle();
bundle.putString(android.provider.Settings.EXTRA_INPUT_METHOD_ID, imiId);
startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
0, bundle);
return true;
}
});
}
if (mSettingsIntent != null) {
mInputMethodSettingsButton.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View arg0) {
try {
mFragment.startActivity(mSettingsIntent);
} catch (ActivityNotFoundException e) {
Log.d(TAG, "IME's Settings Activity Not Found: " + e);
final String msg = mFragment.getString(
R.string.failed_to_open_app_settings_toast,
mImi.loadLabel(
mFragment.getActivity().getPackageManager()));
Toast.makeText(
mFragment.getActivity(), msg, Toast.LENGTH_LONG).show();
}
}
});
}
if (hasSubtypes) {
final OnLongClickListener listener = new OnLongClickListener() {
@Override
public boolean onLongClick(View arg0) {
final Bundle bundle = new Bundle();
bundle.putString(android.provider.Settings.EXTRA_INPUT_METHOD_ID, imiId);
startFragment(mFragment, InputMethodAndSubtypeEnabler.class.getName(),
0, bundle);
return true;
}
};
mInputMethodSettingsButton.setOnLongClickListener(listener);
}
if (mSettingsIntent == null) {
mInputMethodSettingsButton.setVisibility(View.GONE);
}
updatePreferenceViews();
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
updatePreferenceViews();
}
public void updatePreferenceViews() {
final boolean isAlwaysChecked =
InputMethodSettingValuesWrapper.getInstance(getContext()).isAlwaysCheckedIme(
mImi, getContext());
if (isAlwaysChecked) {
super.setChecked(true);
super.setEnabled(false);
} else {
super.setEnabled(true);
}
final boolean checked = isChecked();
if (mInputMethodSettingsButton != null) {
mInputMethodSettingsButton.setEnabled(checked);
mInputMethodSettingsButton.setClickable(checked);
mInputMethodSettingsButton.setFocusable(checked);
if (!checked) {
mInputMethodSettingsButton.setAlpha(Utils.DISABLED_ALPHA);
}
}
if (mTitleText != null) {
mTitleText.setEnabled(true);
}
if (mSummaryText != null) {
mSummaryText.setEnabled(checked);
}
if (mInputMethodPref != null) {
mInputMethodPref.setEnabled(true);
mInputMethodPref.setLongClickable(checked);
final boolean enabled = isEnabled();
mInputMethodPref.setOnClickListener(enabled ? mPrefOnclickListener : null);
if (!enabled) {
mInputMethodPref.setBackgroundColor(0);
}
}
updateSummary();
}
public static boolean startFragment(
Fragment fragment, String fragmentClass, int requestCode, Bundle extras) {
if (fragment.getActivity() instanceof SettingsActivity) {
SettingsActivity sa = (SettingsActivity) fragment.getActivity();
sa.startPreferencePanel(fragmentClass, extras, 0, null, fragment, requestCode);
return true;
} else {
Log.w(TAG, "Parent isn't Settings, thus there's no way to launch the "
+ "given Fragment (name: " + fragmentClass + ", requestCode: " + requestCode
+ ")");
return false; return false;
} }
if (isChecked()) {
// Disable this IME.
setChecked(false);
mOnSaveListener.onSaveInputMethodPreference(this);
return false;
}
if (InputMethodUtils.isSystemIme(mImi)) {
// Enable a system IME. No need to show a security warning dialog.
setChecked(true);
mOnSaveListener.onSaveInputMethodPreference(this);
return false;
}
// Enable a 3rd party IME.
showSecurityWarnDialog(mImi);
return false;
}
@Override
public boolean onPreferenceClick(final Preference preference) {
// Always returns true to prevent invoking an intent without catching exceptions.
// See {@link Preference#performClick(PreferenceScreen)}/
if (isImeEnabler()) {
// Prevent invoking a settings activity because this preference is for enabling and
// disabling an input method.
return true;
}
final Context context = getContext();
try {
final Intent intent = getIntent();
if (intent != null) {
// Invoke a settings activity of an input method.
context.startActivity(intent);
}
} catch (final ActivityNotFoundException e) {
Log.d(TAG, "IME's Settings Activity Not Found", e);
final String message = context.getString(
R.string.failed_to_open_app_settings_toast,
mImi.loadLabel(context.getPackageManager()));
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
return true;
}
void updatePreferenceViews() {
final boolean isAlwaysChecked = mInputMethodSettingValues.isAlwaysCheckedIme(
mImi, getContext());
// Only when this preference has a switch and an input method should be always enabled,
// this preference should be disabled to prevent accidentally disabling an input method.
setEnabled(!(isAlwaysChecked && isImeEnabler()));
setChecked(mInputMethodSettingValues.isEnabledImi(mImi));
setSummary(getSummaryString());
}
private InputMethodManager getInputMethodManager() {
return (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
} }
private String getSummaryString() { private String getSummaryString() {
final StringBuilder builder = new StringBuilder(); final Context context = getContext();
final List<InputMethodSubtype> subtypes = mImm.getEnabledInputMethodSubtypeList(mImi, true); final InputMethodManager imm = getInputMethodManager();
for (InputMethodSubtype subtype : subtypes) { final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(mImi, true);
if (builder.length() > 0) { final ArrayList<CharSequence> subtypeLabels = new ArrayList<>();
builder.append(", "); for (final InputMethodSubtype subtype : subtypes) {
} final CharSequence label = subtype.getDisplayName(
final CharSequence subtypeLabel = subtype.getDisplayName(mFragment.getActivity(), context, mImi.getPackageName(), mImi.getServiceInfo().applicationInfo);
mImi.getPackageName(), mImi.getServiceInfo().applicationInfo); subtypeLabels.add(label);
builder.append(subtypeLabel);
} }
return builder.toString(); // TODO: A delimiter of subtype labels should be localized.
return TextUtils.join(", ", subtypeLabels);
} }
private void updateSummary() { private void showSecurityWarnDialog(final InputMethodInfo imi) {
final String summary = getSummaryString();
if (TextUtils.isEmpty(summary)) {
return;
}
setSummary(summary);
}
/**
* Sets the checkbox state and optionally saves the settings.
* @param checked whether to check the box
* @param save whether to save IME settings
*/
private void setChecked(boolean checked, boolean save) {
final boolean wasChecked = isChecked();
super.setChecked(checked);
if (save) {
saveImeSettings();
if (wasChecked != checked && mOnImePreferenceChangeListener != null) {
mOnImePreferenceChangeListener.onPreferenceChange(this, checked);
}
}
}
public void setOnImePreferenceChangeListener(OnPreferenceChangeListener listener) {
mOnImePreferenceChangeListener = listener;
}
private void showSecurityWarnDialog(InputMethodInfo imi, final InputMethodPreference chkPref) {
if (mDialog != null && mDialog.isShowing()) { if (mDialog != null && mDialog.isShowing()) {
mDialog.dismiss(); mDialog.dismiss();
} }
mDialog = (new AlertDialog.Builder(mFragment.getActivity())) final Context context = getContext();
.setTitle(android.R.string.dialog_alert_title) final AlertDialog.Builder builder = new AlertDialog.Builder(context);
.setCancelable(true) builder.setCancelable(true /* cancelable */);
.setPositiveButton(android.R.string.ok, builder.setTitle(android.R.string.dialog_alert_title);
new DialogInterface.OnClickListener() { final CharSequence label = imi.getServiceInfo().applicationInfo.loadLabel(
@Override context.getPackageManager());
public void onClick(DialogInterface dialog, int which) { builder.setMessage(context.getString(R.string.ime_security_warning, label));
chkPref.setChecked(true, true); builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
} @Override
}) public void onClick(final DialogInterface dialog, final int which) {
.setNegativeButton(android.R.string.cancel, // The user confirmed to enable a 3rd party IME.
new DialogInterface.OnClickListener() { setChecked(true);
@Override mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this);
public void onClick(DialogInterface dialog, int which) { notifyChanged();
} }
}) });
.create(); builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
mDialog.setMessage(mFragment.getResources().getString(R.string.ime_security_warning, @Override
imi.getServiceInfo().applicationInfo.loadLabel( public void onClick(final DialogInterface dialog, final int which) {
mFragment.getActivity().getPackageManager()))); // The user canceled to enable a 3rd party IME.
setChecked(false);
mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this);
notifyChanged();
}
});
mDialog = builder.create();
mDialog.show(); mDialog.show();
} }
@Override int compareTo(final InputMethodPreference rhs, final Collator collator) {
public int compareTo(Preference p) { if (mHasPriorityInSorting == rhs.mHasPriorityInSorting) {
if (!(p instanceof InputMethodPreference)) {
return super.compareTo(p);
}
final InputMethodPreference imp = (InputMethodPreference) p;
final boolean priority0 = mIsSystemIme && mIsValidSystemNonAuxAsciiCapableIme;
final boolean priority1 = imp.mIsSystemIme && imp.mIsValidSystemNonAuxAsciiCapableIme;
if (priority0 == priority1) {
final CharSequence t0 = getTitle(); final CharSequence t0 = getTitle();
final CharSequence t1 = imp.getTitle(); final CharSequence t1 = rhs.getTitle();
if (TextUtils.isEmpty(t0)) { if (TextUtils.isEmpty(t0)) {
return 1; return 1;
} }
if (TextUtils.isEmpty(t1)) { if (TextUtils.isEmpty(t1)) {
return -1; return -1;
} }
return mCollator.compare(t0.toString(), t1.toString()); return collator.compare(t0.toString(), t1.toString());
} }
// Prefer always checked system IMEs // Prefer always checked system IMEs
return priority0 ? -1 : 1; return mHasPriorityInSorting ? -1 : 1;
}
private void saveImeSettings() {
InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(
mFragment, mFragment.getActivity().getContentResolver(), mImm.getInputMethodList(),
mFragment.getResources().getConfiguration().keyboard
== Configuration.KEYBOARD_QWERTY);
} }
} }

View File

@@ -169,7 +169,7 @@ class InputMethodSettingValuesWrapper {
return count; return count;
} }
private boolean isEnabledImi(InputMethodInfo imi) { boolean isEnabledImi(InputMethodInfo imi) {
final List<InputMethodInfo> enabledImis; final List<InputMethodInfo> enabledImis;
synchronized (mMethodMap) { synchronized (mMethodMap) {
enabledImis = mSettings.getEnabledInputMethodListLocked(); enabledImis = mSettings.getEnabledInputMethodListLocked();