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