1/* 2 * Copyright (C) 2010 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.settings.inputmethod; 18 19import android.content.Context; 20import android.content.Intent; 21import android.content.pm.PackageManager; 22import android.content.res.Configuration; 23import android.os.Bundle; 24import android.support.v7.preference.Preference; 25import android.support.v7.preference.Preference.OnPreferenceChangeListener; 26import android.support.v7.preference.PreferenceCategory; 27import android.support.v7.preference.PreferenceScreen; 28import android.support.v7.preference.TwoStatePreference; 29import android.text.TextUtils; 30import android.view.inputmethod.InputMethodInfo; 31import android.view.inputmethod.InputMethodManager; 32import android.view.inputmethod.InputMethodSubtype; 33 34import com.android.internal.logging.MetricsProto.MetricsEvent; 35import com.android.settings.R; 36import com.android.settings.SettingsPreferenceFragment; 37 38import java.text.Collator; 39import java.util.ArrayList; 40import java.util.Collections; 41import java.util.Comparator; 42import java.util.HashMap; 43import java.util.List; 44 45public class InputMethodAndSubtypeEnabler extends SettingsPreferenceFragment 46 implements OnPreferenceChangeListener { 47 private boolean mHaveHardKeyboard; 48 private final HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap = 49 new HashMap<>(); 50 private final HashMap<String, TwoStatePreference> mAutoSelectionPrefsMap = new HashMap<>(); 51 private InputMethodManager mImm; 52 // TODO: Change mInputMethodInfoList to Map 53 private List<InputMethodInfo> mInputMethodInfoList; 54 private Collator mCollator; 55 56 @Override 57 protected int getMetricsCategory() { 58 return MetricsEvent.INPUTMETHOD_SUBTYPE_ENABLER; 59 } 60 61 @Override 62 public void onCreate(final Bundle icicle) { 63 super.onCreate(icicle); 64 mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 65 final Configuration config = getResources().getConfiguration(); 66 mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY); 67 68 // Input method id should be available from an Intent when this preference is launched as a 69 // single Activity (see InputMethodAndSubtypeEnablerActivity). It should be available 70 // from a preference argument when the preference is launched as a part of the other 71 // Activity (like a right pane of 2-pane Settings app) 72 final String targetImi = getStringExtraFromIntentOrArguments( 73 android.provider.Settings.EXTRA_INPUT_METHOD_ID); 74 75 mInputMethodInfoList = mImm.getInputMethodList(); 76 mCollator = Collator.getInstance(); 77 78 final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity()); 79 final int imiCount = mInputMethodInfoList.size(); 80 for (int index = 0; index < imiCount; ++index) { 81 final InputMethodInfo imi = mInputMethodInfoList.get(index); 82 // Add subtype preferences of this IME when it is specified or no IME is specified. 83 if (imi.getId().equals(targetImi) || TextUtils.isEmpty(targetImi)) { 84 addInputMethodSubtypePreferences(imi, root); 85 } 86 } 87 setPreferenceScreen(root); 88 } 89 90 private String getStringExtraFromIntentOrArguments(final String name) { 91 final Intent intent = getActivity().getIntent(); 92 final String fromIntent = intent.getStringExtra(name); 93 if (fromIntent != null) { 94 return fromIntent; 95 } 96 final Bundle arguments = getArguments(); 97 return (arguments == null) ? null : arguments.getString(name); 98 } 99 100 @Override 101 public void onActivityCreated(final Bundle icicle) { 102 super.onActivityCreated(icicle); 103 final String title = getStringExtraFromIntentOrArguments(Intent.EXTRA_TITLE); 104 if (!TextUtils.isEmpty(title)) { 105 getActivity().setTitle(title); 106 } 107 } 108 109 @Override 110 public void onResume() { 111 super.onResume(); 112 // Refresh internal states in mInputMethodSettingValues to keep the latest 113 // "InputMethodInfo"s and "InputMethodSubtype"s 114 InputMethodSettingValuesWrapper 115 .getInstance(getActivity()).refreshAllInputMethodAndSubtypes(); 116 InputMethodAndSubtypeUtil.loadInputMethodSubtypeList( 117 this, getContentResolver(), mInputMethodInfoList, mInputMethodAndSubtypePrefsMap); 118 updateAutoSelectionPreferences(); 119 } 120 121 @Override 122 public void onPause() { 123 super.onPause(); 124 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(), 125 mInputMethodInfoList, mHaveHardKeyboard); 126 } 127 128 @Override 129 public boolean onPreferenceChange(final Preference pref, final Object newValue) { 130 if (!(newValue instanceof Boolean)) { 131 return true; // Invoke default behavior. 132 } 133 final boolean isChecking = (Boolean) newValue; 134 for (final String imiId : mAutoSelectionPrefsMap.keySet()) { 135 // An auto select subtype preference is changing. 136 if (mAutoSelectionPrefsMap.get(imiId) == pref) { 137 final TwoStatePreference autoSelectionPref = (TwoStatePreference) pref; 138 autoSelectionPref.setChecked(isChecking); 139 // Enable or disable subtypes depending on the auto selection preference. 140 setAutoSelectionSubtypesEnabled(imiId, autoSelectionPref.isChecked()); 141 return false; 142 } 143 } 144 // A subtype preference is changing. 145 if (pref instanceof InputMethodSubtypePreference) { 146 final InputMethodSubtypePreference subtypePref = (InputMethodSubtypePreference) pref; 147 subtypePref.setChecked(isChecking); 148 if (!subtypePref.isChecked()) { 149 // It takes care of the case where no subtypes are explicitly enabled then the auto 150 // selection preference is going to be checked. 151 updateAutoSelectionPreferences(); 152 } 153 return false; 154 } 155 return true; // Invoke default behavior. 156 } 157 158 private void addInputMethodSubtypePreferences(final InputMethodInfo imi, 159 final PreferenceScreen root) { 160 final Context context = getPrefContext(); 161 final int subtypeCount = imi.getSubtypeCount(); 162 if (subtypeCount <= 1) { 163 return; 164 } 165 final String imiId = imi.getId(); 166 final PreferenceCategory keyboardSettingsCategory = 167 new PreferenceCategory(getPrefContext()); 168 root.addPreference(keyboardSettingsCategory); 169 final PackageManager pm = getPackageManager(); 170 final CharSequence label = imi.loadLabel(pm); 171 172 keyboardSettingsCategory.setTitle(label); 173 keyboardSettingsCategory.setKey(imiId); 174 // TODO: Use toggle Preference if images are ready. 175 final TwoStatePreference autoSelectionPref = 176 new SwitchWithNoTextPreference(getPrefContext()); 177 mAutoSelectionPrefsMap.put(imiId, autoSelectionPref); 178 keyboardSettingsCategory.addPreference(autoSelectionPref); 179 autoSelectionPref.setOnPreferenceChangeListener(this); 180 181 final PreferenceCategory activeInputMethodsCategory = 182 new PreferenceCategory(getPrefContext()); 183 activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes); 184 root.addPreference(activeInputMethodsCategory); 185 186 CharSequence autoSubtypeLabel = null; 187 final ArrayList<Preference> subtypePreferences = new ArrayList<>(); 188 for (int index = 0; index < subtypeCount; ++index) { 189 final InputMethodSubtype subtype = imi.getSubtypeAt(index); 190 if (subtype.overridesImplicitlyEnabledSubtype()) { 191 if (autoSubtypeLabel == null) { 192 autoSubtypeLabel = InputMethodAndSubtypeUtil.getSubtypeLocaleNameAsSentence( 193 subtype, context, imi); 194 } 195 } else { 196 final Preference subtypePref = new InputMethodSubtypePreference( 197 context, subtype, imi); 198 subtypePreferences.add(subtypePref); 199 } 200 } 201 Collections.sort(subtypePreferences, new Comparator<Preference>() { 202 @Override 203 public int compare(final Preference lhs, final Preference rhs) { 204 if (lhs instanceof InputMethodSubtypePreference) { 205 return ((InputMethodSubtypePreference) lhs).compareTo(rhs, mCollator); 206 } 207 return lhs.compareTo(rhs); 208 } 209 }); 210 final int prefCount = subtypePreferences.size(); 211 for (int index = 0; index < prefCount; ++index) { 212 final Preference pref = subtypePreferences.get(index); 213 activeInputMethodsCategory.addPreference(pref); 214 pref.setOnPreferenceChangeListener(this); 215 InputMethodAndSubtypeUtil.removeUnnecessaryNonPersistentPreference(pref); 216 } 217 mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences); 218 if (TextUtils.isEmpty(autoSubtypeLabel)) { 219 autoSelectionPref.setTitle( 220 R.string.use_system_language_to_select_input_method_subtypes); 221 } else { 222 autoSelectionPref.setTitle(autoSubtypeLabel); 223 } 224 } 225 226 private boolean isNoSubtypesExplicitlySelected(final String imiId) { 227 final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); 228 for (final Preference pref : subtypePrefs) { 229 if (pref instanceof TwoStatePreference && ((TwoStatePreference)pref).isChecked()) { 230 return false; 231 } 232 } 233 return true; 234 } 235 236 private void setAutoSelectionSubtypesEnabled(final String imiId, 237 final boolean autoSelectionEnabled) { 238 final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId); 239 if (autoSelectionPref == null) { 240 return; 241 } 242 autoSelectionPref.setChecked(autoSelectionEnabled); 243 final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); 244 for (final Preference pref : subtypePrefs) { 245 if (pref instanceof TwoStatePreference) { 246 // When autoSelectionEnabled is true, all subtype prefs need to be disabled with 247 // implicitly checked subtypes. In case of false, all subtype prefs need to be 248 // enabled. 249 pref.setEnabled(!autoSelectionEnabled); 250 if (autoSelectionEnabled) { 251 ((TwoStatePreference)pref).setChecked(false); 252 } 253 } 254 } 255 if (autoSelectionEnabled) { 256 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList( 257 this, getContentResolver(), mInputMethodInfoList, mHaveHardKeyboard); 258 updateImplicitlyEnabledSubtypes(imiId, true /* check */); 259 } 260 } 261 262 private void updateImplicitlyEnabledSubtypes(final String targetImiId, final boolean check) { 263 // When targetImiId is null, apply to all subtypes of all IMEs 264 for (final InputMethodInfo imi : mInputMethodInfoList) { 265 final String imiId = imi.getId(); 266 final TwoStatePreference autoSelectionPref = mAutoSelectionPrefsMap.get(imiId); 267 // No need to update implicitly enabled subtypes when the user has unchecked the 268 // "subtype auto selection". 269 if (autoSelectionPref == null || !autoSelectionPref.isChecked()) { 270 continue; 271 } 272 if (imiId.equals(targetImiId) || targetImiId == null) { 273 updateImplicitlyEnabledSubtypesOf(imi, check); 274 } 275 } 276 } 277 278 private void updateImplicitlyEnabledSubtypesOf(final InputMethodInfo imi, final boolean check) { 279 final String imiId = imi.getId(); 280 final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); 281 final List<InputMethodSubtype> implicitlyEnabledSubtypes = 282 mImm.getEnabledInputMethodSubtypeList(imi, true); 283 if (subtypePrefs == null || implicitlyEnabledSubtypes == null) { 284 return; 285 } 286 for (final Preference pref : subtypePrefs) { 287 if (!(pref instanceof TwoStatePreference)) { 288 continue; 289 } 290 final TwoStatePreference subtypePref = (TwoStatePreference)pref; 291 subtypePref.setChecked(false); 292 if (check) { 293 for (final InputMethodSubtype subtype : implicitlyEnabledSubtypes) { 294 final String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode(); 295 if (subtypePref.getKey().equals(implicitlyEnabledSubtypePrefKey)) { 296 subtypePref.setChecked(true); 297 break; 298 } 299 } 300 } 301 } 302 } 303 304 private void updateAutoSelectionPreferences() { 305 for (final String imiId : mInputMethodAndSubtypePrefsMap.keySet()) { 306 setAutoSelectionSubtypesEnabled(imiId, isNoSubtypesExplicitlySelected(imiId)); 307 } 308 updateImplicitlyEnabledSubtypes(null /* targetImiId */, true /* check */); 309 } 310} 311