fix: 首次提交
This commit is contained in:
@@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.inputmethod;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Configuration;
|
||||
import android.text.TextUtils;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
|
||||
import com.android.settingslib.R;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
public class InputMethodAndSubtypeEnablerManagerCompat implements
|
||||
Preference.OnPreferenceChangeListener {
|
||||
|
||||
private final PreferenceFragmentCompat mFragment;
|
||||
|
||||
private boolean mHaveHardKeyboard;
|
||||
private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap =
|
||||
new HashMap<>();
|
||||
private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>();
|
||||
private InputMethodManager mImm;
|
||||
// TODO: Change mInputMethodInfoList to Map
|
||||
private List<InputMethodInfo> mInputMethodInfoList;
|
||||
private final Collator mCollator = Collator.getInstance();
|
||||
|
||||
public InputMethodAndSubtypeEnablerManagerCompat(PreferenceFragmentCompat fragment) {
|
||||
mFragment = fragment;
|
||||
mImm = fragment.getContext().getSystemService(InputMethodManager.class);
|
||||
|
||||
mInputMethodInfoList = mImm.getInputMethodList();
|
||||
}
|
||||
|
||||
public void init(PreferenceFragmentCompat fragment, String targetImi, PreferenceScreen root) {
|
||||
final Configuration config = fragment.getResources().getConfiguration();
|
||||
mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY);
|
||||
|
||||
for (final InputMethodInfo imi : mInputMethodInfoList) {
|
||||
// Add subtype preferences of this IME when it is specified or no IME is specified.
|
||||
if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) {
|
||||
addInputMethodSubtypePreferences(fragment, imi, root);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void refresh(Context context, PreferenceFragmentCompat fragment) {
|
||||
// Refresh internal states in mInputMethodSettingValues to keep the latest
|
||||
// "InputMethodInfo"s and "InputMethodSubtype"s
|
||||
InputMethodSettingValuesWrapper
|
||||
.getInstance(context).refreshAllInputMethodAndSubtypes();
|
||||
InputMethodAndSubtypeUtilCompat.loadInputMethodSubtypeList(fragment,
|
||||
context.getContentResolver(), mInputMethodInfoList, mInputMethodAndSubtypePrefsMap);
|
||||
updateAutoSelectionPreferences();
|
||||
}
|
||||
|
||||
public void save(Context context, PreferenceFragmentCompat fragment) {
|
||||
InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeList(fragment,
|
||||
context.getContentResolver(), mInputMethodInfoList, mHaveHardKeyboard);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(final Preference pref, final Object newValue) {
|
||||
if (!(newValue instanceof Boolean)) {
|
||||
return true; // Invoke default behavior.
|
||||
}
|
||||
final boolean isChecking = (Boolean) newValue;
|
||||
for (final String imiId : mAutoSelectionPrefsMap.keySet()) {
|
||||
// An auto select subtype preference is changing.
|
||||
if (mAutoSelectionPrefsMap.get(imiId) == pref) {
|
||||
final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref;
|
||||
autoSelectionPref.setChecked(isChecking);
|
||||
// Enable or disable subtypes depending on the auto selection preference.
|
||||
setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// A subtype preference is changing.
|
||||
if (pref instanceof InputMethodSubtypePreference) {
|
||||
final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref;
|
||||
subtypePref.setChecked(isChecking);
|
||||
if (!subtypePref.isChecked()) {
|
||||
// It takes care of the case where no subtypes are explicitly enabled then the auto
|
||||
// selection preference is going to be checked.
|
||||
updateAutoSelectionPreferences();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true; // Invoke default behavior.
|
||||
}
|
||||
|
||||
private void addInputMethodSubtypePreferences(PreferenceFragmentCompat fragment,
|
||||
InputMethodInfo imi, final PreferenceScreen root) {
|
||||
Context prefContext = fragment.getPreferenceManager().getContext();
|
||||
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
if (subtypeCount <= 1) {
|
||||
return;
|
||||
}
|
||||
final String imiId = imi.getId();
|
||||
final PreferenceCategory keyboardSettingsCategory =
|
||||
new PreferenceCategory(prefContext);
|
||||
root.addPreference(keyboardSettingsCategory);
|
||||
final PackageManager pm = prefContext.getPackageManager();
|
||||
final CharSequence label = imi.loadLabel(pm);
|
||||
|
||||
keyboardSettingsCategory.setTitle(label);
|
||||
keyboardSettingsCategory.setKey(imiId);
|
||||
// TODO: Use toggle Preference if images are ready.
|
||||
final TwoStatePreference autoSelectionPref =
|
||||
new SwitchWithNoTextPreference(prefContext);
|
||||
mAutoSelectionPrefsMap.put(imiId, autoSelectionPref);
|
||||
keyboardSettingsCategory.addPreference(autoSelectionPref);
|
||||
autoSelectionPref.setOnPreferenceChangeListener(this);
|
||||
|
||||
final PreferenceCategory activeInputMethodsCategory =
|
||||
new PreferenceCategory(prefContext);
|
||||
activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes);
|
||||
root.addPreference(activeInputMethodsCategory);
|
||||
|
||||
CharSequence autoSubtypeLabel = null;
|
||||
final ArrayList<Preference> subtypePreferences = new ArrayList<>();
|
||||
for (int index = 0; index < subtypeCount; ++index) {
|
||||
final InputMethodSubtype subtype = imi.getSubtypeAt(index);
|
||||
if (subtype.overridesImplicitlyEnabledSubtype()) {
|
||||
if (autoSubtypeLabel == null) {
|
||||
autoSubtypeLabel = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(
|
||||
subtype, prefContext, imi);
|
||||
}
|
||||
} else {
|
||||
final Preference subtypePref = new InputMethodSubtypePreference(
|
||||
prefContext, subtype, imi);
|
||||
subtypePreferences.add(subtypePref);
|
||||
}
|
||||
}
|
||||
subtypePreferences.sort((lhs, rhs) -> {
|
||||
if (lhs instanceof InputMethodSubtypePreference) {
|
||||
return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator);
|
||||
}
|
||||
return lhs.compareTo(rhs);
|
||||
});
|
||||
for (final Preference pref : subtypePreferences) {
|
||||
activeInputMethodsCategory.addPreference(pref);
|
||||
pref.setOnPreferenceChangeListener(this);
|
||||
InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref);
|
||||
}
|
||||
mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences);
|
||||
if (TextUtils.isEmpty(autoSubtypeLabel)) {
|
||||
autoSelectionPref.setTitle(
|
||||
R.string.use_system_language_to_select_input_method_subtypes);
|
||||
} else {
|
||||
autoSelectionPref.setTitle(autoSubtypeLabel);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNoSubtypesExplicitlySelected(final String imiId) {
|
||||
final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
|
||||
for (final Preference pref : subtypePrefs) {
|
||||
if (pref instanceof TwoStatePreference && ((TwoStatePreference) pref).isChecked()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setAutoSelectionSubtypesEnabled(final String imiId,
|
||||
final boolean autoSelectionEnabled) {
|
||||
final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
|
||||
if (autoSelectionPref == null) {
|
||||
return;
|
||||
}
|
||||
autoSelectionPref.setChecked(autoSelectionEnabled);
|
||||
final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
|
||||
for (final Preference pref : subtypePrefs) {
|
||||
if (pref instanceof TwoStatePreference) {
|
||||
// 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.
|
||||
pref.setEnabled(!autoSelectionEnabled);
|
||||
if (autoSelectionEnabled) {
|
||||
((TwoStatePreference) pref).setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (autoSelectionEnabled) {
|
||||
InputMethodAndSubtypeUtilCompat.saveInputMethodSubtypeList(
|
||||
mFragment, mFragment.getContext().getContentResolver(),
|
||||
mInputMethodInfoList, mHaveHardKeyboard);
|
||||
updateImplicitlyEnabledSubtypes(imiId);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateImplicitlyEnabledSubtypes(final String targetImiId) {
|
||||
// When targetImiId is null, apply to all subtypes of all IMEs
|
||||
for (final InputMethodInfo imi : mInputMethodInfoList) {
|
||||
final String imiId = imi.getId();
|
||||
final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId);
|
||||
// No need to update implicitly enabled subtypes when the user has unchecked the
|
||||
// "subtype auto selection".
|
||||
if (autoSelectionPref == null || !autoSelectionPref.isChecked()) {
|
||||
continue;
|
||||
}
|
||||
if (imiId.equals(targetImiId) || targetImiId == null) {
|
||||
updateImplicitlyEnabledSubtypesOf(imi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi) {
|
||||
final String imiId = imi.getId();
|
||||
final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId);
|
||||
final List<InputMethodSubtype> implicitlyEnabledSubtypes =
|
||||
mImm.getEnabledInputMethodSubtypeList(imi, true);
|
||||
if (subtypePrefs == null || implicitlyEnabledSubtypes == null) {
|
||||
return;
|
||||
}
|
||||
for (final Preference pref : subtypePrefs) {
|
||||
if (!(pref instanceof TwoStatePreference)) {
|
||||
continue;
|
||||
}
|
||||
final TwoStatePreference subtypePref = (TwoStatePreference) pref;
|
||||
subtypePref.setChecked(false);
|
||||
for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) {
|
||||
final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode();
|
||||
if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) {
|
||||
subtypePref.setChecked(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAutoSelectionPreferences() {
|
||||
for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) {
|
||||
setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId));
|
||||
}
|
||||
updateImplicitlyEnabledSubtypes(null /* targetImiId */ /* check */);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.settingslib.inputmethod;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.icu.text.ListFormatter;
|
||||
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.InputMethodSubtype;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragment;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
|
||||
import com.android.internal.app.LocaleHelper;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
// TODO: Consolidate this with {@link InputMethodSettingValuesWrapper}.
|
||||
public class InputMethodAndSubtypeUtil {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "InputMethdAndSubtypeUtl";
|
||||
|
||||
private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
|
||||
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 TextUtils.SimpleStringSplitter sStringInputMethodSplitter
|
||||
= new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
|
||||
|
||||
private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
|
||||
= new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
|
||||
|
||||
// InputMethods and subtypes are saved in the settings as follows:
|
||||
// ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
|
||||
public static String buildInputMethodsAndSubtypesString(
|
||||
final HashMap<String, HashSet<String>> imeToSubtypesMap) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final String imi : imeToSubtypesMap.keySet()) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(INPUT_METHOD_SEPARATER);
|
||||
}
|
||||
final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
|
||||
builder.append(imi);
|
||||
for (final String subtypeId : subtypeIdSet) {
|
||||
builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String buildInputMethodsString(final HashSet<String> imiList) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final String imi : imiList) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(INPUT_METHOD_SEPARATER);
|
||||
}
|
||||
builder.append(imi);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
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.
|
||||
static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
|
||||
ContentResolver resolver) {
|
||||
final String enabledInputMethodsStr = Settings.Secure.getString(
|
||||
resolver, Settings.Secure.ENABLED_INPUT_METHODS);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
|
||||
}
|
||||
return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
|
||||
}
|
||||
|
||||
public static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
|
||||
final String inputMethodsAndSubtypesString) {
|
||||
final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
|
||||
if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
|
||||
return subtypesMap;
|
||||
}
|
||||
sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
|
||||
while (sStringInputMethodSplitter.hasNext()) {
|
||||
final String nextImsStr = sStringInputMethodSplitter.next();
|
||||
sStringInputMethodSubtypeSplitter.setString(nextImsStr);
|
||||
if (sStringInputMethodSubtypeSplitter.hasNext()) {
|
||||
final HashSet<String> subtypeIdSet = new HashSet<>();
|
||||
// The first element is {@link InputMethodInfoId}.
|
||||
final String imiId = sStringInputMethodSubtypeSplitter.next();
|
||||
while (sStringInputMethodSubtypeSplitter.hasNext()) {
|
||||
subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
|
||||
}
|
||||
subtypesMap.put(imiId, subtypeIdSet);
|
||||
}
|
||||
}
|
||||
return subtypesMap;
|
||||
}
|
||||
|
||||
private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
|
||||
HashSet<String> set = new HashSet<>();
|
||||
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 void saveInputMethodSubtypeList(PreferenceFragment context,
|
||||
ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
|
||||
boolean hasHardKeyboard) {
|
||||
String currentInputMethodId = Settings.Secure.getString(resolver,
|
||||
Settings.Secure.DEFAULT_INPUT_METHOD);
|
||||
final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
|
||||
final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
|
||||
getEnabledInputMethodsAndSubtypeList(resolver);
|
||||
final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
|
||||
|
||||
boolean needsToResetSelectedSubtype = false;
|
||||
for (final InputMethodInfo imi : inputMethodInfos) {
|
||||
final String imiId = imi.getId();
|
||||
final Preference pref = context.findPreference(imiId);
|
||||
if (pref == null) {
|
||||
continue;
|
||||
}
|
||||
// In the choose input method screen or in the subtype enabler screen,
|
||||
// <code>pref</code> is an instance of TwoStatePreference.
|
||||
final boolean isImeChecked = (pref instanceof TwoStatePreference) ?
|
||||
((TwoStatePreference) pref).isChecked()
|
||||
: enabledIMEsAndSubtypesMap.containsKey(imiId);
|
||||
final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
|
||||
final boolean systemIme = imi.isSystem();
|
||||
if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
|
||||
context.getActivity()).isAlwaysCheckedIme(imi))
|
||||
|| isImeChecked) {
|
||||
if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
|
||||
// imiId has just been enabled
|
||||
enabledIMEsAndSubtypesMap.put(imiId, new HashSet<>());
|
||||
}
|
||||
final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
|
||||
|
||||
boolean subtypePrefFound = false;
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
for (int i = 0; i < subtypeCount; ++i) {
|
||||
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
|
||||
final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
|
||||
final TwoStatePreference subtypePref = (TwoStatePreference) context
|
||||
.findPreference(imiId + subtypeHashCodeStr);
|
||||
// In the Configure input method screen which does not have subtype preferences.
|
||||
if (subtypePref == null) {
|
||||
continue;
|
||||
}
|
||||
if (!subtypePrefFound) {
|
||||
// Once subtype preference 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;
|
||||
}
|
||||
// Checking <code>subtypePref.isEnabled()</code> is insufficient to determine
|
||||
// whether the user manually enabled this subtype or not. Implicitly-enabled
|
||||
// subtypes are also checked just as an indicator to users. We also need to
|
||||
// check <code>subtypePref.isEnabled()</code> so that only manually enabled
|
||||
// subtypes can be saved here.
|
||||
if (subtypePref.isEnabled() && 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 {
|
||||
enabledIMEsAndSubtypesMap.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
|
||||
enabledIMEsAndSubtypesMap);
|
||||
final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
|
||||
Log.d(TAG, "--- Save disabled system inputmethod settings. :"
|
||||
+ disabledSystemIMEsString);
|
||||
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, enabledIMEsAndSubtypesString);
|
||||
if (disabledSystemIMEsString.length() > 0) {
|
||||
Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
|
||||
disabledSystemIMEsString);
|
||||
}
|
||||
// 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(final PreferenceFragment context,
|
||||
final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos,
|
||||
final Map<String, List<Preference>> inputMethodPrefsMap) {
|
||||
final HashMap<String, HashSet<String>> enabledSubtypes =
|
||||
getEnabledInputMethodsAndSubtypeList(resolver);
|
||||
|
||||
for (final InputMethodInfo imi : inputMethodInfos) {
|
||||
final String imiId = imi.getId();
|
||||
final Preference pref = context.findPreference(imiId);
|
||||
if (pref instanceof TwoStatePreference) {
|
||||
final TwoStatePreference subtypePref = (TwoStatePreference) pref;
|
||||
final boolean isEnabled = enabledSubtypes.containsKey(imiId);
|
||||
subtypePref.setChecked(isEnabled);
|
||||
if (inputMethodPrefsMap != null) {
|
||||
for (final Preference childPref: inputMethodPrefsMap.get(imiId)) {
|
||||
childPref.setEnabled(isEnabled);
|
||||
}
|
||||
}
|
||||
setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
|
||||
}
|
||||
}
|
||||
updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
|
||||
}
|
||||
|
||||
private static void setSubtypesPreferenceEnabled(final PreferenceFragment context,
|
||||
final List<InputMethodInfo> inputMethodProperties, final String id,
|
||||
final boolean enabled) {
|
||||
final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
|
||||
for (final InputMethodInfo imi : inputMethodProperties) {
|
||||
if (id.equals(imi.getId())) {
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
for (int i = 0; i < subtypeCount; ++i) {
|
||||
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
|
||||
final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
|
||||
.findPreference(id + subtype.hashCode());
|
||||
if (pref != null) {
|
||||
pref.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateSubtypesPreferenceChecked(final PreferenceFragment context,
|
||||
final List<InputMethodInfo> inputMethodProperties,
|
||||
final HashMap<String, HashSet<String>> enabledSubtypes) {
|
||||
final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
|
||||
for (final InputMethodInfo imi : inputMethodProperties) {
|
||||
final String id = imi.getId();
|
||||
if (!enabledSubtypes.containsKey(id)) {
|
||||
// There is no need to enable/disable subtypes of disabled IMEs.
|
||||
continue;
|
||||
}
|
||||
final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
for (int i = 0; i < subtypeCount; ++i) {
|
||||
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
|
||||
final String hashCode = String.valueOf(subtype.hashCode());
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
|
||||
+ enabledSubtypesSet.contains(hashCode));
|
||||
}
|
||||
final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
|
||||
.findPreference(id + hashCode);
|
||||
if (pref != null) {
|
||||
pref.setChecked(enabledSubtypesSet.contains(hashCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public 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();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getSubtypeLocaleNameAsSentence(@Nullable InputMethodSubtype subtype,
|
||||
@NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo) {
|
||||
if (subtype == null) {
|
||||
return "";
|
||||
}
|
||||
final Locale locale = getDisplayLocale(context);
|
||||
final CharSequence subtypeName = subtype.getDisplayName(context,
|
||||
inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
|
||||
.applicationInfo);
|
||||
return LocaleHelper.toSentenceCase(subtypeName.toString(), locale);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getSubtypeLocaleNameListAsSentence(
|
||||
@NonNull final List<InputMethodSubtype> subtypes, @NonNull final Context context,
|
||||
@NonNull final InputMethodInfo inputMethodInfo) {
|
||||
if (subtypes.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
final Locale locale = getDisplayLocale(context);
|
||||
final int subtypeCount = subtypes.size();
|
||||
final CharSequence[] subtypeNames = new CharSequence[subtypeCount];
|
||||
for (int i = 0; i < subtypeCount; i++) {
|
||||
subtypeNames[i] = subtypes.get(i).getDisplayName(context,
|
||||
inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
|
||||
.applicationInfo);
|
||||
}
|
||||
return LocaleHelper.toSentenceCase(
|
||||
ListFormatter.getInstance(locale).format((Object[]) subtypeNames), locale);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Locale getDisplayLocale(@Nullable final Context context) {
|
||||
if (context == null) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
if (context.getResources() == null) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
final Configuration configuration = context.getResources().getConfiguration();
|
||||
if (configuration == null) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
final Locale configurationLocale = configuration.getLocales().get(0);
|
||||
if (configurationLocale == null) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
return configurationLocale;
|
||||
}
|
||||
|
||||
public static boolean isValidNonAuxAsciiCapableIme(InputMethodInfo imi) {
|
||||
if (imi.isAuxiliaryIme()) {
|
||||
return false;
|
||||
}
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
for (int i = 0; i < subtypeCount; ++i) {
|
||||
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
|
||||
if (SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
|
||||
&& subtype.isAsciiCapable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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.settingslib.inputmethod;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.Configuration;
|
||||
import android.icu.text.ListFormatter;
|
||||
import android.os.UserHandle;
|
||||
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.InputMethodSubtype;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
import androidx.preference.TwoStatePreference;
|
||||
|
||||
import com.android.internal.app.LocaleHelper;
|
||||
import com.android.settingslib.PrimarySwitchPreference;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
// TODO: Consolidate this with {@link InputMethodSettingValuesWrapper}.
|
||||
public class InputMethodAndSubtypeUtilCompat {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "InputMethdAndSubtypeUtlCompat";
|
||||
|
||||
private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
|
||||
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 TextUtils.SimpleStringSplitter sStringInputMethodSplitter
|
||||
= new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
|
||||
|
||||
private static final TextUtils.SimpleStringSplitter sStringInputMethodSubtypeSplitter
|
||||
= new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
|
||||
|
||||
// InputMethods and subtypes are saved in the settings as follows:
|
||||
// ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
|
||||
public static String buildInputMethodsAndSubtypesString(
|
||||
final HashMap<String, HashSet<String>> imeToSubtypesMap) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final String imi : imeToSubtypesMap.keySet()) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(INPUT_METHOD_SEPARATER);
|
||||
}
|
||||
final HashSet<String> subtypeIdSet = imeToSubtypesMap.get(imi);
|
||||
builder.append(imi);
|
||||
for (final String subtypeId : subtypeIdSet) {
|
||||
builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private static String buildInputMethodsString(final HashSet<String> imiList) {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (final String imi : imiList) {
|
||||
if (builder.length() > 0) {
|
||||
builder.append(INPUT_METHOD_SEPARATER);
|
||||
}
|
||||
builder.append(imi);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
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.
|
||||
static HashMap<String, HashSet<String>> getEnabledInputMethodsAndSubtypeList(
|
||||
ContentResolver resolver) {
|
||||
final String enabledInputMethodsStr = Settings.Secure.getString(
|
||||
resolver, Settings.Secure.ENABLED_INPUT_METHODS);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "--- Load enabled input methods: " + enabledInputMethodsStr);
|
||||
}
|
||||
return parseInputMethodsAndSubtypesString(enabledInputMethodsStr);
|
||||
}
|
||||
|
||||
public static HashMap<String, HashSet<String>> parseInputMethodsAndSubtypesString(
|
||||
final String inputMethodsAndSubtypesString) {
|
||||
final HashMap<String, HashSet<String>> subtypesMap = new HashMap<>();
|
||||
if (TextUtils.isEmpty(inputMethodsAndSubtypesString)) {
|
||||
return subtypesMap;
|
||||
}
|
||||
sStringInputMethodSplitter.setString(inputMethodsAndSubtypesString);
|
||||
while (sStringInputMethodSplitter.hasNext()) {
|
||||
final String nextImsStr = sStringInputMethodSplitter.next();
|
||||
sStringInputMethodSubtypeSplitter.setString(nextImsStr);
|
||||
if (sStringInputMethodSubtypeSplitter.hasNext()) {
|
||||
final HashSet<String> subtypeIdSet = new HashSet<>();
|
||||
// The first element is {@link InputMethodInfoId}.
|
||||
final String imiId = sStringInputMethodSubtypeSplitter.next();
|
||||
while (sStringInputMethodSubtypeSplitter.hasNext()) {
|
||||
subtypeIdSet.add(sStringInputMethodSubtypeSplitter.next());
|
||||
}
|
||||
subtypesMap.put(imiId, subtypeIdSet);
|
||||
}
|
||||
}
|
||||
return subtypesMap;
|
||||
}
|
||||
|
||||
private static HashSet<String> getDisabledSystemIMEs(ContentResolver resolver) {
|
||||
HashSet<String> set = new HashSet<>();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the enabled/disabled input methods and selected subtype states into system settings.
|
||||
*
|
||||
* @param fragment The preference fragment user interact with.
|
||||
* @param resolver The {@link ContentResolver} used to access the database.
|
||||
* @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
|
||||
* @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
|
||||
*/
|
||||
public static void saveInputMethodSubtypeList(PreferenceFragmentCompat fragment,
|
||||
ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
|
||||
boolean hasHardKeyboard) {
|
||||
saveInputMethodSubtypeListForUserInternal(
|
||||
fragment, resolver, inputMethodInfos, hasHardKeyboard, UserHandle.myUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the enabled/disabled input methods and selected subtype states into system settings as
|
||||
* given userId.
|
||||
*
|
||||
* @param fragment The preference fragment user interact with.
|
||||
* @param resolver The {@link ContentResolver} used to access the database.
|
||||
* @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
|
||||
* @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
|
||||
* @param userId The given userId
|
||||
*/
|
||||
public static void saveInputMethodSubtypeListForUser(PreferenceFragmentCompat fragment,
|
||||
ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
|
||||
boolean hasHardKeyboard, @UserIdInt int userId) {
|
||||
saveInputMethodSubtypeListForUserInternal(
|
||||
fragment, resolver, inputMethodInfos, hasHardKeyboard, userId);
|
||||
}
|
||||
|
||||
private static void saveInputMethodSubtypeListForUserInternal(PreferenceFragmentCompat fragment,
|
||||
ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
|
||||
boolean hasHardKeyboard, @UserIdInt int userId) {
|
||||
String currentInputMethodId = Settings.Secure.getString(resolver,
|
||||
Settings.Secure.DEFAULT_INPUT_METHOD);
|
||||
final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
|
||||
final HashMap<String, HashSet<String>> enabledIMEsAndSubtypesMap =
|
||||
getEnabledInputMethodsAndSubtypeList(resolver);
|
||||
final HashSet<String> disabledSystemIMEs = getDisabledSystemIMEs(resolver);
|
||||
|
||||
boolean needsToResetSelectedSubtype = false;
|
||||
for (final InputMethodInfo imi : inputMethodInfos) {
|
||||
final String imiId = imi.getId();
|
||||
final Preference pref = fragment.findPreference(imiId);
|
||||
if (pref == null) {
|
||||
continue;
|
||||
}
|
||||
// In the choose input method screen or in the subtype enabler screen,
|
||||
// <code>pref</code> is an instance of TwoStatePreference.
|
||||
final boolean isImeChecked;
|
||||
if (pref instanceof TwoStatePreference) {
|
||||
isImeChecked = ((TwoStatePreference) pref).isChecked();
|
||||
} else if (pref instanceof PrimarySwitchPreference) {
|
||||
isImeChecked = ((PrimarySwitchPreference) pref).isChecked();
|
||||
} else {
|
||||
isImeChecked = enabledIMEsAndSubtypesMap.containsKey(imiId);
|
||||
}
|
||||
final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
|
||||
final boolean systemIme = imi.isSystem();
|
||||
// Create context as given userId
|
||||
final Context wrapperContext = userId == UserHandle.myUserId() ? fragment.getActivity()
|
||||
: fragment.getActivity().createContextAsUser(UserHandle.of(userId), 0);
|
||||
if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
|
||||
wrapperContext).isAlwaysCheckedIme(imi))
|
||||
|| isImeChecked) {
|
||||
if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
|
||||
// imiId has just been enabled
|
||||
enabledIMEsAndSubtypesMap.put(imiId, new HashSet<>());
|
||||
}
|
||||
final HashSet<String> subtypesSet = enabledIMEsAndSubtypesMap.get(imiId);
|
||||
|
||||
boolean subtypePrefFound = false;
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
for (int i = 0; i < subtypeCount; ++i) {
|
||||
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
|
||||
final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
|
||||
final TwoStatePreference subtypePref = (TwoStatePreference) fragment
|
||||
.findPreference(imiId + subtypeHashCodeStr);
|
||||
// In the Configure input method screen which does not have subtype preferences.
|
||||
if (subtypePref == null) {
|
||||
continue;
|
||||
}
|
||||
if (!subtypePrefFound) {
|
||||
// Once subtype preference 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;
|
||||
}
|
||||
// Checking <code>subtypePref.isEnabled()</code> is insufficient to determine
|
||||
// whether the user manually enabled this subtype or not. Implicitly-enabled
|
||||
// subtypes are also checked just as an indicator to users. We also need to
|
||||
// check <code>subtypePref.isEnabled()</code> so that only manually enabled
|
||||
// subtypes can be saved here.
|
||||
if (subtypePref.isEnabled() && 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 {
|
||||
enabledIMEsAndSubtypesMap.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final String enabledIMEsAndSubtypesString = buildInputMethodsAndSubtypesString(
|
||||
enabledIMEsAndSubtypesMap);
|
||||
final String disabledSystemIMEsString = buildInputMethodsString(disabledSystemIMEs);
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "--- Save enabled inputmethod settings. :" + enabledIMEsAndSubtypesString);
|
||||
Log.d(TAG, "--- Save disabled system inputmethod settings. :"
|
||||
+ disabledSystemIMEsString);
|
||||
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, enabledIMEsAndSubtypesString);
|
||||
if (disabledSystemIMEsString.length() > 0) {
|
||||
Settings.Secure.putString(resolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS,
|
||||
disabledSystemIMEsString);
|
||||
}
|
||||
// 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(final PreferenceFragmentCompat context,
|
||||
final ContentResolver resolver, final List<InputMethodInfo> inputMethodInfos,
|
||||
final Map<String, List<Preference>> inputMethodPrefsMap) {
|
||||
final HashMap<String, HashSet<String>> enabledSubtypes =
|
||||
getEnabledInputMethodsAndSubtypeList(resolver);
|
||||
|
||||
for (final InputMethodInfo imi : inputMethodInfos) {
|
||||
final String imiId = imi.getId();
|
||||
final Preference pref = context.findPreference(imiId);
|
||||
if (pref instanceof TwoStatePreference) {
|
||||
final TwoStatePreference subtypePref = (TwoStatePreference) pref;
|
||||
final boolean isEnabled = enabledSubtypes.containsKey(imiId);
|
||||
subtypePref.setChecked(isEnabled);
|
||||
if (inputMethodPrefsMap != null) {
|
||||
for (final Preference childPref: inputMethodPrefsMap.get(imiId)) {
|
||||
childPref.setEnabled(isEnabled);
|
||||
}
|
||||
}
|
||||
setSubtypesPreferenceEnabled(context, inputMethodInfos, imiId, isEnabled);
|
||||
}
|
||||
}
|
||||
updateSubtypesPreferenceChecked(context, inputMethodInfos, enabledSubtypes);
|
||||
}
|
||||
|
||||
private static void setSubtypesPreferenceEnabled(final PreferenceFragmentCompat context,
|
||||
final List<InputMethodInfo> inputMethodProperties, final String id,
|
||||
final boolean enabled) {
|
||||
final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
|
||||
for (final InputMethodInfo imi : inputMethodProperties) {
|
||||
if (id.equals(imi.getId())) {
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
for (int i = 0; i < subtypeCount; ++i) {
|
||||
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
|
||||
final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
|
||||
.findPreference(id + subtype.hashCode());
|
||||
if (pref != null) {
|
||||
pref.setEnabled(enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void updateSubtypesPreferenceChecked(final PreferenceFragmentCompat context,
|
||||
final List<InputMethodInfo> inputMethodProperties,
|
||||
final HashMap<String, HashSet<String>> enabledSubtypes) {
|
||||
final PreferenceScreen preferenceScreen = context.getPreferenceScreen();
|
||||
for (final InputMethodInfo imi : inputMethodProperties) {
|
||||
final String id = imi.getId();
|
||||
if (!enabledSubtypes.containsKey(id)) {
|
||||
// There is no need to enable/disable subtypes of disabled IMEs.
|
||||
continue;
|
||||
}
|
||||
final HashSet<String> enabledSubtypesSet = enabledSubtypes.get(id);
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
for (int i = 0; i < subtypeCount; ++i) {
|
||||
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
|
||||
final String hashCode = String.valueOf(subtype.hashCode());
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "--- Set checked state: " + "id" + ", " + hashCode + ", "
|
||||
+ enabledSubtypesSet.contains(hashCode));
|
||||
}
|
||||
final TwoStatePreference pref = (TwoStatePreference) preferenceScreen
|
||||
.findPreference(id + hashCode);
|
||||
if (pref != null) {
|
||||
pref.setChecked(enabledSubtypesSet.contains(hashCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public 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();
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getSubtypeLocaleNameAsSentence(@Nullable InputMethodSubtype subtype,
|
||||
@NonNull final Context context, @NonNull final InputMethodInfo inputMethodInfo) {
|
||||
if (subtype == null) {
|
||||
return "";
|
||||
}
|
||||
final Locale locale = getDisplayLocale(context);
|
||||
final CharSequence subtypeName = subtype.getDisplayName(context,
|
||||
inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
|
||||
.applicationInfo);
|
||||
return LocaleHelper.toSentenceCase(subtypeName.toString(), locale);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static String getSubtypeLocaleNameListAsSentence(
|
||||
@NonNull final List<InputMethodSubtype> subtypes, @NonNull final Context context,
|
||||
@NonNull final InputMethodInfo inputMethodInfo) {
|
||||
if (subtypes.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
final Locale locale = getDisplayLocale(context);
|
||||
final int subtypeCount = subtypes.size();
|
||||
final CharSequence[] subtypeNames = new CharSequence[subtypeCount];
|
||||
for (int i = 0; i < subtypeCount; i++) {
|
||||
subtypeNames[i] = subtypes.get(i).getDisplayName(context,
|
||||
inputMethodInfo.getPackageName(), inputMethodInfo.getServiceInfo()
|
||||
.applicationInfo);
|
||||
}
|
||||
return LocaleHelper.toSentenceCase(
|
||||
ListFormatter.getInstance(locale).format((Object[]) subtypeNames), locale);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static Locale getDisplayLocale(@Nullable final Context context) {
|
||||
if (context == null) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
if (context.getResources() == null) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
final Configuration configuration = context.getResources().getConfiguration();
|
||||
if (configuration == null) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
final Locale configurationLocale = configuration.getLocales().get(0);
|
||||
if (configurationLocale == null) {
|
||||
return Locale.getDefault();
|
||||
}
|
||||
return configurationLocale;
|
||||
}
|
||||
|
||||
public static boolean isValidSystemNonAuxAsciiCapableIme(InputMethodInfo imi) {
|
||||
if (imi.isAuxiliaryIme() || !imi.isSystem()) {
|
||||
return false;
|
||||
}
|
||||
final int subtypeCount = imi.getSubtypeCount();
|
||||
for (int i = 0; i < subtypeCount; ++i) {
|
||||
final InputMethodSubtype subtype = imi.getSubtypeAt(i);
|
||||
if (SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())
|
||||
&& subtype.isAsciiCapable()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.settingslib.inputmethod;
|
||||
|
||||
import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
|
||||
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.Preference.OnPreferenceChangeListener;
|
||||
import androidx.preference.Preference.OnPreferenceClickListener;
|
||||
import androidx.preference.PreferenceViewHolder;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.settingslib.PrimarySwitchPreference;
|
||||
import com.android.settingslib.R;
|
||||
import com.android.settingslib.RestrictedLockUtilsInternal;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Input method preference.
|
||||
*
|
||||
* This preference represents an IME. It is used for two purposes. 1) Using a switch to enable or
|
||||
* disable the IME 2) Invoking the setting activity of the IME.
|
||||
*/
|
||||
public class InputMethodPreference extends PrimarySwitchPreference
|
||||
implements OnPreferenceClickListener, OnPreferenceChangeListener {
|
||||
private static final String TAG = InputMethodPreference.class.getSimpleName();
|
||||
|
||||
public 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.
|
||||
*/
|
||||
void onSaveInputMethodPreference(InputMethodPreference pref);
|
||||
}
|
||||
|
||||
private final InputMethodInfo mImi;
|
||||
private final boolean mHasPriorityInSorting;
|
||||
private final OnSavePreferenceListener mOnSaveListener;
|
||||
private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
|
||||
private final boolean mIsAllowedByOrganization;
|
||||
@UserIdInt
|
||||
private final int mUserId;
|
||||
|
||||
private AlertDialog mDialog = null;
|
||||
|
||||
/**
|
||||
* A preference entry of an input method.
|
||||
*
|
||||
* @param prefContext The Context this preference is associated with.
|
||||
* @param imi The {@link InputMethodInfo} of this preference.
|
||||
* @param isAllowedByOrganization false if the IME has been disabled by a device or profile
|
||||
* owner.
|
||||
* @param onSaveListener The listener called when this preference has been changed and needs
|
||||
* to save the state to shared preference.
|
||||
* @param userId The userId to specify the corresponding user for this preference.
|
||||
*/
|
||||
public InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
|
||||
final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener,
|
||||
final @UserIdInt int userId) {
|
||||
this(prefContext, imi, imi.loadLabel(prefContext.getPackageManager()),
|
||||
isAllowedByOrganization, onSaveListener, userId);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
|
||||
final CharSequence title, final boolean isAllowedByOrganization,
|
||||
final OnSavePreferenceListener onSaveListener, final @UserIdInt int userId) {
|
||||
super(prefContext);
|
||||
setPersistent(false);
|
||||
mImi = imi;
|
||||
mIsAllowedByOrganization = isAllowedByOrganization;
|
||||
mOnSaveListener = onSaveListener;
|
||||
setKey(imi.getId());
|
||||
setTitle(title);
|
||||
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);
|
||||
}
|
||||
// Handle the context by given userId because {@link InputMethodSettingValuesWrapper} is
|
||||
// per-user instance.
|
||||
final Context userAwareContext = userId == UserHandle.myUserId() ? prefContext :
|
||||
getContext().createContextAsUser(UserHandle.of(userId), 0);
|
||||
mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(userAwareContext);
|
||||
mUserId = userId;
|
||||
mHasPriorityInSorting = imi.isSystem()
|
||||
&& InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(imi);
|
||||
setOnPreferenceClickListener(this);
|
||||
setOnPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
public InputMethodInfo getInputMethodInfo() {
|
||||
return mImi;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(PreferenceViewHolder holder) {
|
||||
super.onBindViewHolder(holder);
|
||||
final CompoundButton switchWidget = getSwitch();
|
||||
if (switchWidget != null) {
|
||||
// Avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}.
|
||||
switchWidget.setOnClickListener(v -> {
|
||||
if (!switchWidget.isEnabled()) {
|
||||
return;
|
||||
}
|
||||
final boolean newValue = !isChecked();
|
||||
// Keep switch to previous state because we have to show the dialog first.
|
||||
switchWidget.setChecked(isChecked());
|
||||
callChangeListener(newValue);
|
||||
});
|
||||
}
|
||||
final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
|
||||
final int iconSize = getContext().getResources().getDimensionPixelSize(
|
||||
com.android.settingslib.widget.theme.R.dimen.secondary_app_icon_size);
|
||||
if (icon != null && iconSize > 0) {
|
||||
ViewGroup.LayoutParams params = icon.getLayoutParams();
|
||||
params.height = iconSize;
|
||||
params.width = iconSize;
|
||||
icon.setLayoutParams(params);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
|
||||
// Always returns false to prevent default behavior.
|
||||
// See {@link TwoStatePreference#onClick()}.
|
||||
if (isChecked()) {
|
||||
// Disable this IME.
|
||||
setCheckedInternal(false);
|
||||
return false;
|
||||
}
|
||||
if (mImi.isSystem()) {
|
||||
// Enable a system IME. No need to show a security warning dialog,
|
||||
// but we might need to prompt if it's not Direct Boot aware.
|
||||
// TV doesn't doesn't need to worry about this, but other platforms should show
|
||||
// a warning.
|
||||
if (mImi.getServiceInfo().directBootAware || isTv()) {
|
||||
setCheckedInternal(true);
|
||||
} else if (!isTv()){
|
||||
showDirectBootWarnDialog();
|
||||
}
|
||||
} else {
|
||||
// Once security is confirmed, we might prompt if the IME isn't
|
||||
// Direct Boot aware.
|
||||
showSecurityWarnDialog();
|
||||
}
|
||||
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)}/
|
||||
final Context context = getContext();
|
||||
try {
|
||||
final Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
// Invoke a settings activity of an input method.
|
||||
context.startActivityAsUser(intent, UserHandle.of(mUserId));
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
public void updatePreferenceViews() {
|
||||
final boolean isAlwaysChecked = mInputMethodSettingValues.isAlwaysCheckedIme(mImi);
|
||||
// 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.
|
||||
// This preference should also be disabled in case the admin does not allow this input
|
||||
// method.
|
||||
if (isAlwaysChecked) {
|
||||
setDisabledByAdmin(null);
|
||||
setSwitchEnabled(false);
|
||||
} else if (!mIsAllowedByOrganization) {
|
||||
EnforcedAdmin admin =
|
||||
RestrictedLockUtilsInternal.checkIfInputMethodDisallowed(
|
||||
getContext(), mImi.getPackageName(), mUserId);
|
||||
setDisabledByAdmin(admin);
|
||||
} else {
|
||||
setEnabled(true);
|
||||
setSwitchEnabled(true);
|
||||
}
|
||||
setChecked(mInputMethodSettingValues.isEnabledImi(mImi));
|
||||
if (!isDisabledByAdmin()) {
|
||||
setSummary(getSummaryString());
|
||||
}
|
||||
}
|
||||
|
||||
private InputMethodManager getInputMethodManager() {
|
||||
return (InputMethodManager)getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
}
|
||||
|
||||
private String getSummaryString() {
|
||||
final InputMethodManager imm = getInputMethodManager();
|
||||
final List<InputMethodSubtype> subtypes = imm.getEnabledInputMethodSubtypeList(mImi, true);
|
||||
return InputMethodAndSubtypeUtil.getSubtypeLocaleNameListAsSentence(
|
||||
subtypes, getContext(), mImi);
|
||||
}
|
||||
|
||||
private void setCheckedInternal(boolean checked) {
|
||||
super.setChecked(checked);
|
||||
mOnSaveListener.onSaveInputMethodPreference(InputMethodPreference.this);
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
private void showSecurityWarnDialog() {
|
||||
if (mDialog != null && mDialog.isShowing()) {
|
||||
mDialog.dismiss();
|
||||
}
|
||||
final Context context = getContext();
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setCancelable(true /* cancelable */);
|
||||
builder.setTitle(android.R.string.dialog_alert_title);
|
||||
final CharSequence label = mImi.getServiceInfo().applicationInfo.loadLabel(
|
||||
context.getPackageManager());
|
||||
builder.setMessage(context.getString(R.string.ime_security_warning, label));
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> {
|
||||
// The user confirmed to enable a 3rd party IME, but we might
|
||||
// need to prompt if it's not Direct Boot aware.
|
||||
// TV doesn't doesn't need to worry about this, but other platforms should show
|
||||
// a warning.
|
||||
if (mImi.getServiceInfo().directBootAware || isTv()) {
|
||||
setCheckedInternal(true);
|
||||
} else {
|
||||
showDirectBootWarnDialog();
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(android.R.string.cancel, (dialog, which) -> {
|
||||
// The user canceled to enable a 3rd party IME.
|
||||
setCheckedInternal(false);
|
||||
});
|
||||
builder.setOnCancelListener((dialog) -> {
|
||||
// The user canceled to enable a 3rd party IME.
|
||||
setCheckedInternal(false);
|
||||
});
|
||||
mDialog = builder.create();
|
||||
mDialog.show();
|
||||
}
|
||||
|
||||
private boolean isTv() {
|
||||
return (getContext().getResources().getConfiguration().uiMode
|
||||
& Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
|
||||
}
|
||||
|
||||
private void showDirectBootWarnDialog() {
|
||||
if (mDialog != null && mDialog.isShowing()) {
|
||||
mDialog.dismiss();
|
||||
}
|
||||
final Context context = getContext();
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setCancelable(true /* cancelable */);
|
||||
builder.setMessage(context.getText(R.string.direct_boot_unaware_dialog_message));
|
||||
builder.setPositiveButton(android.R.string.ok, (dialog, which) -> setCheckedInternal(true));
|
||||
builder.setNegativeButton(android.R.string.cancel,
|
||||
(dialog, which) -> setCheckedInternal(false));
|
||||
mDialog = builder.create();
|
||||
mDialog.show();
|
||||
}
|
||||
|
||||
public int compareTo(final InputMethodPreference rhs, final Collator collator) {
|
||||
if (this == rhs) {
|
||||
return 0;
|
||||
}
|
||||
if (mHasPriorityInSorting != rhs.mHasPriorityInSorting) {
|
||||
// Prefer always checked system IMEs
|
||||
return mHasPriorityInSorting ? -1 : 1;
|
||||
}
|
||||
final CharSequence title = getTitle();
|
||||
final CharSequence rhsTitle = rhs.getTitle();
|
||||
final boolean emptyTitle = TextUtils.isEmpty(title);
|
||||
final boolean rhsEmptyTitle = TextUtils.isEmpty(rhsTitle);
|
||||
if (!emptyTitle && !rhsEmptyTitle) {
|
||||
return collator.compare(title.toString(), rhsTitle.toString());
|
||||
}
|
||||
// For historical reasons, an empty text needs to be put at the first.
|
||||
return (emptyTitle ? -1 : 0) - (rhsEmptyTitle ? -1 : 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.settingslib.inputmethod;
|
||||
|
||||
import android.annotation.AnyThread;
|
||||
import android.annotation.UiThread;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.inputmethod.DirectBootAwareness;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class is a wrapper for {@link InputMethodManager} and
|
||||
* {@link android.provider.Settings.Secure#ENABLED_INPUT_METHODS}. You need to refresh internal
|
||||
* states manually on some events when "InputMethodInfo"s and "InputMethodSubtype"s can be changed.
|
||||
*
|
||||
* <p>TODO: Consolidate this with {@link InputMethodAndSubtypeUtil}.</p>
|
||||
*/
|
||||
@UiThread
|
||||
public class InputMethodSettingValuesWrapper {
|
||||
private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();
|
||||
|
||||
private static final Object sInstanceMapLock = new Object();
|
||||
/**
|
||||
* Manages mapping between user ID and corresponding singleton
|
||||
* {@link InputMethodSettingValuesWrapper} object.
|
||||
*/
|
||||
@GuardedBy("sInstanceMapLock")
|
||||
private static SparseArray<InputMethodSettingValuesWrapper> sInstanceMap = new SparseArray<>();
|
||||
private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
|
||||
private final ContentResolver mContentResolver;
|
||||
private final InputMethodManager mImm;
|
||||
|
||||
@AnyThread
|
||||
@NonNull
|
||||
public static InputMethodSettingValuesWrapper getInstance(@NonNull Context context) {
|
||||
final int requestUserId = context.getUserId();
|
||||
InputMethodSettingValuesWrapper valuesWrapper;
|
||||
// First time to create the wrapper.
|
||||
synchronized (sInstanceMapLock) {
|
||||
if (sInstanceMap.size() == 0) {
|
||||
valuesWrapper = new InputMethodSettingValuesWrapper(context);
|
||||
sInstanceMap.put(requestUserId, valuesWrapper);
|
||||
return valuesWrapper;
|
||||
}
|
||||
// We have same user context as request.
|
||||
if (sInstanceMap.indexOfKey(requestUserId) >= 0) {
|
||||
return sInstanceMap.get(requestUserId);
|
||||
}
|
||||
// Request by a new user context.
|
||||
valuesWrapper = new InputMethodSettingValuesWrapper(context);
|
||||
sInstanceMap.put(context.getUserId(), valuesWrapper);
|
||||
}
|
||||
|
||||
return valuesWrapper;
|
||||
}
|
||||
|
||||
// Ensure singleton
|
||||
private InputMethodSettingValuesWrapper(Context context) {
|
||||
mContentResolver = context.getContentResolver();
|
||||
mImm = context.getSystemService(InputMethodManager.class);
|
||||
refreshAllInputMethodAndSubtypes();
|
||||
}
|
||||
|
||||
public void refreshAllInputMethodAndSubtypes() {
|
||||
mMethodList.clear();
|
||||
List<InputMethodInfo> imis = mImm.getInputMethodListAsUser(
|
||||
mContentResolver.getUserId(), DirectBootAwareness.ANY);
|
||||
for (int i = 0; i < imis.size(); ++i) {
|
||||
InputMethodInfo imi = imis.get(i);
|
||||
if (!imi.isVirtualDeviceOnly()) {
|
||||
mMethodList.add(imi);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<InputMethodInfo> getInputMethodList() {
|
||||
return new ArrayList<>(mMethodList);
|
||||
}
|
||||
|
||||
public boolean isAlwaysCheckedIme(InputMethodInfo imi) {
|
||||
final boolean isEnabled = isEnabledImi(imi);
|
||||
if (getEnabledInputMethodList().size() <= 1 && isEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final int enabledValidNonAuxAsciiCapableImeCount =
|
||||
getEnabledValidNonAuxAsciiCapableImeCount();
|
||||
|
||||
return enabledValidNonAuxAsciiCapableImeCount <= 1
|
||||
&& !(enabledValidNonAuxAsciiCapableImeCount == 1 && !isEnabled)
|
||||
&& imi.isSystem()
|
||||
&& InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(imi);
|
||||
}
|
||||
|
||||
private int getEnabledValidNonAuxAsciiCapableImeCount() {
|
||||
int count = 0;
|
||||
final List<InputMethodInfo> enabledImis = getEnabledInputMethodList();
|
||||
for (final InputMethodInfo imi : enabledImis) {
|
||||
if (InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(imi)) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
if (count == 0) {
|
||||
Log.w(TAG, "No \"enabledValidNonAuxAsciiCapableIme\"s found.");
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public boolean isEnabledImi(InputMethodInfo imi) {
|
||||
final List<InputMethodInfo> enabledImis = getEnabledInputMethodList();
|
||||
for (final InputMethodInfo tempImi : enabledImis) {
|
||||
if (tempImi.getId().equals(imi.getId())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of the enabled {@link InputMethodInfo} determined by
|
||||
* {@link android.provider.Settings.Secure#ENABLED_INPUT_METHODS} rather than just returning
|
||||
* {@link InputMethodManager#getEnabledInputMethodList()}.
|
||||
*
|
||||
* @return the list of the enabled {@link InputMethodInfo}
|
||||
*/
|
||||
private ArrayList<InputMethodInfo> getEnabledInputMethodList() {
|
||||
final HashMap<String, HashSet<String>> enabledInputMethodsAndSubtypes =
|
||||
InputMethodAndSubtypeUtil.getEnabledInputMethodsAndSubtypeList(mContentResolver);
|
||||
final ArrayList<InputMethodInfo> result = new ArrayList<>();
|
||||
for (InputMethodInfo imi : mMethodList) {
|
||||
if (enabledInputMethodsAndSubtypes.keySet().contains(imi.getId())) {
|
||||
result.add(imi);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.settingslib.inputmethod;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Input method subtype preference.
|
||||
*
|
||||
* This preference represents a subtype of an IME. It is used to enable or disable the subtype.
|
||||
*/
|
||||
public class InputMethodSubtypePreference extends SwitchWithNoTextPreference {
|
||||
private final boolean mIsSystemLocale;
|
||||
private final boolean mIsSystemLanguage;
|
||||
|
||||
public InputMethodSubtypePreference(final Context context, final InputMethodSubtype subtype,
|
||||
final InputMethodInfo imi) {
|
||||
this(context,
|
||||
imi.getId() + subtype.hashCode(),
|
||||
InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence(subtype, context, imi),
|
||||
subtype.getLocaleObject(),
|
||||
context.getResources().getConfiguration().locale);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
InputMethodSubtypePreference(
|
||||
final Context context,
|
||||
final String prefKey,
|
||||
final CharSequence title,
|
||||
final Locale subtypeLocale,
|
||||
final Locale systemLocale) {
|
||||
super(context);
|
||||
setPersistent(false);
|
||||
setKey(prefKey);
|
||||
setTitle(title);
|
||||
if (subtypeLocale == null) {
|
||||
mIsSystemLocale = false;
|
||||
mIsSystemLanguage = false;
|
||||
} else {
|
||||
mIsSystemLocale = subtypeLocale.equals(systemLocale);
|
||||
mIsSystemLanguage = mIsSystemLocale
|
||||
|| TextUtils.equals(subtypeLocale.getLanguage(), systemLocale.getLanguage());
|
||||
}
|
||||
}
|
||||
|
||||
public int compareTo(final Preference rhs, final Collator collator) {
|
||||
if (this == rhs) {
|
||||
return 0;
|
||||
}
|
||||
if (rhs instanceof InputMethodSubtypePreference) {
|
||||
final InputMethodSubtypePreference rhsPref = (InputMethodSubtypePreference) rhs;
|
||||
if (mIsSystemLocale && !rhsPref.mIsSystemLocale) {
|
||||
return -1;
|
||||
}
|
||||
if (!mIsSystemLocale && rhsPref.mIsSystemLocale) {
|
||||
return 1;
|
||||
}
|
||||
if (mIsSystemLanguage && !rhsPref.mIsSystemLanguage) {
|
||||
return -1;
|
||||
}
|
||||
if (!mIsSystemLanguage && rhsPref.mIsSystemLanguage) {
|
||||
return 1;
|
||||
}
|
||||
final CharSequence title = getTitle();
|
||||
final CharSequence rhsTitle = rhs.getTitle();
|
||||
final boolean emptyTitle = TextUtils.isEmpty(title);
|
||||
final boolean rhsEmptyTitle = TextUtils.isEmpty(rhsTitle);
|
||||
if (!emptyTitle && !rhsEmptyTitle) {
|
||||
return collator.compare(title.toString(), rhsTitle.toString());
|
||||
}
|
||||
// For historical reasons, an empty text needs to be put at the first.
|
||||
return (emptyTitle ? -1 : 0) - (rhsEmptyTitle ? -1 : 0);
|
||||
}
|
||||
return super.compareTo(rhs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Bug component: 34867
|
||||
|
||||
include platform/frameworks/base:/services/core/java/com/android/server/inputmethod/OWNERS
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (C) 2017 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.settingslib.inputmethod;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
public class SwitchWithNoTextPreference extends SwitchPreference {
|
||||
private static final String EMPTY_TEXT = "";
|
||||
|
||||
public SwitchWithNoTextPreference(final Context context) {
|
||||
super(context);
|
||||
setSwitchTextOn(EMPTY_TEXT);
|
||||
setSwitchTextOff(EMPTY_TEXT);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user