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