InputMethodAndSubtypeEnabler.java revision ae70ee49492bd89c949946562b43a75f3c81b0ad
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 com.android.settings.R; 20import com.android.settings.SettingsPreferenceFragment; 21 22import android.app.AlertDialog; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.content.Intent; 26import android.content.pm.PackageManager; 27import android.content.res.Configuration; 28import android.os.Bundle; 29import android.preference.CheckBoxPreference; 30import android.preference.Preference; 31import android.preference.PreferenceCategory; 32import android.preference.PreferenceScreen; 33import android.text.TextUtils; 34import android.util.Log; 35import android.view.inputmethod.InputMethodInfo; 36import android.view.inputmethod.InputMethodManager; 37import android.view.inputmethod.InputMethodSubtype; 38 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 private static final String TAG =InputMethodAndSubtypeEnabler.class.getSimpleName(); 47 private AlertDialog mDialog = null; 48 private boolean mHaveHardKeyboard; 49 final private HashMap<String, List<Preference>> mInputMethodAndSubtypePrefsMap = 50 new HashMap<String, List<Preference>>(); 51 final private HashMap<String, CheckBoxPreference> mSubtypeAutoSelectionCBMap = 52 new HashMap<String, CheckBoxPreference>(); 53 private InputMethodManager mImm; 54 private List<InputMethodInfo> mInputMethodProperties; 55 private String mInputMethodId; 56 private String mTitle; 57 private String mSystemLocale = ""; 58 59 @Override 60 public void onCreate(Bundle icicle) { 61 super.onCreate(icicle); 62 mImm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); 63 final Configuration config = getResources().getConfiguration(); 64 mHaveHardKeyboard = (config.keyboard == Configuration.KEYBOARD_QWERTY); 65 66 final Bundle arguments = getArguments(); 67 // Input method id should be available from an Intent when this preference is launched as a 68 // single Activity (see InputMethodAndSubtypeEnablerActivity). It should be available 69 // from a preference argument when the preference is launched as a part of the other 70 // Activity (like a right pane of 2-pane Settings app) 71 mInputMethodId = getActivity().getIntent().getStringExtra( 72 android.provider.Settings.EXTRA_INPUT_METHOD_ID); 73 if (mInputMethodId == null && (arguments != null)) { 74 final String inputMethodId = 75 arguments.getString(android.provider.Settings.EXTRA_INPUT_METHOD_ID); 76 if (inputMethodId != null) { 77 mInputMethodId = inputMethodId; 78 } 79 } 80 mTitle = getActivity().getIntent().getStringExtra(Intent.EXTRA_TITLE); 81 if (mTitle == null && (arguments != null)) { 82 final String title = arguments.getString(Intent.EXTRA_TITLE); 83 if (title != null) { 84 mTitle = title; 85 } 86 } 87 88 mSystemLocale = config.locale.toString(); 89 onCreateIMM(); 90 setPreferenceScreen(createPreferenceHierarchy()); 91 } 92 93 @Override 94 public void onActivityCreated(Bundle icicle) { 95 super.onActivityCreated(icicle); 96 if (!TextUtils.isEmpty(mTitle)) { 97 getActivity().setTitle(mTitle); 98 } 99 } 100 101 @Override 102 public void onResume() { 103 super.onResume(); 104 InputMethodAndSubtypeUtil.loadInputMethodSubtypeList( 105 this, getContentResolver(), mInputMethodProperties, mInputMethodAndSubtypePrefsMap); 106 updateAutoSelectionCB(); 107 } 108 109 @Override 110 public void onPause() { 111 super.onPause(); 112 // Clear all subtypes of all IMEs to make sure 113 clearImplicitlyEnabledSubtypes(null); 114 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(), 115 mInputMethodProperties, mHaveHardKeyboard); 116 } 117 118 @Override 119 public boolean onPreferenceTreeClick( 120 PreferenceScreen preferenceScreen, Preference preference) { 121 122 if (preference instanceof CheckBoxPreference) { 123 final CheckBoxPreference chkPref = (CheckBoxPreference) preference; 124 125 for (String imiId: mSubtypeAutoSelectionCBMap.keySet()) { 126 if (mSubtypeAutoSelectionCBMap.get(imiId) == chkPref) { 127 // We look for the first preference item in subtype enabler. 128 // The first item is used for turning on/off subtype auto selection. 129 // We are in the subtype enabler and trying selecting subtypes automatically. 130 setSubtypeAutoSelectionEnabled(imiId, chkPref.isChecked()); 131 return super.onPreferenceTreeClick(preferenceScreen, preference); 132 } 133 } 134 135 final String id = chkPref.getKey(); 136 if (chkPref.isChecked()) { 137 InputMethodInfo selImi = null; 138 final int N = mInputMethodProperties.size(); 139 for (int i = 0; i < N; i++) { 140 InputMethodInfo imi = mInputMethodProperties.get(i); 141 if (id.equals(imi.getId())) { 142 selImi = imi; 143 if (InputMethodAndSubtypeUtil.isSystemIme(imi)) { 144 InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled( 145 this, mInputMethodProperties, id, true); 146 // This is a built-in IME, so no need to warn. 147 return super.onPreferenceTreeClick(preferenceScreen, preference); 148 } 149 break; 150 } 151 } 152 if (selImi == null) { 153 return super.onPreferenceTreeClick(preferenceScreen, preference); 154 } 155 chkPref.setChecked(false); 156 if (mDialog == null) { 157 mDialog = (new AlertDialog.Builder(getActivity())) 158 .setTitle(android.R.string.dialog_alert_title) 159 .setIcon(android.R.drawable.ic_dialog_alert) 160 .setCancelable(true) 161 .setPositiveButton(android.R.string.ok, 162 new DialogInterface.OnClickListener() { 163 @Override 164 public void onClick(DialogInterface dialog, int which) { 165 chkPref.setChecked(true); 166 InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled( 167 InputMethodAndSubtypeEnabler.this, 168 mInputMethodProperties, id, true); 169 } 170 171 }) 172 .setNegativeButton(android.R.string.cancel, 173 new DialogInterface.OnClickListener() { 174 @Override 175 public void onClick(DialogInterface dialog, int which) { 176 } 177 178 }) 179 .create(); 180 } else { 181 if (mDialog.isShowing()) { 182 mDialog.dismiss(); 183 } 184 } 185 mDialog.setMessage(getResources().getString( 186 R.string.ime_security_warning, 187 selImi.getServiceInfo().applicationInfo.loadLabel(getPackageManager()))); 188 mDialog.show(); 189 } else { 190 InputMethodAndSubtypeUtil.setSubtypesPreferenceEnabled( 191 this, mInputMethodProperties, id, false); 192 updateAutoSelectionCB(); 193 } 194 } 195 return super.onPreferenceTreeClick(preferenceScreen, preference); 196 } 197 198 @Override 199 public void onDestroy() { 200 super.onDestroy(); 201 if (mDialog != null) { 202 mDialog.dismiss(); 203 mDialog = null; 204 } 205 } 206 207 private void onCreateIMM() { 208 InputMethodManager imm = (InputMethodManager) getSystemService( 209 Context.INPUT_METHOD_SERVICE); 210 211 // TODO: Change mInputMethodProperties to Map 212 mInputMethodProperties = imm.getInputMethodList(); 213 } 214 215 private PreferenceScreen createPreferenceHierarchy() { 216 // Root 217 final PreferenceScreen root = getPreferenceManager().createPreferenceScreen(getActivity()); 218 final Context context = getActivity(); 219 220 int N = (mInputMethodProperties == null ? 0 : mInputMethodProperties.size()); 221 222 for (int i = 0; i < N; ++i) { 223 final InputMethodInfo imi = mInputMethodProperties.get(i); 224 final int subtypeCount = imi.getSubtypeCount(); 225 if (subtypeCount <= 1) continue; 226 final String imiId = imi.getId(); 227 // Add this subtype to the list when no IME is specified or when the IME of this 228 // subtype is the specified IME. 229 if (!TextUtils.isEmpty(mInputMethodId) && !mInputMethodId.equals(imiId)) { 230 continue; 231 } 232 final PreferenceCategory keyboardSettingsCategory = new PreferenceCategory(context); 233 root.addPreference(keyboardSettingsCategory); 234 final PackageManager pm = getPackageManager(); 235 final CharSequence label = imi.loadLabel(pm); 236 237 keyboardSettingsCategory.setTitle(label); 238 keyboardSettingsCategory.setKey(imiId); 239 // TODO: Use toggle Preference if images are ready. 240 final CheckBoxPreference autoCB = new CheckBoxPreference(context); 241 mSubtypeAutoSelectionCBMap.put(imiId, autoCB); 242 keyboardSettingsCategory.addPreference(autoCB); 243 244 final PreferenceCategory activeInputMethodsCategory = new PreferenceCategory(context); 245 activeInputMethodsCategory.setTitle(R.string.active_input_method_subtypes); 246 root.addPreference(activeInputMethodsCategory); 247 248 boolean isAutoSubtype = false; 249 CharSequence autoSubtypeLabel = null; 250 final ArrayList<Preference> subtypePreferences = new ArrayList<Preference>(); 251 if (subtypeCount > 0) { 252 for (int j = 0; j < subtypeCount; ++j) { 253 final InputMethodSubtype subtype = imi.getSubtypeAt(j); 254 final CharSequence subtypeLabel = subtype.getDisplayName(context, 255 imi.getPackageName(), imi.getServiceInfo().applicationInfo); 256 if (subtype.overridesImplicitlyEnabledSubtype()) { 257 if (!isAutoSubtype) { 258 isAutoSubtype = true; 259 autoSubtypeLabel = subtypeLabel; 260 } 261 } else { 262 final CheckBoxPreference chkbxPref = new SubtypeCheckBoxPreference( 263 context, subtype.getLocale(), mSystemLocale); 264 chkbxPref.setKey(imiId + subtype.hashCode()); 265 chkbxPref.setTitle(subtypeLabel); 266 subtypePreferences.add(chkbxPref); 267 } 268 } 269 Collections.sort(subtypePreferences); 270 for (int j = 0; j < subtypePreferences.size(); ++j) { 271 activeInputMethodsCategory.addPreference(subtypePreferences.get(j)); 272 } 273 mInputMethodAndSubtypePrefsMap.put(imiId, subtypePreferences); 274 } 275 if (isAutoSubtype) { 276 if (TextUtils.isEmpty(autoSubtypeLabel)) { 277 Log.w(TAG, "Title for auto subtype is empty."); 278 autoCB.setTitle("---"); 279 } else { 280 autoCB.setTitle(autoSubtypeLabel); 281 } 282 } else { 283 autoCB.setTitle(R.string.use_system_language_to_select_input_method_subtypes); 284 } 285 } 286 return root; 287 } 288 289 private boolean isNoSubtypesExplicitlySelected(String imiId) { 290 boolean allSubtypesOff = true; 291 final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); 292 for (Preference subtypePref: subtypePrefs) { 293 if (subtypePref instanceof CheckBoxPreference 294 && ((CheckBoxPreference)subtypePref).isChecked()) { 295 allSubtypesOff = false; 296 break; 297 } 298 } 299 return allSubtypesOff; 300 } 301 302 private void setSubtypeAutoSelectionEnabled(String imiId, boolean autoSelectionEnabled) { 303 CheckBoxPreference autoSelectionCB = mSubtypeAutoSelectionCBMap.get(imiId); 304 if (autoSelectionCB == null) return; 305 autoSelectionCB.setChecked(autoSelectionEnabled); 306 final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); 307 for (Preference subtypePref: subtypePrefs) { 308 if (subtypePref instanceof CheckBoxPreference) { 309 // When autoSelectionEnabled is true, all subtype prefs need to be disabled with 310 // implicitly checked subtypes. In case of false, all subtype prefs need to be 311 // enabled. 312 subtypePref.setEnabled(!autoSelectionEnabled); 313 if (autoSelectionEnabled) { 314 ((CheckBoxPreference)subtypePref).setChecked(false); 315 } 316 } 317 } 318 if (autoSelectionEnabled) { 319 InputMethodAndSubtypeUtil.saveInputMethodSubtypeList(this, getContentResolver(), 320 mInputMethodProperties, mHaveHardKeyboard); 321 setCheckedImplicitlyEnabledSubtypes(imiId); 322 } 323 } 324 325 private void setCheckedImplicitlyEnabledSubtypes(String targetImiId) { 326 updateImplicitlyEnabledSubtypes(targetImiId, true); 327 } 328 329 private void clearImplicitlyEnabledSubtypes(String targetImiId) { 330 updateImplicitlyEnabledSubtypes(targetImiId, false); 331 } 332 333 private void updateImplicitlyEnabledSubtypes(String targetImiId, boolean check) { 334 // When targetImiId is null, apply to all subtypes of all IMEs 335 for (InputMethodInfo imi: mInputMethodProperties) { 336 String imiId = imi.getId(); 337 if (targetImiId != null && !targetImiId.equals(imiId)) continue; 338 final CheckBoxPreference autoCB = mSubtypeAutoSelectionCBMap.get(imiId); 339 // No need to update implicitly enabled subtypes when the user has unchecked the 340 // "subtype auto selection". 341 if (autoCB == null || !autoCB.isChecked()) continue; 342 final List<Preference> subtypePrefs = mInputMethodAndSubtypePrefsMap.get(imiId); 343 final List<InputMethodSubtype> implicitlyEnabledSubtypes = 344 mImm.getEnabledInputMethodSubtypeList(imi, true); 345 if (subtypePrefs == null || implicitlyEnabledSubtypes == null) continue; 346 for (Preference subtypePref: subtypePrefs) { 347 if (subtypePref instanceof CheckBoxPreference) { 348 CheckBoxPreference cb = (CheckBoxPreference)subtypePref; 349 cb.setChecked(false); 350 if (check) { 351 for (InputMethodSubtype subtype: implicitlyEnabledSubtypes) { 352 String implicitlyEnabledSubtypePrefKey = imiId + subtype.hashCode(); 353 if (cb.getKey().equals(implicitlyEnabledSubtypePrefKey)) { 354 cb.setChecked(true); 355 break; 356 } 357 } 358 } 359 } 360 } 361 } 362 } 363 364 private void updateAutoSelectionCB() { 365 for (String imiId: mInputMethodAndSubtypePrefsMap.keySet()) { 366 setSubtypeAutoSelectionEnabled(imiId, isNoSubtypesExplicitlySelected(imiId)); 367 } 368 setCheckedImplicitlyEnabledSubtypes(null); 369 } 370 371 private static class SubtypeCheckBoxPreference extends CheckBoxPreference { 372 private final boolean mIsSystemLocale; 373 private final boolean mIsSystemLanguage; 374 375 public SubtypeCheckBoxPreference( 376 Context context, String subtypeLocale, String systemLocale) { 377 super(context); 378 if (TextUtils.isEmpty(subtypeLocale)) { 379 mIsSystemLocale = false; 380 mIsSystemLanguage = false; 381 } else { 382 mIsSystemLocale = subtypeLocale.equals(systemLocale); 383 mIsSystemLanguage = mIsSystemLocale 384 || subtypeLocale.startsWith(systemLocale.substring(0, 2)); 385 } 386 } 387 388 @Override 389 public int compareTo(Preference p) { 390 if (p instanceof SubtypeCheckBoxPreference) { 391 final SubtypeCheckBoxPreference pref = ((SubtypeCheckBoxPreference)p); 392 final CharSequence t0 = getTitle(); 393 final CharSequence t1 = pref.getTitle(); 394 if (TextUtils.equals(t0, t1)) { 395 return 0; 396 } 397 if (mIsSystemLocale) { 398 return -1; 399 } 400 if (pref.mIsSystemLocale) { 401 return 1; 402 } 403 if (mIsSystemLanguage) { 404 return -1; 405 } 406 if (pref.mIsSystemLanguage) { 407 return 1; 408 } 409 if (TextUtils.isEmpty(t0)) { 410 return 1; 411 } 412 if (TextUtils.isEmpty(t1)) { 413 return -1; 414 } 415 return t0.toString().compareTo(t1.toString()); 416 } else { 417 Log.w(TAG, "Illegal preference type."); 418 return -1; 419 } 420 } 421 } 422} 423