1/* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17package com.android.settingslib.inputmethod; 18 19import android.content.Context; 20import android.content.pm.PackageManager; 21import android.content.res.Configuration; 22import android.support.v14.preference.PreferenceFragment; 23import android.support.v7.preference.Preference; 24import android.support.v7.preference.PreferenceCategory; 25import android.support.v7.preference.PreferenceScreen; 26import android.support.v7.preference.TwoStatePreference; 27import android.text.TextUtils; 28import android.view.inputmethod.InputMethodInfo; 29import android.view.inputmethod.InputMethodManager; 30import android.view.inputmethod.InputMethodSubtype; 31 32import com.android.settingslib.R; 33 34import java.text.Collator; 35import java.util.ArrayList; 36import java.util.HashMap; 37import java.util.List; 38 39public class InputMethodAndSubtypeEnablerManager implements Preference.OnPreferenceChangeListener { 40 41 private final PreferenceFragment mFragment; 42 43 private boolean mHaveHardKeyboard; 44 private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap = 45 new HashMap<>(); 46 private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>(); 47 private InputMethodManager mImm; 48 // TODO: Change mInputMethodInfoList to Map 49 private List<InputMethodInfo> mInputMethodInfoList; 50 private final Collator mCollator = Collator.getInstance(); 51 52 public InputMethodAndSubtypeEnablerManager(PreferenceFragment fragment) { 53 mFragment = fragment; 54 mImm = fragment.getContext().getSystemService(InputMethodManager.class); 55 56 mInputMethodInfoList = mImm.getInputMethodList(); 57 } 58 59 public void init(PreferenceFragment fragment, String targetImi, PreferenceScreen root) { 60 final Configuration config = fragment.getResources().getConfiguration(); 61 mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY); 62 63 for (final InputMethodInfo imi : mInputMethodInfoList) { 64 // Add subtype preferences of this IME when it is specified or no IME is specified. 65 if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) { 66 addInputMethodSubtypePreferences(fragment, imi, root); 67 } 68 } 69 } 70 71 public void refresh(Context context, PreferenceFragment fragment) { 72 // Refresh internal states in mInputMethodSettingValues to keep the latest 73 // "InputMethodInfo"s and "InputMethodSubtype"s 74 InputMethodSettingValuesWrapper 75 .getInstance(context).refreshAllInputMethodAndSubtypes(); 76 InputMethodAndSubtypeUtil.loadInputMethodSubtypeList(fragment, context.getContentResolver(), 77 mInputMethodInfoList, mInputMethodAndSubtypePrefsMap); 78 updateAutoSelectionPreferences(); 79 } 80 81 public void save(Context context, PreferenceFragment fragment) { 82 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(fragment, context.getContentResolver(), 83 mInputMethodInfoList, mHaveHardKeyboard); 84 } 85 86 @Override 87 public boolean onPreferenceChange(final Preference pref, final Object newValue) { 88 if (!(newValue instanceof Boolean)) { 89 return true; // Invoke default behavior. 90 } 91 final boolean isChecking = (Boolean) newValue; 92 for (final String imiId : mAutoSelectionPrefsMap.keySet()) { 93 // An auto select subtype preference is changing. 94 if (mAutoSelectionPrefsMap.get(imiId) == pref) { 95 final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref; 96 autoSelectionPref.setChecked(isChecking); 97 // Enable or disable subtypes depending on the auto selection preference. 98 setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked()); 99 return false; 100 } 101 } 102 // A subtype preference is changing. 103 if (pref instanceof InputMethodSubtypePreference) { 104 final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref; 105 subtypePref.setChecked(isChecking); 106 if (!subtypePref.isChecked()) { 107 // It takes care of the case where no subtypes are explicitly enabled then the auto 108 // selection preference is going to be checked. 109 updateAutoSelectionPreferences(); 110 } 111 return false; 112 } 113 return true; // Invoke default behavior. 114 } 115 116 private void addInputMethodSubtypePreferences(PreferenceFragment fragment, InputMethodInfo imi, 117 final PreferenceScreen root) { 118 Context prefContext = fragment.getPreferenceManager().getContext(); 119 120 final int subtypeCount = imi.getSubtypeCount(); 121 if (subtypeCount <= 1) { 122 return; 123 } 124 final String imiId = imi.getId(); 125 final PreferenceCategory keyboardSettingsCategory = 126 new PreferenceCategory(prefContext); 127 root.addPreference(keyboardSettingsCategory); 128 final PackageManager pm = prefContext.getPackageManager(); 129 final CharSequence label = imi.loadLabel(pm); 130 131 keyboardSettingsCategory.setTitle(label); 132 keyboardSettingsCategory.setKey(imiId); 133 // TODO: Use toggle Preference if images are ready. 134 final TwoStatePreference autoSelectionPref = 135 new SwitchWithNoTextPreference(prefContext); 136 mAutoSelectionPrefsMap.put(imiId, autoSelectionPref); 137 keyboardSettingsCategory.addPreference(autoSelectionPref); 138 autoSelectionPref.setOnPreferenceChangeListener(this); 139 140 final PreferenceCategory activeInputMethodsCategory = 141 new PreferenceCategory(prefContext); 142 activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes); 143 root.addPreference(activeInputMethodsCategory); 144 145 CharSequence autoSubtypeLabel = null; 146 final ArrayList<Preference> subtypePreferences = new ArrayList<>(); 147 for (int index = 0; index < subtypeCount; ++index) { 148 final InputMethodSubtype subtype = imi.getSubtypeAt(index); 149 if (subtype.overridesImplicitlyEnabledSubtype()) { 150 if (autoSubtypeLabel == null) { 151 autoSubtypeLabel = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence( 152 subtype, prefContext, imi); 153 } 154 } else { 155 final Preference subtypePref = new InputMethodSubtypePreference( 156 prefContext, subtype, imi); 157 subtypePreferences.add(subtypePref); 158 } 159 } 160 subtypePreferences.sort((lhs, rhs) -> { 161 if (lhs instanceof InputMethodSubtypePreference) { 162 return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator); 163 } 164 return lhs.compareTo(rhs); 165 }); 166 for (final Preference pref : subtypePreferences) { 167 activeInputMethodsCategory.addPreference(pref); 168 pref.setOnPreferenceChangeListener(this); 169 InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref); 170 } 171 mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences); 172 if (TextUtils.isEmpty(autoSubtypeLabel)) { 173 autoSelectionPref.setTitle( 174 R.string.use_system_language_to_select_input_method_subtypes); 175 } else { 176 autoSelectionPref.setTitle(autoSubtypeLabel); 177 } 178 } 179 180 private boolean isNoSubtypesExplicitlySelected(final String imiId) { 181 final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); 182 for (final Preference pref : subtypePrefs) { 183 if (pref instanceof TwoStatePreference && ((TwoStatePreference) pref).isChecked()) { 184 return false; 185 } 186 } 187 return true; 188 } 189 190 private void setAutoSelectionSubtypesEnabled(final String imiId, 191 final boolean autoSelectionEnabled) { 192 final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId); 193 if (autoSelectionPref == null) { 194 return; 195 } 196 autoSelectionPref.setChecked(autoSelectionEnabled); 197 final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); 198 for (final Preference pref : subtypePrefs) { 199 if (pref instanceof TwoStatePreference) { 200 // When autoSelectionEnabled is true, all subtype prefs need to be disabled with 201 // implicitly checked subtypes. In case of false, all subtype prefs need to be 202 // enabled. 203 pref.setEnabled(!autoSelectionEnabled); 204 if (autoSelectionEnabled) { 205 ((TwoStatePreference) pref).setChecked(false); 206 } 207 } 208 } 209 if (autoSelectionEnabled) { 210 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList( 211 mFragment, mFragment.getContext().getContentResolver(), 212 mInputMethodInfoList, mHaveHardKeyboard); 213 updateImplicitlyEnabledSubtypes(imiId); 214 } 215 } 216 217 private void updateImplicitlyEnabledSubtypes(final String targetImiId) { 218 // When targetImiId is null, apply to all subtypes of all IMEs 219 for (final InputMethodInfo imi : mInputMethodInfoList) { 220 final String imiId = imi.getId(); 221 final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId); 222 // No need to update implicitly enabled subtypes when the user has unchecked the 223 // "subtype auto selection". 224 if (autoSelectionPref == null || !autoSelectionPref.isChecked()) { 225 continue; 226 } 227 if (imiId.equals(targetImiId) || targetImiId == null) { 228 updateImplicitlyEnabledSubtypesOf(imi); 229 } 230 } 231 } 232 233 private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi) { 234 final String imiId = imi.getId(); 235 final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); 236 final List<InputMethodSubtype> implicitlyEnabledSubtypes = 237 mImm.getEnabledInputMethodSubtypeList(imi, true); 238 if (subtypePrefs == null || implicitlyEnabledSubtypes == null) { 239 return; 240 } 241 for (final Preference pref : subtypePrefs) { 242 if (!(pref instanceof TwoStatePreference)) { 243 continue; 244 } 245 final TwoStatePreference subtypePref = (TwoStatePreference) pref; 246 subtypePref.setChecked(false); 247 for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) { 248 final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode(); 249 if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) { 250 subtypePref.setChecked(true); 251 break; 252 } 253 } 254 } 255 } 256 257 private void updateAutoSelectionPreferences() { 258 for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) { 259 setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId)); 260 } 261 updateImplicitlyEnabledSubtypes(null /* targetImiId */ /* check */); 262 } 263} 264