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