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